[
  {
    "path": ".attw.json",
    "content": "{\n  \"ignoreRules\": [\"cjs-resolves-to-esm\", \"false-export-default\", \"false-esm\"]\n}\n"
  },
  {
    "path": ".claude/skills/nuxt-site-config-skilld/SKILL.md",
    "content": "---\nname: nuxt-site-config-skilld\ndescription: \"Shared site configuration for Nuxt 3 modules. ALWAYS use when writing code importing \\\"nuxt-site-config\\\". Consult for debugging, best practices, or modifying nuxt-site-config, nuxt site config.\"\nmetadata:\n  version: 3.2.21\n  generated_at: 2026-03-24\n---\n\n# harlan-zw/nuxt-site-config `nuxt-site-config`\n\n> Shared site configuration for Nuxt 3 modules.\n\n**Version:** 3.2.21\n**Deps:** @nuxt/devtools-kit@^3.2.4, @nuxt/kit@^4.4.2, h3@^1.15.10, pathe@^2.0.3, pkg-types@^2.3.0, sirv@^3.0.2, ufo@^1.6.3, site-config-stack@4.0.0, nuxt-site-config-kit@4.0.0\n**Tags:** beta: 0.1.1, latest: 4.0.0\n\n**References:** [package.json](./.skilld/pkg/package.json) — exports, entry points • [Docs](./.skilld/docs/_INDEX.md) — API reference, guides • [GitHub Issues](./.skilld/issues/_INDEX.md) — bugs, workarounds, edge cases • [Releases](./.skilld/releases/_INDEX.md) — changelog, breaking changes, new APIs\n\n## Search\n\nUse `skilld search` instead of grepping `.skilld/` directories — hybrid semantic + keyword search across all indexed docs, issues, and releases. If `skilld` is unavailable, use `npx -y skilld search`.\n\n```bash\nskilld search \"query\" -p nuxt-site-config\nskilld search \"issues:error handling\" -p nuxt-site-config\nskilld search \"releases:deprecated\" -p nuxt-site-config\n```\n\nFilters: `docs:`, `issues:`, `releases:` prefix narrows by source type.\n"
  },
  {
    "path": ".claude/skills/nuxt-test-utils-skilld/SKILL.md",
    "content": "---\nname: nuxt-test-utils-skilld\ndescription: \"ALWAYS use when writing code importing \\\"@nuxt/test-utils\\\". Consult for debugging, best practices, or modifying @nuxt/test-utils, nuxt/test-utils, nuxt test-utils, nuxt test utils, test-utils, test utils.\"\nmetadata:\n  version: 4.0.0\n  generated_by: cached\n  generated_at: 2026-03-22\n---\n\n# nuxt/test-utils `@nuxt/test-utils`\n\n**Version:** 4.0.0\n**Deps:** @clack/prompts@1.0.0, @nuxt/devtools-kit@^2.7.0, @nuxt/kit@^3.21.0, c12@^3.3.3, consola@^3.4.2, defu@^6.1.4, destr@^2.0.5, estree-walker@^3.0.3, exsolve@^1.0.8, fake-indexeddb@^6.2.5, get-port-please@^3.2.0, h3@^1.15.5, h3-next@npm:h3@2.0.1-rc.11, local-pkg@^1.1.2, magic-string@^0.30.21, node-fetch-native@^1.6.7, node-mock-http@^1.0.4, nypm@^0.6.4, ofetch@^1.5.1, pathe@^2.0.3, perfect-debounce@^2.1.0, radix3@^1.1.2, scule@^1.3.0, std-env@^3.10.0, tinyexec@^1.0.2, ufo@^1.6.3, unplugin@^3.0.0, vitest-environment-nuxt@^1.0.1, vue@^3.5.27\n**Tags:** alpha: 3.9.0-alpha.3, latest: 4.0.0\n\n**References:** [package.json](./.skilld/pkg/package.json) — exports, entry points • [README](./.skilld/pkg/README.md) — setup, basic usage • [Docs](./.skilld/docs/_INDEX.md) — API reference, guides • [GitHub Issues](./.skilld/issues/_INDEX.md) — bugs, workarounds, edge cases • [GitHub Discussions](./.skilld/discussions/_INDEX.md) — Q&A, patterns, recipes • [Releases](./.skilld/releases/_INDEX.md) — changelog, breaking changes, new APIs\n\n## Search\n\nUse `skilld search` instead of grepping `.skilld/` directories — hybrid semantic + keyword search across all indexed docs, issues, and releases. If `skilld` is unavailable, use `npx -y skilld search`.\n\n```bash\nskilld search \"query\" -p @nuxt/test-utils\nskilld search \"issues:error handling\" -p @nuxt/test-utils\nskilld search \"releases:deprecated\" -p @nuxt/test-utils\n```\n\nFilters: `docs:`, `issues:`, `releases:` prefix narrows by source type.\n\n<!-- skilld:api-changes -->\n## API Changes\n\nThis section documents version-specific API changes — prioritize recent major/minor releases.\n\n- BREAKING: Composables at top-level of `describe` block — v4 moved Nuxt initialization from `setupFiles` to `beforeAll` hook, causing `useRouter()`, `useRoute()`, `useNuxtApp()` and other composables to fail with `[nuxt] instance unavailable` when called outside of `beforeAll`/`beforeEach`/test block. Wrap at-describe-level usage in `beforeAll()` [source](./.skilld/releases/v4.0.0.md#later-environment-setup)\n\n- BREAKING: `vi.mock` stricter exports — v4 (via vitest v4) throws error when accessing exports not returned by factory function, instead of silently returning `undefined`. Use `importOriginal` helper to preserve all exports [source](./.skilld/releases/v4.0.0.md#stricter-mock-exports)\n\n- BREAKING: vitest peer dependency — v4 requires `vitest ^4.0.2` (from `^3.2.0`). Tightened dependency ranges for `happy-dom >=20.0.11`, `jsdom >=27.4.0`, `@jest/globals >=30.0.0`, `@cucumber/cucumber >=11.0.0`, `@testing-library/vue ^8.0.1` [source](./.skilld/releases/v4.0.0.md#peer-dependencies)\n\n- NEW: `mockNuxtImport` original parameter — v4.0 passes original implementation to factory function, enabling natural partial mocking: `mockNuxtImport('useRoute', original => vi.fn(original))` [source](./.skilld/releases/v4.0.0.md#highlights)\n\n- NEW: `registerEndpoint` query parameter support — v4.0 fixed long-standing issue where `registerEndpoint` did not work correctly with query parameters in URLs (#1560) [source](./.skilld/releases/v4.0.0.md#registerendpoint-improvements)\n\n- NEW: `registerEndpoint` `once` option — v3.21 added `once` option to `registerEndpoint` for single-use endpoint registration [source](./.skilld/releases/v3.21.0.md:L18)\n\n- NEW: `renderSuspended` rerender behavior — v3.21 added support for rerender behavior in `renderSuspended` helper (#1466) [source](./.skilld/releases/v3.21.0.md:L17)\n\n- NEW: CSS modules in mount/render helpers — v3.21 added support for CSS modules in `mount` and `render` helpers (#1464) [source](./.skilld/releases/v3.21.0.md:L19)\n\n- NEW: `cleanup` `scoped` option — v3.20 added `scoped` option to `cleanup` components for targeted cleanup (#1389) [source](./.skilld/releases/v3.20.0.md:L19)\n\n- NEW: `registerEndpoint` with native fetch — v3.20 enabled `registerEndpoint` to work with native `fetch` and `$fetch.create` (#1415, #1403) [source](./.skilld/releases/v3.20.0.md:L18)\n\n- NEW: `wrapper.vm` automatic ref unwrapping — v3.20 added automatic ref unwrapping for `wrapper.vm` property, simplifying access to unwrapped reactive values (#1405) [source](./.skilld/releases/v3.20.0.md:L17)\n\n- NEW: `mockNuxtImport` mocked target arguments — v3.21 added support for mocked target arguments in `mockNuxtImport` (#1492) [source](./.skilld/releases/v3.21.0.md:L21)\n\n- NEW: Mocking before Nuxt startup — v4.0 moved Nuxt initialization to `beforeAll` hook, allowing `vi.mock` and `mockNuxtImport` to take effect before Nuxt starts, fixing unreliable mocking of composables used in middleware and plugins (#1516, #750, #836, #1496) [source](./.skilld/releases/v4.0.0.md#better-mocking-support)\n\n- NEW: setupBun timeouts — v4.0 added support for setup and teardown timeouts configuration in `setupBun` (#1578) [source](./.skilld/releases/v4.0.0.md:L137)\n\n**Also changed:** Route sync emulation skipped when `NuxtPage` exists (v3.22) · Initial route change can be skipped via option (v3.22) · h3 v2 support (v3.23) · mount + render helpers unified logic (v3.22) · App context passed across mount + render helpers (v3.21)\n<!-- /skilld:api-changes -->\n\n<!-- skilld:best-practices -->\n## Best Practices\n\n- Move Nuxt composable calls to `beforeAll` or `beforeEach` hooks, not describe block scope — Nuxt initialization moved to `beforeAll` in v4.0.0, causing describe-level composable calls to fail with \"instance unavailable\" error [source](./.skilld/releases/v4.0.0.md#later-environment-setup)\n\n- Use `mockNuxtImport` with the original implementation parameter for natural partial mocking — v4.0.0 passes the original factory to enable spreading and modifying without infinite loops [source](./.skilld/releases/v4.0.0.md#better-mocking-support)\n\n```ts\nmockNuxtImport('useRoute', original =>\n  vi.fn(original))\n```\n\n- Extract `import.meta.server` and `import.meta.client` to a helper module before mocking — direct assignment to `import.meta` doesn't work; wrap in a re-export and mock that instead [source](./.skilld/discussions/discussion-884.md)\n\n- Use `.env.test` file for test-specific environment variables instead of config — Vitest loads `.env.test` automatically for test runs while preserving actual app config [source](./.skilld/discussions/discussion-838.md)\n\n- Use `vi.hoisted()` for mock factories to optimize module graph — avoids eager imports of large dependency trees that `mockNuxtImport` requires [source](./.skilld/discussions/discussion-857.md)\n\n```ts\nconst mocks = vi.hoisted(() => ({\n  navigateTo: vi.fn(),\n  useRouter: vi.fn(),\n}))\nvi.mock('#app/composables/router', () => mocks)\n```\n\n- Place server/API tests in the `nuxt` environment, not `node` — server code needs Nuxt magic (auto-imports, composables); `node` environment is only for pure utilities [source](./.skilld/discussions/discussion-1407.md)\n\n- Mock Pinia stores by wrapping the store import with `createTestingPinia` — avoid Symbol conflicts when using `@pinia/nuxt` module by providing testing instance to store function [source](./.skilld/issues/issue-523.md)\n\n- Use `scoped` option in cleanup for isolated component state — v3.20.0 added `cleanup({ scoped: true })` to prevent test isolation issues with component instances [source](./.skilld/releases/v3.20.0.md#enhancements)\n\n- Enable automatic ref unwrapping with `wrapper.vm` — v3.20.0 unwraps refs automatically, eliminating `.value` calls for cleaner test assertions [source](./.skilld/releases/v3.20.0.md#enhancements)\n\n- Use `registerEndpoint` in setup files for persistent mock routes — v4.0.0 ensures endpoints persist across module resets and supports query parameters [source](./.skilld/releases/v4.0.0.md#registerendpoint-improvements)\n<!-- /skilld:best-practices -->\n"
  },
  {
    "path": ".claude/skills/skilld-lock.yaml",
    "content": "skills:\n  nuxt-test-utils-skilld:\n    packageName: '@nuxt/test-utils'\n    version: 4.0.0\n    repo: nuxt/test-utils\n    source: 'http://nuxt.com/llms.txt'\n    syncedAt: 2026-03-22\n    generator: skilld\n  vitest-skilld:\n    packageName: vitest\n    version: 4.1.0\n    repo: vitest-dev/vitest\n    source: 'https://github.com/vitest-dev/vitest/tree/v4.1.0/docs'\n    syncedAt: 2026-03-22\n    generator: skilld\n  nuxt-site-config-skilld:\n    packageName: nuxt-site-config\n    version: 3.2.21\n    repo: harlan-zw/nuxt-site-config\n    source: 'https://github.com/harlan-zw/nuxt-site-config/tree/v3.2.21/docs'\n    syncedAt: 2026-03-24\n    generator: skilld\n  devtools-layer-skilld:\n    packageName: nuxtseo-layer-devtools\n    version: 0.3.0\n    source: shipped\n    syncedAt: 2026-03-25\n    generator: skilld\n"
  },
  {
    "path": ".claude/skills/vitest-skilld/SKILL.md",
    "content": "---\nname: vitest-skilld\ndescription: \"ALWAYS use when writing code importing \\\"vitest\\\". Consult for debugging, best practices, or modifying vitest.\"\nmetadata:\n  version: 4.1.0\n  generated_by: cached\n  generated_at: 2026-03-22\n---\n\n# vitest-dev/vitest `vitest`\n\n**Version:** 4.1.0\n**Deps:** es-module-lexer@^2.0.0, expect-type@^1.3.0, magic-string@^0.30.21, obug@^2.1.1, pathe@^2.0.3, picomatch@^4.0.3, std-env@^4.0.0-rc.1, tinybench@^2.9.0, tinyexec@^1.0.2, tinyglobby@^0.2.15, tinyrainbow@^3.0.3, vite@^6.0.0 || ^7.0.0 || ^8.0.0-0, why-is-node-running@^2.3.0, @vitest/expect@4.1.0, @vitest/mocker@4.1.0, @vitest/runner@4.1.0, @vitest/snapshot@4.1.0, @vitest/pretty-format@4.1.0, @vitest/spy@4.1.0, @vitest/utils@4.1.0\n**Tags:** latest: 4.1.0, beta: 4.1.0-beta.6\n\n**References:** [package.json](./.skilld/pkg/package.json) — exports, entry points • [README](./.skilld/pkg/README.md) — setup, basic usage • [Docs](./.skilld/docs/_INDEX.md) — API reference, guides • [GitHub Issues](./.skilld/issues/_INDEX.md) — bugs, workarounds, edge cases • [GitHub Discussions](./.skilld/discussions/_INDEX.md) — Q&A, patterns, recipes • [Releases](./.skilld/releases/_INDEX.md) — changelog, breaking changes, new APIs\n\n## Search\n\nUse `skilld search` instead of grepping `.skilld/` directories — hybrid semantic + keyword search across all indexed docs, issues, and releases. If `skilld` is unavailable, use `npx -y skilld search`.\n\n```bash\nskilld search \"query\" -p vitest\nskilld search \"issues:error handling\" -p vitest\nskilld search \"releases:deprecated\" -p vitest\n```\n\nFilters: `docs:`, `issues:`, `releases:` prefix narrows by source type.\n\n<!-- skilld:api-changes -->\n## API Changes\n\nThis section documents version-specific API changes — prioritize recent major/minor releases.\n\n### Breaking Changes v4.0\n\n- BREAKING: `test()` and `describe()` third argument — options must be the second argument, not third [source](./.skilld/docs/guide/migration.md:L491:L502)\n\n- BREAKING: Pool configuration options restructured — `maxThreads`/`maxForks` → `maxWorkers`, `singleThread`/`singleFork` → `maxWorkers: 1, isolate: false`, `poolOptions` removed, `vmMemoryLimit` replaces nested config [source](./.skilld/docs/guide/migration.md:L328:L356)\n\n- BREAKING: `@vitest/browser/context` and `@vitest/browser/utils` moved — import from `vitest/browser` instead [source](./.skilld/docs/guide/migration.md:L298:L316)\n\n- BREAKING: Browser provider now accepts factory function instead of string — `provider: 'playwright'` → `provider: playwright({ launchOptions: {...} })` [source](./.skilld/docs/guide/migration.md:L266:L293)\n\n- BREAKING: `workspace` config option renamed to `projects` — move code from `vitest.workspace.js` to `vitest.config.ts` [source](./.skilld/docs/guide/migration.md:L230:L264)\n\n- BREAKING: Module environment now uses `viteEnvironment` property instead of `transformMode` [source](./.skilld/docs/guide/migration.md:L222)\n\n- BREAKING: `vi.fn().getMockName()` returns `'vi.fn()'` by default instead of `'spy'` — affects snapshots with mock names [source](./.skilld/releases/v4.0.0.md:L156)\n\n- BREAKING: `vi.restoreAllMocks` no longer resets automocks — only restores manual `vi.spyOn` spies [source](./.skilld/releases/v4.0.0.md:L157)\n\n- BREAKING: Coverage `coverage.all` and `coverage.extensions` removed — use `coverage.include` to specify source file pattern [source](./.skilld/docs/guide/migration.md:L34:L77)\n\n- BREAKING: Verbose reporter now prints as flat list — use `'tree'` reporter for previous hierarchical output [source](./.skilld/docs/guide/migration.md:L438:L447)\n\n- BREAKING: Removed deprecated config options — `poolMatchGlobs`, `environmentMatchGlobs`, `deps.external`, `deps.inline`, `deps.fallbackCJS` replaced with `projects` and `server.deps.*` [source](./.skilld/docs/guide/migration.md:L486:L488)\n\n- BREAKING: Snapshots with custom elements now include shadow root contents — set `printShadowRoot: false` to restore previous behavior [source](./.skilld/docs/guide/migration.md:L449:L480)\n\n### New Features v4.0\n\n- NEW: `vi.spyOn()` and `vi.fn()` support constructors — can now spy on and mock constructor functions with `new` keyword [source](./.skilld/releases/v4.0.0.md:L121)\n\n- NEW: `toMatchScreenshot()` for visual regression testing in browser mode [source](./.skilld/releases/v4.0.0.md:L69)\n\n- NEW: `toBeInViewport()` browser utility to assert element visibility [source](./.skilld/releases/v4.0.0.md:L67)\n\n- NEW: `onUnhandledError` callback hook for handling unhandled errors [source](./.skilld/releases/v4.0.0.md:L48)\n\n- NEW: `onConsoleLog` callback now receives `entity` parameter [source](./.skilld/releases/v4.0.0.md:L47)\n\n- NEW: `expect.assert()` for type narrowing in assertions [source](./.skilld/releases/v4.0.0.md:L55)\n\n- NEW: Custom screenshot comparison algorithms support in browser mode [source](./.skilld/releases/v4.0.0.md:L76)\n\n- NEW: Module Runner replaces vite-node — provides `moduleRunner` instance injected into test runners instead of `__vitest_executor` [source](./.skilld/docs/guide/migration.md:L215:L228)\n\n- NEW: API method `enableCoverage()` and `disableCoverage()` for dynamic coverage control [source](./.skilld/releases/v4.0.0.md:L62)\n\n- NEW: API method `getGlobalTestNamePattern()` to access current test name filter [source](./.skilld/releases/v4.0.0.md:L63)\n\n- NEW: API method `getSeed()` to retrieve random seed value [source](./.skilld/releases/v4.0.0.md:L65)\n\n- NEW: `experimental_parseSpecifications` API for parsing test specifications [source](./.skilld/releases/v4.0.0.md:L60)\n\n### Deprecation & Removal\n\n- DEPRECATED: Reporter APIs `onCollected`, `onSpecsCollected`, `onPathsCollected`, `onTaskUpdate`, `onFinished` — migrate to new reporter API [source](./.skilld/docs/guide/migration.md:L424)\n\n- DEPRECATED: `--browser.provider` CLI option removed [source](./.skilld/releases/v4.0.16.md:L16)\n\n- DEPRECATED: `test.poolOptions` config — use top-level options instead [source](./.skilld/releases/v4.0.16.md:L16)\n\n**Also changed:** `vi.mockObject()` adds `spy` option · `recordArtifact()` exported from vitest package · `toBeNullable()` matcher · Module graph UI fixes in HTML reporter · Playwright tracing support · Separate browser provider packages (`@vitest/browser-playwright`, etc.)\n<!-- /skilld:api-changes -->\n\n<!-- skilld:best-practices -->\n## Best Practices\n\n- Disable test isolation selectively with `isolate: false` for projects without side effects or that properly cleanup state — reduces test run time by eliminating per-file VM/worker overhead [source](./.skilld/docs/guide/improving-performance.md#test-isolation)\n\n- Use `context.expect` instead of global `expect` when running concurrent snapshot tests — ensures each test's snapshots are tracked independently and prevents conflicts [source](./.skilld/docs/guide/test-context.md#expect)\n\n- Define test tags in configuration to apply shared options (timeout, retry, priority) to grouped tests — enables filtering and automatic configuration without repeating test options [source](./.skilld/docs/guide/test-tags.md#defining-tags)\n\n- Return a cleanup function from `beforeEach` instead of using `afterEach` — simpler syntax and keeps setup/teardown logic in one place [source](./.skilld/docs/api/hooks.md#beforeeach)\n\n```ts\nbeforeEach(() => {\n  const resource = setupResource()\n  return () => resource.cleanup()\n})\n```\n\n- Use dynamic `import()` syntax with `vi.mock` for better TypeScript support and IDE integration — allows the compiler to validate the module path and type the `importOriginal` helper [source](./.skilld/docs/api/vi.md#vi-mock)\n\n- Use `vi.hoisted` to declare variables referenced in `vi.mock` factories — allows bypassing the hoisting limitation and referencing setup code [source](./.skilld/docs/api/vi.md#vi-mock)\n\n- Choose the `threads` pool over `forks` for larger projects to improve test run time — threads pool is faster for parallelization on multi-core machines [source](./.skilld/docs/guide/improving-performance.md#pool)\n\n- Await `importOriginal()` inside mock factories to properly handle async module loading — mock factory receives an async helper that must be awaited to access the real module [source](./.skilld/docs/guide/mocking/modules.md#mocking-a-module)\n\n- Apply retry conditions to tests with transient failures using regex or function-based matching — enables automatic retry only for specific error patterns without blanket retries [source](./.skilld/docs/config/retry.md#condition)\n<!-- /skilld:best-practices -->\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_size = 2\nindent_style = space\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [harlan-zw]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/01-feature-suggestion.yml",
    "content": "name: 🆕 Feature suggestion\ndescription: Suggest an idea!\ntitle: 'feat: '\nlabels: [enhancement]\nbody:\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: 🆒 Your use case\n      description: Add a description of your use case, and how this feature would help you.\n      placeholder: When I do [...] I would expect to be able to do [...]\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: 🆕 The solution you'd like\n      description: Describe what you want to happen.\n  - type: textarea\n    attributes:\n      label: 🔍 Alternatives you've considered\n      description: Have you considered any alternative solutions or features?\n  - type: textarea\n    attributes:\n      label: ℹ️ Additional info\n      description: Is there any other context you think would be helpful to know?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/02-bug-report.yml",
    "content": "name: 🐛 Bug report\ndescription: Something's not working\ntitle: 'fix: '\nlabels: [bug]\nbody:\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: 🐛 The bug\n      description: What isn't working? Describe what the bug is.\n  - type: input\n    validations:\n      required: true\n    attributes:\n      label: 🛠️ To reproduce\n      description: |\n        A reproduction of the bug. Please create a StackBlitz reproduction from one of the starters:\n        - [Basic](https://stackblitz.com/github/nuxt-modules/sitemap/tree/main/examples/basic)\n        - [i18n](https://stackblitz.com/github/nuxt-modules/sitemap/tree/main/examples/i18n)\n        - [Dynamic URLs](https://stackblitz.com/github/nuxt-modules/sitemap/tree/main/examples/dynamic-urls)\n      placeholder: https://stackblitz.com/[...]\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: 🌈 Expected behavior\n      description: What did you expect to happen? Is there a section in the docs about this?\n  - type: textarea\n    attributes:\n      label: ℹ️ Additional context\n      description: Add any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/03-documentation.yml",
    "content": "name: 📚 Documentation\ndescription: How do I ... ?\ntitle: 'docs: '\nlabels: [documentation]\nbody:\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: 📚 Is your documentation request related to a problem?\n      description: A clear and concise description of what the problem is.\n      placeholder: I feel I should be able to [...] but I can't see how to do it from the docs.\n  - type: textarea\n    attributes:\n      label: 🔍 Where should you find it?\n      description: What page of the docs do you expect this information to be found on?\n  - type: textarea\n    attributes:\n      label: ℹ️ Additional context\n      description: Add any other context or information.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/04-help-wanted.yml",
    "content": "name: 🆘 Help\ndescription: I need help with ...\ntitle: 'help: '\nlabels: [help wanted]\nbody:\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: 📚 What are you trying to do?\n      description: A clear and concise description of your objective.\n      placeholder: I'm not sure how to [...].\n  - type: textarea\n    attributes:\n      label: 🔍 What have you tried?\n      description: Have you looked through the docs? Tried different approaches? The more detail the better.\n  - type: textarea\n    attributes:\n      label: ℹ️ Additional context\n      description: Add any other context or information.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "contact_links:\n  - name: 📖 Documentation\n    url: https://nuxtseo.com/sitemap/getting-started/installation\n    about: Check the documentation for guides and examples.\n  - name: 💬 Harlan's Discord Server\n    url: https://discord.com/invite/5jDAMswWwX\n    about: Join the friendly discord server for help with your issue.\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!---\n☝️ PR title should follow conventional commits (https://conventionalcommits.org)\n-->\n\n### 🔗 Linked issue\n\n<!-- If it resolves an open issue, please link the issue here. For example \"Resolves #123\" -->\n\n### ❓ Type of change\n\n<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->\n\n- [ ] 📖 Documentation (updates to the documentation or readme)\n- [ ] 🐞 Bug fix (a non-breaking change that fixes an issue)\n- [ ] 👌 Enhancement (improving an existing functionality)\n- [ ] ✨ New feature (a non-breaking change that adds functionality)\n- [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries)\n- [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change)\n\n### 📚 Description\n\n<!-- Describe your changes in detail -->\n<!-- Why is this change required? What problem does it solve? -->\n"
  },
  {
    "path": ".github/renovate.json5",
    "content": "{\n  // https://github.com/nuxt/renovate-config-nuxt\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"github>nuxt/renovate-config-nuxt\"]\n}\n"
  },
  {
    "path": ".github/workflows/nightly.yml",
    "content": "name: Nightly\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n    tags:\n      - '!**'\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    uses: harlan-zw/nuxt-seo/.github/workflows/reusable-nightly.yml@main\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    permissions:\n      contents: write\n      id-token: write\n    uses: harlan-zw/nuxt-seo/.github/workflows/reusable-release.yml@main\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: CI\n\non:\n  push:\n    paths-ignore:\n      - '**/README.md'\n      - 'docs/**'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.number || github.sha }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\njobs:\n  ci:\n    uses: harlan-zw/nuxt-seo/.github/workflows/reusable-ci.yml@main\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\n.output\n.nuxt\n.temp\n.tmp\n.cache\n.idea\n.vscode\n*.swp\n.DS_Store\n*.log\ncoverage\n.env\n.env.*\n!.env.example\n\n# Nuxt\nplayground/.nuxt\nplayground/.output\ntest/fixtures/**/.nuxt\ntest/fixtures/**/.output\n.vercel_build_output\n.build-*\n.netlify\n.data\n\n# Skilld references (recreated by `skilld install`)\n.skilld\n"
  },
  {
    "path": ".npmrc",
    "content": "shamefully-hoist=true\n"
  },
  {
    "path": ".nuxtrc",
    "content": "imports.autoImport=false\ntypescript.includeWorkspace=true\nmodules.0=\"@nuxtjs/sitemap\"\nsetups.@nuxt/test-utils=\"4.0.0\""
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2024 Harlan Wilton\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1>@nuxtjs/sitemap</h1>\n\n[![npm version][npm-version-src]][npm-version-href]\n[![npm downloads][npm-downloads-src]][npm-downloads-href]\n[![License][license-src]][license-href]\n[![Nuxt][nuxt-src]][nuxt-href]\n\nNuxt Sitemap is a module for generating best-practice XML sitemaps that are consumed by the robots crawling your site.\n\nNew to XML sitemaps or SEO? Check out the [Controlling Web Crawlers](https://nuxtseo.com/learn/controlling-crawlers) guide to learn more about why you might\nneed these.\n\n<p align=\"center\">\n<table>\n<tbody>\n<td align=\"center\">\n<sub>Made possible by my <a href=\"https://github.com/sponsors/harlan-zw\">Sponsor Program 💖</a><br> Follow me <a href=\"https://twitter.com/harlan_zw\">@harlan_zw</a> 🐦 • Join <a href=\"https://discord.gg/275MBUBvgP\">Discord</a> for help</sub><br>\n</td>\n</tbody>\n</table>\n</p>\n\n## Features\n\n- 🌴 Single `/sitemap.xml` or multiple `/posts-sitemap.xml`, `/pages-sitemap.xml`\n- 📊 Fetch your sitemap URLs from anywhere\n- 😌 Automatic `lastmod`, image discovery and best practice sitemaps\n- 🔄 SWR caching, route rules support\n- 🎨 Debug using the Nuxt DevTools integration or the XML Stylesheet\n- 🤝 Integrates smoothly with [Nuxt I18n](https://github.com/nuxt-modules/i18n) and [Nuxt Content](https://github.com/nuxt/content)\n\n## Installation\n\n💡 Using Nuxt 2? Use the [nuxt-community/sitemap-module](https://github.com/nuxt-community/sitemap-module) docs.\n\nInstall `@nuxtjs/sitemap` dependency to your project:\n\n```bash\nnpx nuxi@latest module add sitemap\n```\n\n> [!TIP]\n> Generate an Agent Skill for this package using [skilld](https://github.com/harlan-zw/skilld):\n> ```bash\n> npx skilld add @nuxtjs/sitemap\n> ```\n\n💡 Need a complete SEO solution for Nuxt? Check out [Nuxt SEO](https://nuxtseo.com).\n\n## Documentation\n\n[📖 Read the full documentation](https://nuxtseo.com/sitemap) for more information.\n\n## Demos\n\n- [Dynamic URLs](https://stackblitz.com/edit/nuxt-starter-dyraxc?file=server%2Fapi%2F_sitemap-urls.ts)\n- [i18n](https://stackblitz.com/edit/nuxt-starter-jwuie4?file=app.vue)\n- [Manual Chunking](https://stackblitz.com/edit/nuxt-starter-umyso3?file=nuxt.config.ts)\n- [Nuxt Content Document Driven](https://stackblitz.com/edit/nuxt-starter-a5qk3s?file=nuxt.config.ts)\n\n## Sponsors\n\n<p align=\"center\">\n  <a href=\"https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg\">\n    <img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg' alt=\"Sponsors\"/>\n  </a>\n</p>\n\n## License\n\nLicensed under the [MIT license](https://github.com/nuxt-modules/sitemap/blob/main/LICENSE.md).\n\n<!-- Badges -->\n[npm-version-src]: https://img.shields.io/npm/v/@nuxtjs/sitemap/latest.svg?style=flat&colorA=18181B&colorB=28CF8D\n[npm-version-href]: https://npmjs.com/package/@nuxtjs/sitemap\n\n[npm-downloads-src]: https://img.shields.io/npm/dm/@nuxtjs/sitemap.svg?style=flat&colorA=18181B&colorB=28CF8D\n[npm-downloads-href]: https://npmjs.com/package/@nuxtjs/sitemap\n\n[license-src]: https://img.shields.io/github/license/nuxt-modules/sitemap.svg?style=flat&colorA=18181B&colorB=28CF8D\n[license-href]: https://github.com/nuxt-modules/sitemap/blob/main/LICENSE.md\n\n[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt\n[nuxt-href]: https://nuxt.com\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nI take the security of my Nuxt modules seriously. If you believe you've found a security vulnerability, please follow these steps:\n\n### Option 1: GitHub Security Advisory\n\n1. Go to the GitHub repository of the affected module\n2. Navigate to \"Security\" tab\n3. Select \"Report a vulnerability\"\n4. Provide a detailed description of the vulnerability\n\n### Option 2: Email\n\nAlternatively, you can email security concerns directly to:\n- harlan@harlanzw.com\n\n## What to Include in Your Report\n\nPlease include:\n\n- Description of the vulnerability\n- Steps to reproduce\n- Potential impact\n- Any possible mitigations you've identified\n\n## Response Process\n\nWhen a vulnerability is reported:\n\n1. I will acknowledge receipt within 48 hours\n2. I will validate and investigate the report\n3. I will work on a fix and coordinate the release process\n4. After the fix is released, I will acknowledge your contribution (if desired)\n\n## Scope\n\nThis security policy applies to all my Nuxt modules as published on npm.\n\nThank you for helping keep the Nuxt ecosystem secure!\n"
  },
  {
    "path": "benchmark/app/app.vue",
    "content": "<template>\n  <div>bench</div>\n</template>\n"
  },
  {
    "path": "benchmark/bench.mjs",
    "content": "// Minimal throughput benchmark for @nuxtjs/sitemap\n// Usage:\n//   node benchmark/bench.mjs              # all variants\n//   BENCH_TARGET=/api/ping node benchmark/bench.mjs\n//\n// Each run gets its own .output dir so builds cannot leak between runs.\n// After each build we assert presence/absence of sitemap module artefacts.\n\nimport { spawn } from 'node:child_process'\nimport { once } from 'node:events'\nimport { existsSync, readdirSync, readFileSync, rmSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { setTimeout as sleep } from 'node:timers/promises'\nimport { fileURLToPath } from 'node:url'\nimport autocannon from 'autocannon'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst cwd = __dirname\n\nconst TARGET = process.env.BENCH_TARGET || '/api/ping'\nconst PORT = Number(process.env.BENCH_PORT || 3777)\nconst DURATION = Number(process.env.BENCH_DURATION || 10)\nconst CONNECTIONS = Number(process.env.BENCH_CONNECTIONS || 100)\n\nconst SITEMAP_ARTEFACTS = [\n  'chunks/routes/sitemap.xml.mjs',\n  'chunks/virtual/global-sources.mjs',\n  'chunks/virtual/child-sources.mjs',\n]\n// strings that must NOT appear in baseline server bundle and SHOULD appear with sitemap on\nconst SITEMAP_MARKERS = ['@nuxtjs/sitemap', 'useSitemapRuntimeConfig', '#sitemap-virtual']\n\nfunction isolate(label) {\n  const slug = label.replace(/[^a-z0-9]+/gi, '-').toLowerCase()\n  return {\n    nuxtDir: resolve(cwd, `.nuxt-${slug}`),\n    outDir: resolve(cwd, `.output-${slug}`),\n  }\n}\n\nfunction assertSitemapPresence({ outDir, expectSitemap, label }) {\n  const indexPath = resolve(outDir, 'server/index.mjs')\n  if (!existsSync(indexPath))\n    throw new Error(`[${label}] missing build: ${indexPath}`)\n\n  const presentArtefacts = SITEMAP_ARTEFACTS.filter(p => existsSync(resolve(outDir, 'server', p)))\n\n  const grepOut = []\n  const walker = (dir) => {\n    for (const entry of readdirSync(dir, { withFileTypes: true })) {\n      const full = resolve(dir, entry.name)\n      if (entry.isDirectory()) {\n        walker(full)\n      }\n      else if (entry.name.endsWith('.mjs')) {\n        const txt = readFileSync(full, 'utf8')\n        for (const m of SITEMAP_MARKERS) {\n          if (txt.includes(m))\n            grepOut.push(`${full.slice(outDir.length + 1)}: ${m}`)\n        }\n      }\n    }\n  }\n  walker(resolve(outDir, 'server'))\n\n  console.log(`[${label}] sitemap artefacts present: ${presentArtefacts.length} -> ${JSON.stringify(presentArtefacts)}`)\n  console.log(`[${label}] sitemap marker hits in bundle: ${grepOut.length}`)\n  if (grepOut.length)\n    console.log(grepOut.slice(0, 5).map(l => `  - ${l}`).join('\\n'))\n\n  if (expectSitemap) {\n    if (grepOut.length === 0)\n      throw new Error(`[${label}] expected sitemap markers but found none`)\n  }\n  else {\n    if (presentArtefacts.length > 0)\n      throw new Error(`[${label}] BASELINE LEAK: sitemap artefacts present: ${JSON.stringify(presentArtefacts)}`)\n    if (grepOut.length > 0)\n      throw new Error(`[${label}] BASELINE LEAK: sitemap markers found in baseline bundle:\\n${grepOut.slice(0, 10).join('\\n')}`)\n  }\n}\n\nasync function run(label, env, expectSitemap) {\n  const { nuxtDir, outDir } = isolate(label)\n  console.log(`\\n=== ${label} ===`)\n  console.log(`env: ${JSON.stringify(env)}`)\n  console.log(`nuxtDir: ${nuxtDir}`)\n  console.log(`outDir:  ${outDir}`)\n\n  // wipe per-run dirs\n  for (const d of [nuxtDir, outDir]) rmSync(d, { recursive: true, force: true })\n\n  console.log('building...')\n  const slug = label.replace(/[^a-z0-9]+/gi, '-').toLowerCase()\n  const build = spawn(\n    'npx',\n    ['nuxt', 'build'],\n    {\n      cwd,\n      env: {\n        ...process.env,\n        ...env,\n        BENCH_SLUG: slug,\n        NUXT_TELEMETRY_DISABLED: '1',\n      },\n      stdio: 'inherit',\n    },\n  )\n  const [code] = await once(build, 'exit')\n  if (code !== 0)\n    throw new Error(`build failed (${code})`)\n\n  await assertSitemapPresence({ outDir, expectSitemap, label })\n\n  const server = spawn('node', [resolve(outDir, 'server/index.mjs')], {\n    cwd,\n    env: { ...process.env, PORT: String(PORT), HOST: '127.0.0.1' },\n    stdio: ['ignore', 'pipe', 'pipe'],\n  })\n  let ready = false\n  server.stdout.on('data', (b) => {\n    const s = String(b)\n    process.stdout.write(`[server] ${s}`)\n    if (/Listening/.test(s))\n      ready = true\n  })\n  server.stderr.on('data', b => process.stderr.write(`[server] ${b}`))\n\n  for (let i = 0; i < 200 && !ready; i++) await sleep(100)\n  if (!ready) {\n    server.kill('SIGKILL')\n    throw new Error('server failed to start')\n  }\n  await sleep(200)\n\n  console.log(`benchmarking http://127.0.0.1:${PORT}${TARGET} for ${DURATION}s, ${CONNECTIONS} conns`)\n  const result = await autocannon({\n    url: `http://127.0.0.1:${PORT}${TARGET}`,\n    connections: CONNECTIONS,\n    duration: DURATION,\n  })\n\n  server.kill('SIGTERM')\n  await once(server, 'exit').catch(() => {})\n\n  return {\n    label,\n    rps: result.requests.average,\n    rpsMin: result.requests.min,\n    rpsMax: result.requests.max,\n    latencyAvg: result.latency.average,\n    latencyP99: result.latency.p99,\n    errors: result.errors,\n    non2xx: result.non2xx,\n  }\n}\n\nconst runs = []\nruns.push(await run('baseline-no-sitemap', { BENCH_SITEMAP: '0' }, false))\nruns.push(await run('sitemap-default', { BENCH_SITEMAP: '1', BENCH_WARMUP: '1' }, true))\nruns.push(await run('sitemap-no-warmup', { BENCH_SITEMAP: '1', BENCH_WARMUP: '0' }, true))\nruns.push(await run('sitemap-no-xsl', { BENCH_SITEMAP: '1', BENCH_WARMUP: '0', BENCH_XSL: '0' }, true))\nruns.push(await run('sitemap-zero-runtime', { BENCH_SITEMAP: '1', BENCH_WARMUP: '0', BENCH_ZERO: '1' }, true))\nruns.push(await run('sitemap-rc-stub', { BENCH_SITEMAP: '1', BENCH_WARMUP: '0', BENCH_RC_STUB: '1' }, true))\n\nconsole.log('\\n=== summary ===')\nconsole.table(runs.map(r => ({\n  'label': r.label,\n  'req/s avg': r.rps.toFixed(0),\n  'req/s min': r.rpsMin.toFixed(0),\n  'req/s max': r.rpsMax.toFixed(0),\n  'lat avg ms': r.latencyAvg.toFixed(2),\n  'lat p99 ms': r.latencyP99.toFixed(2),\n  'errors': r.errors,\n  'non2xx': r.non2xx,\n})))\n"
  },
  {
    "path": "benchmark/nuxt.config.ts",
    "content": "const enableSitemap = process.env.BENCH_SITEMAP === '1'\nconst enableWarmUp = process.env.BENCH_WARMUP !== '0'\nconst enableXsl = process.env.BENCH_XSL !== '0'\nconst zeroRuntime = process.env.BENCH_ZERO === '1'\nconst slug = process.env.BENCH_SLUG || 'default'\n\nconsole.log(`[bench/nuxt.config] sitemap=${enableSitemap} warm=${enableWarmUp} xsl=${enableXsl} zero=${zeroRuntime} slug=${slug}`)\n\nexport default defineNuxtConfig({\n  modules: [\n    ...(enableSitemap ? ['../src/module'] : []),\n    (_options: any, nuxt: any) => {\n      nuxt.hook('modules:done', () => {\n        const names = nuxt.options._installedModules.map((m: any) => m?.meta?.name || m?.entryPath || '?')\n        console.log(`[bench] installed modules (${names.length}): ${JSON.stringify(names)}`)\n      })\n    },\n  ] as any,\n\n  site: {\n    url: 'https://example.com',\n  },\n\n  sitemap: {\n    enabled: enableSitemap,\n    excludeAppSources: true,\n    debug: false,\n    sitemapsPathPrefix: '/',\n    discoverImages: false,\n    discoverVideos: false,\n    experimentalWarmUp: enableWarmUp,\n    xsl: enableXsl ? '/__sitemap__/style.xsl' : false,\n    zeroRuntime,\n    autoI18n: false,\n    cacheMaxAgeSeconds: 36000,\n  },\n\n  compatibilityDate: '2025-01-01',\n\n  buildDir: `.nuxt-${slug}`,\n\n  nitro: {\n    preset: 'node-server',\n    output: {\n      dir: `.output-${slug}`,\n    },\n  },\n})\n"
  },
  {
    "path": "benchmark/package.json",
    "content": "{\n  \"name\": \"sitemap-benchmark\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"bench\": \"node bench.mjs\"\n  },\n  \"dependencies\": {\n    \"@nuxtjs/sitemap\": \"workspace:*\",\n    \"autocannon\": \"catalog:\",\n    \"nuxt\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "benchmark/server/api/ping.get.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => ({ ok: true }))\n"
  },
  {
    "path": "build.config.ts",
    "content": "import { defineBuildConfig } from 'unbuild'\n\nexport default defineBuildConfig({\n  declaration: true,\n  entries: [\n    { input: 'src/content', name: 'content' },\n    { input: 'src/utils', name: 'utils' },\n  ],\n  externals: [\n    // Nuxt core\n    'nuxt',\n    'nuxt/schema',\n    '@nuxt/kit',\n    '@nuxt/schema',\n    'nitropack',\n    'nitropack/types',\n    'h3',\n    // Vue\n    'vue',\n    'vue-router',\n    '@vue/runtime-core',\n    // Common deps\n    '#imports',\n    // Content subpath export\n    '@nuxt/content',\n    'zod',\n  ],\n})\n"
  },
  {
    "path": "devtools/app.config.ts",
    "content": "export default {\n  ui: {\n    colors: {\n      primary: 'green',\n      neutral: 'neutral',\n    },\n    button: {\n      defaultVariants: {\n        color: 'neutral',\n        variant: 'ghost',\n        size: 'sm',\n      },\n    },\n    badge: {\n      defaultVariants: {\n        color: 'neutral',\n        variant: 'subtle',\n        size: 'xs',\n      },\n    },\n    tooltip: {\n      defaultVariants: {\n        delayDuration: 0,\n      },\n    },\n  },\n}\n"
  },
  {
    "path": "devtools/app.vue",
    "content": "<script setup lang=\"ts\">\nimport { loadShiki } from 'nuxtseo-layer-devtools/composables/shiki'\nimport { isProductionMode } from 'nuxtseo-layer-devtools/composables/state'\nimport { computed, ref, watch } from 'vue'\nimport { navigateTo, useRoute } from '#imports'\nimport { data, productionData, productionRemoteDebugData, refreshProductionData, refreshSources } from './composables/state'\nimport './composables/rpc'\n\nawait loadShiki()\n\nconst refreshing = ref(false)\n\nasync function refresh() {\n  if (refreshing.value)\n    return\n  refreshing.value = true\n  data.value = null\n  productionData.value = null\n  productionRemoteDebugData.value = null\n  await refreshSources()\n  if (isProductionMode.value)\n    await refreshProductionData()\n  setTimeout(() => {\n    refreshing.value = false\n  }, 300)\n}\n\nconst route = useRoute()\nconst currentTab = computed(() => {\n  const path = route.path\n  if (path.startsWith('/user-sources'))\n    return 'user-sources'\n  if (path.startsWith('/app-sources'))\n    return 'app-sources'\n  if (path.startsWith('/debug'))\n    return 'debug'\n  if (path.startsWith('/docs'))\n    return 'docs'\n  return 'sitemaps'\n})\n\nconst navItems = [\n  { value: 'sitemaps', to: '/', icon: 'carbon:load-balancer-application', label: 'Sitemaps', devOnly: false },\n  { value: 'user-sources', to: '/user-sources', icon: 'carbon:group-account', label: 'User Sources', devOnly: true },\n  { value: 'app-sources', to: '/app-sources', icon: 'carbon:bot', label: 'App Sources', devOnly: true },\n  { value: 'debug', to: '/debug', icon: 'carbon:debug', label: 'Debug', devOnly: true },\n  { value: 'docs', to: '/docs', icon: 'carbon:book', label: 'Docs', devOnly: false },\n]\n\nconst runtimeVersion = computed(() => data.value?.runtimeConfig?.version)\n\n// Redirect to home when switching to production mode from a dev-only tab\nwatch(isProductionMode, (isProd) => {\n  if (isProd && ['user-sources', 'app-sources', 'debug'].includes(currentTab.value))\n    return navigateTo('/')\n})\n</script>\n\n<template>\n  <DevtoolsLayout\n    v-model:active-tab=\"currentTab\"\n    module-name=\"sitemap\"\n    title=\"Sitemap\"\n    icon=\"carbon:load-balancer-application\"\n    :version=\"runtimeVersion\"\n    :nav-items=\"navItems\"\n    github-url=\"https://github.com/nuxt-modules/sitemap\"\n    :loading=\"!data?.globalSources || refreshing\"\n    @refresh=\"refresh\"\n  >\n    <NuxtPage />\n  </DevtoolsLayout>\n</template>\n\n<style>\n/* Textarea */\ntextarea {\n  background: var(--color-surface-sunken);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius-md);\n}\n\ntextarea:focus-visible {\n  border-color: var(--seo-green);\n  outline: none;\n}\n\n/* JSON Editor theme */\n:root {\n  --jse-theme-color: var(--color-surface-elevated) !important;\n  --jse-text-color-inverse: var(--color-text-muted) !important;\n  --jse-theme-color-highlight: var(--color-surface-sunken) !important;\n  --jse-panel-background: var(--color-surface-elevated) !important;\n  --jse-background-color: var(--jse-panel-background) !important;\n  --jse-error-color: oklch(65% 0.2 25 / 0.3) !important;\n  --jse-main-border: none !important;\n}\n\n.dark,\n.jse-theme-dark {\n  --jse-panel-background: var(--color-neutral-900) !important;\n  --jse-theme-color: var(--color-neutral-900) !important;\n  --jse-text-color-inverse: var(--color-neutral-300) !important;\n  --jse-main-border: none !important;\n}\n\n.jse-main {\n  min-height: 1em !important;\n}\n\n.jse-contents {\n  border-width: 0 !important;\n  border-radius: var(--radius-md) !important;\n}\n</style>\n"
  },
  {
    "path": "devtools/components/Source.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SitemapSourceResolved } from '../../src/runtime/types'\nimport { joinURL } from 'ufo'\nimport { computed } from 'vue'\nimport { data } from '../composables/state'\n\nconst props = defineProps<{ source: SitemapSourceResolved, showContext?: boolean }>()\n\nconst fetchUrl = computed(() => {\n  const url = typeof props.source.fetch === 'string' ? props.source.fetch : props.source.fetch![0]\n  if (url.includes('http'))\n    return url\n  return joinURL(data.value?.nitroOrigin || 'localhost', url)\n})\n\nfunction normaliseTip(tip: string) {\n  return tip.replace(/`([^`]+)`/g, '<code>$1</code>')\n}\n</script>\n\n<template>\n  <DevtoolsSection :class=\"source.error ? 'source-error' : ''\">\n    <template #text>\n      <div class=\"flex items-center gap-3\">\n        <div\n          v-if=\"source.fetch\"\n          class=\"flex items-center gap-1.5\"\n        >\n          <UIcon\n            name=\"carbon:api-1\"\n            class=\"text-[var(--color-text-muted)]\"\n          />\n          <DevtoolsMetric\n            v-if=\"source.timeTakenMs\"\n            :value=\"source.timeTakenMs\"\n            label=\"ms\"\n            variant=\"info\"\n          />\n        </div>\n        <span class=\"font-semibold\">{{ source.context.name }}</span>\n        <DevtoolsMetric\n          :value=\"source.urls?.length || 0\"\n          label=\"URLs\"\n          :variant=\"source.error ? 'danger' : !source.urls?.length ? 'warning' : 'success'\"\n        />\n      </div>\n    </template>\n    <template #description>\n      <div class=\"flex items-center gap-3\">\n        <a\n          v-if=\"source.fetch\"\n          :href=\"fetchUrl\"\n          target=\"_blank\"\n          class=\"link-external text-sm\"\n        >\n          {{ source.fetch }}\n        </a>\n        <span\n          v-if=\"source.context.description\"\n          class=\"text-xs text-[var(--color-text-muted)]\"\n        >\n          {{ source.context.description }}\n        </span>\n      </div>\n    </template>\n    <DevtoolsAlert\n      v-if=\"source.error\"\n      variant=\"error\"\n    >\n      {{ source.error }}\n    </DevtoolsAlert>\n    <template v-else>\n      <DevtoolsAlert\n        v-if=\"source._urlWarnings?.length\"\n        variant=\"warning\"\n      >\n        <div>\n          <div class=\"text-xs font-semibold mb-1\">\n            {{ source._urlWarnings.length }} URL warning{{ source._urlWarnings.length > 1 ? 's' : '' }}\n          </div>\n          <ul class=\"url-warnings-list\">\n            <li\n              v-for=\"(w, i) in source._urlWarnings\"\n              :key=\"i\"\n            >\n              <code>{{ w.loc }}</code> — {{ w.message }}\n            </li>\n          </ul>\n        </div>\n      </DevtoolsAlert>\n      <DevtoolsSnippet\n        :code=\"JSON.stringify(source.urls, null, 2)\"\n        lang=\"json\"\n        label=\"URLs\"\n      />\n    </template>\n    <DevtoolsAlert\n      v-if=\"source.context.tips?.length\"\n      :variant=\"!source.urls?.length && !source.error ? 'warning' : 'info'\"\n    >\n      <div>\n        <h3 class=\"text-xs font-semibold mb-1.5 text-[var(--color-text)] uppercase tracking-wide opacity-70\">\n          Hints\n        </h3>\n        <ul class=\"space-y-1\">\n          <li\n            v-for=\"(tip, key) in source.context.tips\"\n            :key=\"key\"\n            class=\"text-sm text-[var(--color-text-muted)] leading-relaxed\"\n            v-html=\"normaliseTip(tip)\"\n          />\n        </ul>\n      </div>\n    </DevtoolsAlert>\n  </DevtoolsSection>\n</template>\n\n<style scoped>\n.source-error {\n  border-color: oklch(55% 0.15 25 / 0.35);\n}\n\n.source-error:hover {\n  border-color: oklch(55% 0.15 25 / 0.5);\n}\n\n.url-warnings-list {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n  font-size: 0.6875rem;\n  line-height: 1.5;\n  color: var(--color-text-muted);\n}\n\n.url-warnings-list li {\n  padding: 0.125rem 0;\n}\n\n.url-warnings-list code {\n  font-family: var(--font-mono);\n  font-size: 0.625rem;\n  padding: 0.0625rem 0.3125rem;\n  border-radius: 3px;\n  background: var(--color-surface-sunken);\n  color: var(--color-text);\n}\n</style>\n"
  },
  {
    "path": "devtools/composables/rpc.ts",
    "content": "import { useDevtoolsConnection } from 'nuxtseo-layer-devtools/composables/rpc'\nimport { refreshSources } from './state'\n\nuseDevtoolsConnection({\n  onConnected: () => refreshSources(),\n})\n"
  },
  {
    "path": "devtools/composables/state.ts",
    "content": "import type { ProductionDebugResponse } from '../../src/runtime/server/routes/__sitemap__/debug-production'\nimport type { ModuleRuntimeConfig, SitemapDefinition, SitemapSourceResolved } from '../../src/runtime/types'\nimport { appFetch } from 'nuxtseo-layer-devtools/composables/rpc'\nimport { isProductionMode, productionUrl } from 'nuxtseo-layer-devtools/composables/state'\nimport { ref, watch } from 'vue'\n\nexport const data = ref<{\n  nitroOrigin: string\n  globalSources: SitemapSourceResolved[]\n  sitemaps: SitemapDefinition[]\n  runtimeConfig: ModuleRuntimeConfig\n  siteConfig?: { url?: string }\n} | null>(null)\n\n// Production debug data from the remote /__sitemap__/debug.json (requires debug: true in production)\nexport const productionRemoteDebugData = ref<typeof data.value | null>(null)\n\nexport const productionData = ref<ProductionDebugResponse | null>(null)\nexport const productionLoading = ref(false)\n\nexport async function refreshSources() {\n  if (appFetch.value)\n    data.value = await appFetch.value('/__sitemap__/debug.json') as typeof data.value\n}\n\nexport async function refreshProductionData() {\n  if (!appFetch.value || !productionUrl.value)\n    return\n  productionLoading.value = true\n  productionRemoteDebugData.value = null\n\n  // Try fetching the full debug endpoint from production first (proxied through local server)\n  const remoteDebug = await appFetch.value('/__sitemap__/debug-production.json', {\n    query: { url: productionUrl.value, mode: 'debug' },\n  }).catch(() => null) as (typeof data.value & { error?: string }) | null\n  if (remoteDebug && !remoteDebug.error && remoteDebug.sitemaps && !Array.isArray(remoteDebug.sitemaps)) {\n    // Response has object sitemaps (debug.json format) rather than array (XML fallback format)\n    productionRemoteDebugData.value = remoteDebug\n    productionLoading.value = false\n    return\n  }\n\n  // Fall back to XML-based validation\n  productionData.value = await appFetch.value('/__sitemap__/debug-production.json', {\n    query: { url: productionUrl.value },\n  }).catch((err: Error) => {\n    console.error('Failed to fetch production sitemap data:', err)\n    return null\n  }) as ProductionDebugResponse | null\n  productionLoading.value = false\n}\n\n// Sync production URL from siteConfig when debug data loads\nwatch(data, (val) => {\n  if (val?.siteConfig?.url)\n    productionUrl.value = val.siteConfig.url\n}, { immediate: true })\n\n// Fetch production data when switching to production mode\nwatch(isProductionMode, (isProd) => {\n  if (isProd && !productionData.value && !productionRemoteDebugData.value)\n    refreshProductionData()\n})\n"
  },
  {
    "path": "devtools/nuxt.config.ts",
    "content": "import { resolve } from 'pathe'\n\nexport default defineNuxtConfig({\n  extends: ['nuxtseo-layer-devtools'],\n\n  sitemap: false,\n\n  imports: {\n    autoImport: true,\n  },\n\n  nitro: {\n    prerender: {\n      routes: ['/', '/user-sources', '/app-sources', '/debug', '/docs'],\n    },\n    output: {\n      publicDir: resolve(__dirname, '../dist/devtools'),\n    },\n  },\n\n  app: {\n    baseURL: '/__nuxt-sitemap',\n  },\n})\n"
  },
  {
    "path": "devtools/package.json",
    "content": "{\n  \"name\": \"@nuxtjs/sitemap-client\",\n  \"private\": true,\n  \"devDependencies\": {\n    \"@iconify-json/carbon\": \"catalog:\",\n    \"@iconify-json/simple-icons\": \"catalog:\",\n    \"@nuxt/devtools-kit\": \"catalog:\",\n    \"@nuxt/kit\": \"catalog:\",\n    \"@vueuse/core\": \"catalog:\",\n    \"nuxt\": \"catalog:\",\n    \"nuxtseo-layer-devtools\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "devtools/pages/app-sources.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport Source from '../components/Source.vue'\nimport { data } from '../composables/state'\n\nconst appSources = computed(() => (data.value?.globalSources || []).filter(s => s.sourceType === 'app'))\n</script>\n\n<template>\n  <div class=\"space-y-5 animate-fade-up\">\n    <div>\n      <h2 class=\"text-lg font-semibold mb-1\">\n        App Sources\n      </h2>\n      <p class=\"text-xs text-[var(--color-text-muted)]\">\n        Automatic global sources generated from your application.\n      </p>\n    </div>\n    <template v-if=\"appSources.length\">\n      <Source\n        v-for=\"(source, key) in appSources\"\n        :key=\"key\"\n        :source=\"source\"\n      />\n    </template>\n    <DevtoolsEmptyState\n      v-else\n      title=\"No app sources detected\"\n      description=\"App sources are automatically discovered from your Nuxt application routes and pages.\"\n      icon=\"carbon:bot\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "devtools/pages/debug.vue",
    "content": "<script setup lang=\"ts\">\nimport { data } from '../composables/state'\n</script>\n\n<template>\n  <div class=\"space-y-5 animate-fade-up\">\n    <DevtoolsSection>\n      <template #text>\n        <h3 class=\"opacity-80 text-base mb-1\">\n          <UIcon name=\"carbon:settings\" class=\"mr-1\" />\n          Runtime Config\n        </h3>\n      </template>\n      <DevtoolsSnippet\n        :code=\"JSON.stringify(data?.runtimeConfig, null, 2)\"\n        lang=\"json\"\n        label=\"Runtime Config\"\n      />\n    </DevtoolsSection>\n  </div>\n</template>\n"
  },
  {
    "path": "devtools/pages/docs.vue",
    "content": "<template>\n  <DevtoolsDocs url=\"https://nuxtseo.com/sitemap\" />\n</template>\n"
  },
  {
    "path": "devtools/pages/index.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SitemapDefinition, SitemapSourceResolved } from '../../src/runtime/types'\nimport { isProductionMode, productionUrl } from 'nuxtseo-layer-devtools/composables/state'\nimport { joinURL } from 'ufo'\nimport { computed } from 'vue'\nimport Source from '../components/Source.vue'\nimport { data, productionData, productionLoading, productionRemoteDebugData, refreshProductionData } from '../composables/state'\n\nconst appSourcesExcluded = computed(() => data.value?.runtimeConfig?.excludeAppSources || [])\n\nfunction resolveSitemapOrigin() {\n  if (isProductionMode.value && productionUrl.value)\n    return `${productionUrl.value.replace(/\\/$/, '')}/`\n  return data.value?.nitroOrigin || ''\n}\n\nfunction resolveSitemapPath(sitemapName: string) {\n  const source = productionRemoteDebugData.value || data.value\n  if (!source)\n    return ''\n  const prefix = source.runtimeConfig?.sitemapsPathPrefix || ''\n  if (sitemapName === 'sitemap' || sitemapName === 'sitemap.xml')\n    return '/sitemap.xml'\n  if (sitemapName === 'index')\n    return '/sitemap_index.xml'\n  return joinURL('/', prefix, `${sitemapName}-sitemap.xml`)\n}\n\nfunction resolveSitemapUrl(sitemapName: string) {\n  return `${resolveSitemapOrigin()}${resolveSitemapPath(sitemapName).replace(/^\\//, '')}`\n}\n\nfunction resolveSitemapOptions(definition: SitemapDefinition) {\n  const options: Record<string, any> = {}\n  Object.entries(definition).forEach(([key, value]) => {\n    if (value !== undefined && (!Array.isArray(value) || value.length > 0) && key !== 'includeAppSources')\n      options[key] = value\n  })\n  return options\n}\n\nfunction sitemapOptionsAsKeyValues(definition: SitemapDefinition) {\n  const options = resolveSitemapOptions(definition)\n  return Object.entries(options).map(([key, value]) => {\n    const isObject = typeof value === 'object'\n    return {\n      key,\n      value: isObject ? JSON.stringify(value, null, 2) : value,\n      mono: true,\n      code: isObject ? 'json' as const : undefined,\n    }\n  })\n}\n\nfunction sitemapPathFromUrl(url: string) {\n  try {\n    return new URL(url).pathname\n  }\n  catch {\n    return url\n  }\n}\n\nconst hasRemoteDebug = computed(() => !!productionRemoteDebugData.value)\n\nconst totalProductionUrls = computed(() =>\n  productionData.value?.sitemaps.reduce((sum, s) => sum + s.urlCount, 0) ?? 0,\n)\n\nconst totalProductionWarnings = computed(() =>\n  productionData.value?.sitemaps.reduce((sum, s) => sum + s.warnings.length, 0) ?? 0,\n)\n</script>\n\n<template>\n  <div class=\"space-y-5 animate-fade-up\">\n    <!-- Production mode -->\n    <template v-if=\"isProductionMode\">\n      <div class=\"flex items-center justify-between\">\n        <div>\n          <h2 class=\"text-lg font-semibold mb-1\">\n            Production Sitemaps\n          </h2>\n          <p class=\"text-xs text-[var(--color-text-muted)]\">\n            Fetched from {{ productionUrl }}<template v-if=\"hasRemoteDebug\">\n              with debug mode enabled\n            </template>.\n          </p>\n        </div>\n        <UButton\n          icon=\"carbon:reset\"\n          size=\"xs\"\n          variant=\"ghost\"\n          :loading=\"productionLoading\"\n          @click=\"refreshProductionData()\"\n        >\n          Re-validate\n        </UButton>\n      </div>\n\n      <DevtoolsLoading v-if=\"productionLoading && !productionData && !productionRemoteDebugData\" />\n\n      <!-- Full debug view (production has debug: true) -->\n      <template v-if=\"hasRemoteDebug\">\n        <DevtoolsSection\n          v-for=\"(sitemap, key) in productionRemoteDebugData!.sitemaps\"\n          :key=\"key\"\n        >\n          <template #text>\n            <div class=\"flex items-center gap-2\">\n              <span class=\"font-semibold\">{{ sitemap.sitemapName }}</span>\n              <a\n                target=\"_blank\"\n                :href=\"resolveSitemapUrl(sitemap.sitemapName)\"\n                class=\"link-external text-xs font-mono text-[var(--color-text-muted)]\"\n              >\n                {{ resolveSitemapPath(sitemap.sitemapName) }}\n              </a>\n              <UIcon\n                v-if=\"(sitemap.sources || []).some(s => typeof s !== 'string' && 'error' in s && !!s.error)\"\n                name=\"carbon:warning\"\n                class=\"text-red-500\"\n              />\n              <UIcon\n                v-else-if=\"(sitemap.sources || []).some(s => typeof s !== 'string' && '_urlWarnings' in s && s._urlWarnings?.length)\"\n                name=\"carbon:warning-alt\"\n                class=\"text-amber-500\"\n              />\n            </div>\n          </template>\n          <div class=\"space-y-5\">\n            <template v-if=\"sitemap.sitemapName === 'index'\">\n              <DevtoolsAlert variant=\"info\">\n                Links to your other sitemaps.\n                <a\n                  href=\"https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps\"\n                  target=\"_blank\"\n                  class=\"link-external\"\n                >\n                  Learn more\n                </a>\n              </DevtoolsAlert>\n            </template>\n            <template v-else>\n              <div\n                v-if=\"sitemap.sources && sitemap.sources.length\"\n                class=\"flex gap-4\"\n              >\n                <div class=\"w-32 flex-shrink-0\">\n                  <div class=\"font-semibold text-sm\">\n                    Sources\n                  </div>\n                </div>\n                <div class=\"flex-grow space-y-2\">\n                  <Source\n                    v-for=\"(source, k) in (sitemap.sources as SitemapSourceResolved[])\"\n                    :key=\"k\"\n                    :source=\"source\"\n                  />\n                </div>\n              </div>\n              <div class=\"flex gap-4\">\n                <div class=\"w-32 flex-shrink-0\">\n                  <div class=\"font-semibold text-sm\">\n                    Options\n                  </div>\n                </div>\n                <div class=\"flex-grow\">\n                  <DevtoolsKeyValue\n                    :items=\"sitemapOptionsAsKeyValues(sitemap)\"\n                    striped\n                  />\n                </div>\n              </div>\n            </template>\n          </div>\n        </DevtoolsSection>\n      </template>\n\n      <!-- XML-based fallback view -->\n      <template v-else-if=\"productionData?.error\">\n        <DevtoolsProductionError :error=\"productionData.error\" />\n      </template>\n\n      <template v-else-if=\"productionData\">\n        <!-- Summary -->\n        <div class=\"flex items-center gap-4\">\n          <DevtoolsMetric\n            :value=\"productionData.sitemaps.length\"\n            :label=\"productionData.isIndex ? 'child sitemaps' : 'sitemap'\"\n            variant=\"info\"\n          />\n          <DevtoolsMetric\n            :value=\"totalProductionUrls\"\n            label=\"total URLs\"\n            variant=\"success\"\n          />\n          <DevtoolsMetric\n            v-if=\"totalProductionWarnings > 0\"\n            :value=\"totalProductionWarnings\"\n            label=\"warnings\"\n            variant=\"warning\"\n          />\n        </div>\n\n        <!-- Each production sitemap -->\n        <DevtoolsSection\n          v-for=\"(sitemap, i) in productionData.sitemaps\"\n          :key=\"i\"\n        >\n          <template #text>\n            <div class=\"flex items-center gap-2\">\n              <span class=\"font-semibold\">{{ sitemapPathFromUrl(sitemap.loc) }}</span>\n              <DevtoolsMetric\n                :value=\"sitemap.urlCount\"\n                label=\"URLs\"\n                variant=\"success\"\n              />\n              <UIcon\n                v-if=\"sitemap.error\"\n                name=\"carbon:warning\"\n                class=\"text-red-500\"\n              />\n              <UIcon\n                v-else-if=\"sitemap.warnings.length\"\n                name=\"carbon:warning-alt\"\n                class=\"text-amber-500\"\n              />\n            </div>\n          </template>\n          <template #description>\n            <a\n              :href=\"sitemap.loc\"\n              target=\"_blank\"\n              class=\"link-external text-xs font-mono text-[var(--color-text-muted)]\"\n            >\n              {{ sitemap.loc }}\n            </a>\n          </template>\n          <div v-if=\"sitemap.error || sitemap.warnings.length\" class=\"space-y-3\">\n            <DevtoolsAlert\n              v-if=\"sitemap.error\"\n              variant=\"warning\"\n            >\n              {{ sitemap.error }}\n            </DevtoolsAlert>\n            <DevtoolsAlert\n              v-if=\"sitemap.warnings.length\"\n              variant=\"warning\"\n            >\n              <div>\n                <div class=\"text-xs font-semibold mb-1\">\n                  {{ sitemap.warnings.length }} validation warning{{ sitemap.warnings.length > 1 ? 's' : '' }}\n                </div>\n                <ul class=\"prod-warnings-list\">\n                  <li\n                    v-for=\"(w, wi) in sitemap.warnings\"\n                    :key=\"wi\"\n                  >\n                    <template v-if=\"w.context?.url\">\n                      <code>{{ w.context.url }}</code>:\n                    </template>\n                    {{ w.message }}\n                  </li>\n                </ul>\n              </div>\n            </DevtoolsAlert>\n          </div>\n        </DevtoolsSection>\n\n        <!-- Hint about debug mode -->\n        <DevtoolsAlert variant=\"info\">\n          Want to see full source details and URL validation? Deploy with <code>sitemap: { debug: true }</code> to get the same detailed view as development mode.\n        </DevtoolsAlert>\n      </template>\n    </template>\n\n    <!-- Local mode -->\n    <template v-else>\n      <div>\n        <h2 class=\"text-lg font-semibold mb-1\">\n          Sitemaps\n        </h2>\n        <p class=\"text-xs text-[var(--color-text-muted)]\">\n          The sitemaps generated from your site.\n        </p>\n      </div>\n      <DevtoolsSection\n        v-for=\"(sitemap, key) in data?.sitemaps\"\n        :key=\"key\"\n      >\n        <template #text>\n          <div class=\"flex items-center gap-2\">\n            <span class=\"font-semibold\">{{ sitemap.sitemapName }}</span>\n            <a\n              target=\"_blank\"\n              :href=\"resolveSitemapUrl(sitemap.sitemapName)\"\n              class=\"link-external text-xs font-mono text-[var(--color-text-muted)]\"\n            >\n              {{ resolveSitemapPath(sitemap.sitemapName) }}\n            </a>\n            <UIcon\n              v-if=\"(sitemap.sources || []).some(s => typeof s !== 'string' && 'error' in s && !!s.error)\"\n              name=\"carbon:warning\"\n              class=\"text-red-500\"\n            />\n            <UIcon\n              v-else-if=\"(sitemap.sources || []).some(s => typeof s !== 'string' && '_urlWarnings' in s && s._urlWarnings?.length)\"\n              name=\"carbon:warning-alt\"\n              class=\"text-amber-500\"\n            />\n          </div>\n        </template>\n        <div class=\"space-y-5\">\n          <template v-if=\"sitemap.sitemapName === 'index'\">\n            <DevtoolsAlert variant=\"info\">\n              Links to your other sitemaps.\n              <a\n                href=\"https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps\"\n                target=\"_blank\"\n                class=\"link-external\"\n              >\n                Learn more\n              </a>\n            </DevtoolsAlert>\n          </template>\n          <template v-else>\n            <div\n              v-if=\"sitemap.sources && sitemap.sources.length\"\n              class=\"flex gap-4\"\n            >\n              <div class=\"w-32 flex-shrink-0\">\n                <div class=\"font-semibold text-sm\">\n                  Sources\n                </div>\n              </div>\n              <div class=\"flex-grow space-y-2\">\n                <Source\n                  v-for=\"(source, k) in (sitemap.sources as SitemapSourceResolved[])\"\n                  :key=\"k\"\n                  :source=\"source\"\n                />\n              </div>\n            </div>\n            <div class=\"flex gap-4\">\n              <div class=\"w-32 flex-shrink-0\">\n                <div class=\"font-semibold text-sm\">\n                  App Sources\n                </div>\n              </div>\n              <div class=\"flex-grow flex items-center gap-3\">\n                <div\n                  v-if=\"sitemap.includeAppSources && appSourcesExcluded !== true\"\n                  class=\"status-enabled\"\n                >\n                  <UIcon\n                    name=\"carbon:checkmark\"\n                    class=\"text-sm\"\n                  />\n                  <span>Enabled</span>\n                </div>\n                <div\n                  v-else\n                  class=\"status-disabled\"\n                >\n                  <UIcon\n                    name=\"carbon:close\"\n                    class=\"text-sm\"\n                  />\n                  <span>Disabled</span>\n                </div>\n                <NuxtLink\n                  to=\"/app-sources\"\n                  class=\"text-xs text-[var(--seo-green)] hover:underline\"\n                >\n                  View details\n                </NuxtLink>\n              </div>\n            </div>\n            <div class=\"flex gap-4\">\n              <div class=\"w-32 flex-shrink-0\">\n                <div class=\"font-semibold text-sm\">\n                  Options\n                </div>\n              </div>\n              <div class=\"flex-grow\">\n                <DevtoolsKeyValue\n                  :items=\"sitemapOptionsAsKeyValues(sitemap)\"\n                  striped\n                />\n              </div>\n            </div>\n          </template>\n        </div>\n      </DevtoolsSection>\n    </template>\n  </div>\n</template>\n\n<style scoped>\n.status-enabled {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.375rem;\n  padding: 0.25rem 0.625rem;\n  font-size: 0.75rem;\n  font-weight: 500;\n  border-radius: var(--radius-sm);\n  background: oklch(75% 0.15 145 / 0.12);\n  color: oklch(50% 0.15 145);\n}\n\n.dark .status-enabled {\n  background: oklch(50% 0.15 145 / 0.15);\n  color: oklch(75% 0.18 145);\n}\n\n.status-disabled {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.375rem;\n  padding: 0.25rem 0.625rem;\n  font-size: 0.75rem;\n  font-weight: 500;\n  border-radius: var(--radius-sm);\n  background: oklch(65% 0.12 25 / 0.1);\n  color: oklch(55% 0.15 25);\n}\n\n.dark .status-disabled {\n  background: oklch(45% 0.1 25 / 0.15);\n  color: oklch(70% 0.12 25);\n}\n\n.prod-warnings-list {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n  font-size: 0.6875rem;\n  line-height: 1.5;\n  color: var(--color-text-muted);\n}\n\n.prod-warnings-list li {\n  padding: 0.125rem 0;\n}\n\n.prod-warnings-list code {\n  font-family: var(--font-mono);\n  font-size: 0.625rem;\n  padding: 0.0625rem 0.3125rem;\n  border-radius: 3px;\n  background: var(--color-surface-sunken);\n  color: var(--color-text);\n}\n</style>\n"
  },
  {
    "path": "devtools/pages/user-sources.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport Source from '../components/Source.vue'\nimport { data } from '../composables/state'\n\nconst userSources = computed(() => (data.value?.globalSources || []).filter(s => s.sourceType === 'user'))\n</script>\n\n<template>\n  <div class=\"space-y-5 animate-fade-up\">\n    <div>\n      <h2 class=\"text-lg font-semibold mb-1\">\n        User Sources\n      </h2>\n      <p class=\"text-xs text-[var(--color-text-muted)]\">\n        Manually provided global sources provided by you.\n      </p>\n    </div>\n    <template v-if=\"userSources.length\">\n      <Source\n        v-for=\"(source, key) in userSources\"\n        :key=\"key\"\n        :source=\"source\"\n      />\n    </template>\n    <DevtoolsEmptyState\n      v-else\n      title=\"No user sources configured\"\n      description=\"Add custom sources via the sources option in your sitemap config.\"\n      icon=\"carbon:add-alt\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "devtools/tsconfig.json",
    "content": "{\n  \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
  },
  {
    "path": "docs/content/0.getting-started/0.introduction.md",
    "content": "---\ntitle: 'Nuxt Sitemap'\ndescription: 'Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.'\nnavigation:\n  title: 'Introduction'\nrelatedPages:\n  - path: /docs/robots/getting-started/installation\n    title: Nuxt Robots\n  - path: /docs/site-config/getting-started/installation\n    title: Nuxt Site Config\n  - path: /learn/controlling-crawlers\n    title: Controlling Web Crawlers\n---\n\n## Why use Nuxt Sitemap?\n\nNuxt Sitemap automatically generates XML sitemaps with zero configuration, including image discovery and i18n support.\n\nThe module outputs a [sitemap.xml](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview) file that search engines use to understand your site structure and index it more effectively.\n\nWhile it's not required to have a sitemap, it can be a powerful tool in getting your content indexed more frequently and more accurately,\nespecially for larger sites or sites with complex structures.\n\nWhile it's simple to create your own sitemap.xml file, it can be time-consuming to keep it up-to-date with your site's content\nand easy to miss best practices.\n\nNuxt Sitemap automatically generates the sitemap for you based on your site's content, with support for lastmod, image discovery and more.\n\nReady to get started? Check out the [installation guide](/docs/sitemap/getting-started/installation) or learn more on the [Controlling Web Crawlers](/learn-seo/nuxt/controlling-crawlers) guide.\n\n## Features\n\n- 🌴 Single /sitemap.xml or multiple /posts-sitemap.xml, /pages-sitemap.xml\n- 📊 Fetch your sitemap URLs from anywhere\n- 😌 Image discovery, lastmod support, and best practice sitemaps\n- 🔄 SWR caching, route rules support\n- 🎨 Debug using the Nuxt DevTools integration or the XML Stylesheet\n- 🤝 Integrates seamlessly with Nuxt I18n and Nuxt Content\n\n::callout{icon=\"i-heroicons-wrench\" to=\"/tools/xml-sitemap-validator\"}\n**Validate your sitemap** - Use our free [XML Sitemap Validator](/tools/xml-sitemap-validator) to check structure and ensure Google compliance.\n::\n"
  },
  {
    "path": "docs/content/0.getting-started/1.installation.md",
    "content": "---\ntitle: 'Install Nuxt Sitemap'\ndescription: 'Get started with Nuxt Sitemap by installing the dependency to your project.'\nnavigation:\n  title: 'Installation'\nrelatedPages:\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n  - path: /docs/robots/getting-started/installation\n    title: Nuxt Robots\n  - path: /docs/site-config/getting-started/installation\n    title: Nuxt Site Config\n---\n\n## Setup Module\n\nWant to know why you might need this module? Check out the [introduction](/docs/sitemap/getting-started/introduction).\n\nTo get started with Nuxt Sitemap, you need to install the dependency and add it to your Nuxt config.\n\n:ModuleInstall{name=\"@nuxtjs/sitemap\"}\n\n::tip\nGenerate an Agent Skill for this package using [skilld](https://github.com/harlan-zw/skilld):\n```bash\nnpx skilld add @nuxtjs/sitemap\n```\n::\n\n## Verifying Installation\n\nAfter you've set up the module with the minimal config, you should be able to visit [`/sitemap.xml`](http://localhost:3000/sitemap.xml) to see the generated sitemap.\n\nYou may notice that the URLs point to your `localhost` domain, this is to make navigating your local site easier, and will be updated when you deploy your site.\n\nAll pages preset are discovered from your [Application Sources](/docs/sitemap/getting-started/data-sources), for dynamic URLs see [Dynamic URLs](/docs/sitemap/guides/dynamic-urls).\n\nYou can debug this further in Nuxt DevTools under the Sitemap tab.\n\n## Configuration\n\nAt a minimum the module requires a Site URL to be set, this is to ensure only your canonical domain is being used for\nthe sitemap. A site name can also be provided to customize the sitemap [stylesheet](/docs/sitemap/advanced/customising-ui).\n\n::warning\nWithout a Site URL, your sitemap will use localhost in production.\n::\n\n:SiteConfigQuickSetup\n\nTo ensure search engines find your sitemap, you will need to add it to your robots.txt. It's recommended to use the [Nuxt Robots](/docs/robots/getting-started/installation) module for this.\n\n:ModuleCard{slug=\"robots\" class=\"w-1/2\"}\n\nEvery site is different and will require their own further unique configuration, to give you a head start:\n\n- [Dynamic URL Endpoint](/docs/sitemap/guides/dynamic-urls) - If you have dynamic URLs you need to add to the sitemap, you can use a runtime API endpoint. For example, if your\ngenerating your site from a CMS.\n- [Multi Sitemaps](/docs/sitemap/guides/multi-sitemaps) - If you have 10k+ pages, you may want to split your sitemap into multiple files\nso that search engines can process them more efficiently.\n\nYou do not need to worry about any further configuration in most cases, check the [best practices](/docs/sitemap/guides/best-practices) guide for more information.\n\n## Next Steps\n\nYou've successfully installed Nuxt Sitemap. Here's the recommended reading path:\n\n1. **[Data Sources](/docs/sitemap/getting-started/data-sources)** - Understand where your sitemap URLs come from\n2. **[Dynamic URLs](/docs/sitemap/guides/dynamic-urls)** - Add URLs from a CMS or database\n3. **[Best Practices](/docs/sitemap/guides/best-practices)** - Ensure your sitemap follows SEO guidelines\n\n**Using other Nuxt modules?**\n- [Nuxt I18n](/docs/sitemap/guides/i18n) - Automatic locale sitemaps\n- [Nuxt Content](/docs/sitemap/guides/content) - Configure sitemap from markdown frontmatter\n\n**Ready to deploy?** Check out [Submitting Your Sitemap](/docs/sitemap/guides/submitting-sitemap).\n"
  },
  {
    "path": "docs/content/0.getting-started/2.data-sources.md",
    "content": "---\ntitle: Data Sources\ndescription: Understand where your sitemap URLs come from.\nnavigation:\n  title: 'Data Sources'\n---\n\n## Where do sitemap URLs come from?\n\nAfter installing the module, you may wonder: where do the URLs in your sitemap come from?\n\nEvery URL belongs to a **source**. There are two types:\n- **Application Sources** - Automatically discovered from your Nuxt app\n- **User Sources** - Manually provided by you\n\nFor most sites, application sources handle everything automatically. You only need user sources when you have dynamic routes from a CMS or database.\n\n## Application Sources\n\nApplication sources are automatically generated from your Nuxt application. They provide convenience by automatically discovering URLs from your app's structure, but can be disabled if they don't match your needs.\n\n- `nuxt:pages` - Statically analysed pages of your application (including [`definePageMeta`](/docs/sitemap/advanced/loc-data#modify-loc-data-with-page-meta) sitemap config)\n- `nuxt:prerender` - URLs that were prerendered\n- `nuxt:route-rules` - URLs from your route rules\n- `@nuxtjs/i18n:pages` - When using the `pages` config with Nuxt I18n. See [Nuxt I18n](/docs/sitemap/guides/i18n) for more details.\n- `nuxt-i18n-micro:pages` - When using the `pages` config with Nuxt I18n Micro. See [Nuxt I18n](/docs/sitemap/guides/i18n) for more details.\n- `@nuxt/content@v2:urls` - When using Nuxt Content v2. See [Nuxt Content](/docs/sitemap/guides/content) for more details.\n- `@nuxt/content@v3:urls` - When using Nuxt Content v3. See [Nuxt Content](/docs/sitemap/guides/content) for more details.\n\n### Disabling Application Sources\n\nYou can disable application sources individually or all at once using the `excludeAppSources` config option.\n\n::code-group\n\n```ts [Disable all app sources]\nexport default defineNuxtConfig({\n  sitemap: {\n    // exclude all app sources\n    excludeAppSources: true,\n  }\n})\n```\n\n```ts [Disable pages app source]\nexport default defineNuxtConfig({\n  sitemap: {\n    // exclude static pages\n    excludeAppSources: ['nuxt:pages'],\n  }\n})\n```\n\n::\n\n## User Sources\n\nUser sources allow you to manually configure where your sitemap URLs come from. These are especially useful for dynamic routes that aren't using [prerendering discovery](/docs/sitemap/guides/prerendering).\n\nYou have several options for providing user sources:\n\n### 1. Build-time Sources with `urls` Function\n\nFor sitemap data that only needs to be updated at build time, the `urls` function is the simplest solution. This function runs once during sitemap generation.\n\nIt should return an array of path strings or [URL objects](/docs/sitemap/guides/dynamic-urls#url-structure-reference).\n\n::code-group\n\n```ts [Simple strings]\nexport default defineNuxtConfig({\n  sitemap: {\n    urls: ['/about', '/contact', '/products/special-offer']\n  }\n})\n```\n\n```ts [Async function]\nexport default defineNuxtConfig({\n  sitemap: {\n    urls: async () => {\n      const response = await fetch('https://api.example.com/posts')\n      const posts = await response.json()\n      return posts.map(post => ({\n        loc: `/blog/${post.slug}`,\n        lastmod: post.updated_at,\n      }))\n    }\n  }\n})\n```\n\n::\n\n### 2. Runtime Sources with `sources` Array\n\nFor sitemap data that must always be up-to-date at runtime, use the `sources` array. Each source is a URL that gets fetched and should return either:\n- JSON array of sitemap URL entries\n- XML sitemap document\n\n::code-group\n\n```ts [Single Sitemap]\nexport default defineNuxtConfig({\n  sitemap: {\n    sources: [\n      // create our own API endpoints\n      '/api/__sitemap__/urls',\n      // use a static remote file\n      'https://cdn.example.com/my-urls.json',\n      // hit a remote API with credentials\n      ['https://api.example.com/pages/urls', { headers: { Authorization: 'Bearer <token>' } }]\n    ]\n  }\n})\n```\n\n```ts [Multiple Sitemaps]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      foo: {\n        sources: [\n          '/api/__sitemap__/urls/foo',\n        ]\n      },\n      bar: {\n        sources: [\n          '/api/__sitemap__/urls/bar',\n        ]\n      }\n    }\n  }\n})\n```\n\n::\n\nYou can provide multiple sources, but consider implementing your own caching strategy for performance.\n\nLearn more about working with dynamic data in the [Dynamic URLs](/docs/sitemap/guides/dynamic-urls) guide.\n\n### 3. Dynamic Sources Using Nitro Hooks\n\nFor advanced use cases like forwarding authentication headers or adding sources based on request context, see the [Nitro Hooks documentation](/docs/sitemap/nitro-api/nitro-hooks#sitemap-sources).\n"
  },
  {
    "path": "docs/content/0.getting-started/3.troubleshooting.md",
    "content": "---\ntitle: \"Troubleshooting Nuxt Sitemap\"\ndescription: Common issues and debugging tips for Nuxt Sitemap.\nnavigation:\n  title: 'Troubleshooting'\nrelatedPages:\n  - path: /docs/sitemap/advanced/customising-ui\n    title: Customising the UI\n  - path: /docs/sitemap/guides/submitting-sitemap\n    title: Submitting Your Sitemap\n  - path: /docs/nuxt-seo/getting-started/troubleshooting\n    title: Nuxt SEO Troubleshooting\n---\n\n## Debugging\n\n### Nuxt DevTools\n\nThe best tool for debugging is the Nuxt DevTools integration with Nuxt Sitemap.\n\nThis will show you all of your sitemaps and the sources used to generate it.\n\n### Debug Endpoint\n\nIf you prefer looking at the raw data, you can use the debug endpoint. This is only enabled in\ndevelopment unless you enable the `debug` option.\n\nVisit `/__sitemap__/debug.json` within your browser, this is the same data used by Nuxt DevTools.\n\n### Debugging Prerendering\n\nIf you're trying to debug the prerendered sitemap, you should enable the `debug` option and check your output\nfor the file `.output/public/__sitemap__/debug.json`.\n\n## Submitting an Issue\n\nWhen submitting an issue, it's important to provide as much information as possible.\n\nThe easiest way to do this is to create a minimal reproduction using the Stackblitz playgrounds:\n\n- [Dynamic URLs](https://stackblitz.com/edit/nuxt-starter-dyraxc?file=server%2Fapi%2F_sitemap-urls.ts)\n- [i18n](https://stackblitz.com/edit/nuxt-starter-jwuie4?file=app.vue)\n- [Manual Chunking](https://stackblitz.com/edit/nuxt-starter-umyso3?file=nuxt.config.ts)\n- [Nuxt Content Document Driven](https://stackblitz.com/edit/nuxt-starter-a5qk3s?file=nuxt.config.ts)\n\n## Troubleshooting FAQ\n\n### Why is my browser not rendering the XML properly?\n\nWhen disabling the [XSL](/docs/sitemap/advanced/customising-ui#disabling-the-xls) (XML Stylesheet) in, the XML will\nbe rendered by the browser.\n\nIf you have a i18n integration, then it's likely you'll see your sitemap look raw text instead of XML.\n\n![Broken XML because of xhtml namespace.](/docs/sitemap/formatting-error.png)\n\nThis is a [browser bug](https://bugs.chromium.org/p/chromium/issues/detail?id=580033) in parsing the `xhtml` namespace which is required to add localised URLs to your sitemap.\nThere is no workaround besides re-enabled the XSL.\n\n### Google Search Console shows Error when submitting my Sitemap?\n\nSeeing \"Error\" when submitting a new sitemap is common. This is because Google previously\ncrawled your site for a sitemap and found nothing.\n\nIf your sitemap is [validating](https://www.xml-sitemaps.com/validate-xml-sitemap.html) correctly, then you're all set.\nIt's best to wait a few days and check back. In nearly all cases, the error will resolve itself.\n\n### Google Search Console shows \"Couldn't fetch\" or \"Sitemap could not be read\"?\n\nThis is a well known Google Search Console issue where it reports \"Couldn't fetch\" or \"Sitemap could not be read\" even though the sitemap XML is perfectly valid. This is not caused by the module.\n\n**Why it happens:** Google caches sitemap fetch results. When you first submit a sitemap (or resubmit at the same URL), Google may return a stale cached failure instead of actually re-fetching the sitemap. This is especially common when:\n- You've just deployed your site for the first time\n- You've recently added the sitemap module\n- Google previously crawled the URL and found no sitemap\n\n**How to verify your sitemap is fine:**\n\n1. Open the [URL Inspection tool](https://search.google.com/search-console?action=inspect) in Google Search Console\n2. Paste your sitemap URL (e.g. `https://example.com/sitemap.xml`)\n3. Click **Live test**\n4. Expand the **Page availability** section and confirm: Crawl allowed = \"Yes\", Page fetch = \"Successful\", Indexing allowed = \"Yes\"\n\nIf the live test passes, your sitemap is valid and the \"Couldn't fetch\" status is a Google caching issue.\n\n**Workarounds:**\n\n::steps{level=\"4\"}\n#### Wait it out\n\nIn most cases the error resolves itself within 24 to 72 hours without any changes on your end.\n\n#### Remove and resubmit in Search Console\n\nRemove your sitemap from the Google Search Console Sitemaps report, then resubmit it. Google will discover any sub-sitemaps (e.g. locale-specific sitemaps) automatically from the sitemap index.\n\n#### Change the sitemap URL to force a fresh fetch\n\nGoogle caches results by URL. Changing the sitemap filename forces Google to treat it as a new sitemap, bypassing any cached failures. You can do this with the `sitemapName` option:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemapName: 'sitemap_index.xml',\n  },\n})\n```\n\nAfter deploying, submit the new URL in Google Search Console. This workaround has consistently resolved the issue immediately for affected users.\n::\n\n::note\nYou can validate your sitemap independently using the [XML Sitemap Validator](/tools/xml-sitemap-validator) or [xml-sitemaps.com](https://www.xml-sitemaps.com/validate-xml-sitemap.html) to confirm the issue is on Google's side.\n::\n\n### Getting 404/Error when Pinging Google?\n\nIf you are using a script or CI/CD job to \"ping\" Google with your sitemap URL (e.g., `google.com/ping?sitemap=...`), it will now fail.\n\nGoogle **deprecated** the sitemap ping endpoint in January 2024. You should remove this step from your deployment process and rely on `robots.txt` discovery or Google Search Console.\n\n### Search Console shows \"Invalid character\" error?\n\nThis happens when URLs contain reserved characters like `$`, `:`, or `@` that aren't properly encoded for XML.\n\nThe module automatically encodes unicode characters (emojis, accents) but does not encode RFC-3986 reserved characters.\n\n**Solution:** If your API returns pre-encoded URLs, mark them with `_encoded: true` to prevent double-encoding:\n\n```ts [server/api/__sitemap__/urls.ts]\nexport default defineSitemapEventHandler(async () => {\n  const urls = await $fetch('https://api.example.com/pages')\n  // URLs are already encoded: [{ path: '/products/%24pecial' }]\n\n  return urls.map(url => ({\n    loc: url.path,\n    _encoded: true,\n  }))\n})\n```\n\nSee [Handling Pre-Encoded URLs](/docs/sitemap/guides/dynamic-urls#handling-pre-encoded-urls) for more details.\n\n## Debugging Tools\n\n- [XML Sitemap Validator](/tools/xml-sitemap-validator) - Validate sitemap structure, check URL format, and test against Google requirements\n"
  },
  {
    "path": "docs/content/1.guides/0.dynamic-urls.md",
    "content": "---\ntitle: Dynamic URL Endpoints\ndescription: Use runtime API endpoints to generate dynamic URLs for your sitemap.\nrelatedPages:\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n  - path: /docs/sitemap/guides/i18n\n    title: I18n Integration\n  - path: /docs/sitemap/guides/multi-sitemaps\n    title: Multi Sitemaps\n---\n\n## Introduction\n\nWhen working with a CMS or external data sources, you may need to generate sitemap URLs dynamically at runtime.\n\nThe module supports two types of data sources:\n- JSON responses from API endpoints\n- XML sitemaps from external sources\n\n## URL Structure Reference\n\nAll sitemap URLs follow this structure, whether from JSON endpoints or the `urls` config:\n\n```ts\ninterface SitemapUrl {\n  loc: string // Required: The URL path (e.g., '/blog/my-post')\n  lastmod?: string | Date // Optional: Last modified date (ISO 8601 format or Date object)\n  changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n  priority?: 0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1 // Optional: 0.0 to 1.0\n  images?: ImageEntry[] // Optional: Array of image objects\n  videos?: VideoEntry[] // Optional: Array of video objects\n  news?: GoogleNewsEntry // Optional: Google News entry\n  _sitemap?: string // Optional: Specify which sitemap this URL belongs to (for multi-sitemap setups)\n  _encoded?: boolean // Optional: Mark the URL as already encoded\n  _i18nTransform?: boolean // Optional: Automatically transform the URL for all locales\n  alternatives?: Array<{ // Optional: For i18n/alternate language URLs\n    hreflang: string // Language code (e.g., 'en', 'fr', 'es')\n    href: string // Full URL to alternative version\n  }>\n}\n```\n\n## Using External XML Sitemaps\n\nIf you have an existing XML sitemap, you can reference it directly in your configuration:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sources: [\n      'https://example.com/sitemap.xml',\n    ]\n  }\n})\n```\n\n## Dynamic URLs from External APIs\n\nWhen fetching dynamic URLs from external APIs, you have two main approaches:\n\n1. **Direct source configuration** - Use when the API returns data in the correct format\n2. **Custom API endpoint** - Use when you need to transform data or implement caching\n\n### 1. Using Source Configuration\n\nFor APIs that require authentication or custom headers, provide sources as an array with fetch options:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sources: [\n      // Unauthenticated endpoint\n      'https://api.example.com/pages/urls',\n      // Authenticated endpoint\n      [\n        'https://authenticated-api.example.com/pages/urls',\n        { headers: { Authorization: 'Bearer <token>' } }\n      ]\n    ]\n  }\n})\n```\n\n### 2. Creating Custom Endpoints\n\n**Step 1: Create the API endpoint**\n\nUse the [`defineSitemapEventHandler()`{lang=\"ts\"}](/docs/sitemap/nitro-api/nitro-hooks) helper to create type-safe sitemap endpoints:\n\n::code-group\n\n```ts [Simple]\nimport type { SitemapUrlInput } from '#sitemap/types'\n// server/api/__sitemap__/urls.ts\nimport { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/about-us',\n      // Specify which sitemap this URL belongs to\n      _sitemap: 'pages',\n    },\n  ] satisfies SitemapUrlInput[]\n})\n```\n\n```ts [Multiple Sitemaps]\nimport type { SitemapUrl } from '#sitemap/types'\n// server/api/__sitemap__/urls.ts\nimport { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(async () => {\n  const [posts, pages] = await Promise.all([\n    $fetch<{ path: string, slug: string }[]>('https://api.example.com/posts')\n      .then(posts => posts.map(p => ({\n        loc: `/blog/${p.slug}`, // Transform to your domain structure\n        _sitemap: 'posts',\n      } satisfies SitemapUrl))),\n    $fetch<{ path: string }[]>('https://api.example.com/pages')\n      .then(pages => pages.map(p => ({\n        loc: p.path,\n        _sitemap: 'pages',\n      } satisfies SitemapUrl))),\n  ])\n  return [...posts, ...pages]\n})\n```\n\n```ts [WordPress Example]\n// server/api/__sitemap__/wordpress.ts\nimport { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(async () => {\n  const posts = await $fetch('https://api.externalwebsite.com/wp-json/wp/v2/posts')\n\n  return posts.map(post => ({\n    // Transform external URL to your domain\n    loc: `/blog/${post.slug}`, // NOT post.link\n    lastmod: post.modified,\n    changefreq: 'weekly',\n    priority: 0.7,\n  }))\n})\n```\n\n```ts [Dynamic i18n]\nimport type { SitemapUrl } from '#sitemap/types'\n// server/api/__sitemap__/urls.ts\nimport { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(async () => {\n  const config = useRuntimeConfig()\n  const baseUrl = config.public.siteUrl\n  const locales = config.public.i18n.locales.map(locale => locale.code)\n  const isoLocales = Object.fromEntries(\n    config.public.i18n.locales.map(locale => ([locale.code, locale.iso]))\n  )\n\n  // Example: Fetch data for each locale\n  const apiQueries = locales.map(locale =>\n    $fetch(`${config.public.apiEndpoint}/sitemap/${locale}/products`)\n  )\n\n  const sitemaps = await Promise.all(apiQueries)\n\n  return sitemaps.flat().map(entry => ({\n    // explicit sitemap mapping\n    _sitemap: isoLocales[entry.locale],\n    loc: `${baseUrl}/${entry.locale}/product/${entry.url}`,\n    alternatives: entry.alternates?.map(alt => ({\n      hreflang: isoLocales[alt.locale],\n      href: `${baseUrl}/${alt.locale}/product/${alt.url}`\n    }))\n  } satisfies SitemapUrl))\n})\n```\n\n::\n\n**Step 2: Configure the endpoint**\n\nAdd your custom endpoint to the sitemap configuration:\n\n::code-group\n\n```ts [Single Sitemap]\nexport default defineNuxtConfig({\n  sitemap: {\n    sources: [\n      '/api/__sitemap__/urls',\n    ]\n  }\n})\n```\n\n```ts [Multiple Sitemaps]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts: {\n        sources: [\n          '/api/__sitemap__/urls/posts',\n        ]\n      },\n      pages: {\n        sources: [\n          '/api/__sitemap__/urls/pages',\n        ]\n      }\n    }\n  }\n})\n```\n\n::\n\n## Handling Pre-Encoded URLs\n\nBy default, the module automatically encodes URL paths. This handles special characters like spaces and unicode (e.g., emojis, accented characters).\n\nIf your API or CMS returns URLs that are already encoded, mark them with `_encoded: true` to prevent double-encoding.\n\n```ts [server/api/__sitemap__/urls.ts]\nimport { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(async () => {\n  // URLs from your API are already encoded\n  const urls = await $fetch<{ path: string }[]>('https://api.example.com/pages')\n  // e.g. [{ path: '/products/%24pecial-offer' }, { path: '/blog/%F0%9F%98%85' }]\n\n  return urls.map(url => ({\n    loc: url.path,\n    _encoded: true,\n  }))\n})\n```\n\n::callout{type=\"info\"}\nWhen `_encoded: true` is set, the module skips automatic encoding entirely. Make sure your URLs are properly encoded.\n::\n"
  },
  {
    "path": "docs/content/1.guides/1.filtering-urls.md",
    "content": "---\ntitle: Disabling Indexing\ndescription: How to filter the URLs generated from application sources.\nrelatedPages:\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n  - path: /docs/robots/getting-started/installation\n    title: Nuxt Robots\n  - path: /learn/controlling-crawlers\n    title: Controlling Web Crawlers\n---\n\n## Introduction\n\nWhen viewing your sitemap.xml for the first time, you may notice some URLs you don't want to be included.\nThese URLs are most likely coming from [Application Sources](/docs/sitemap/getting-started/data-sources).\n\nIf you don't want to disable these sources but want to remove these URLs you have a couple of options.\n\n## Nuxt Robots\n\nThe easiest way to block search engines from indexing a URL is to use the [Nuxt Robots](/docs/robots/getting-started/installation) module\nand simply block the URL in your robots.txt.\n\n:ModuleCard{slug=\"robots\" class=\"w-1/2\"}\n\nNuxt Sitemap will honour any blocked pages from being ignored in the sitemap.\n\n## Disabling indexing with Route Rules\n\nIf you don't want a page in your sitemap because you don't want search engines to crawl it,\nthen you can make use of the `robots` route rule. For comprehensive route rules documentation, see [Nuxt Robots route rules](/docs/robots/guides/route-rules).\n\n### Disabling indexing for a pattern of URLs\n\nIf you have a pattern of URLs that you want hidden from search you can use route rules.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  routeRules: {\n    // Don't add any /secret/** URLs to the sitemap.xml\n    '/secret/**': { robots: false },\n  }\n})\n```\n\n### Inline route rules\n\nIf you just have some specific pages, you can use the experimental [`defineRouteRules()`{lang=\"ts\"}](https://nuxt.com/docs/api/utils/define-route-rules), which must\nbe enabled.\n\n```vue\n<script setup lang=\"ts\">\ndefineRouteRules({\n  robots: false\n})\n</script>\n```\n\n## Filter URLs with include / exclude\n\nFor all other cases, you can use the `include` and `exclude` module options to filter URLs.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    // exclude all URLs that start with /secret\n    exclude: ['/secret/**'],\n    // include all URLs that start with /public\n    include: ['/public/**'],\n  }\n})\n```\n\nEither option supports either an array of strings, RegExp objects or a `{ regex: string }` object.\n\nProviding strings will use the [route rules path matching](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering) which\ndoes not support variable path segments in front of static ones.\n\nFor example, `/foo/**` will work but `/foo/**/bar` will not. To get around this you should use regex.\n\n### Regex Filtering\n\nFiltering using regex is more powerful and can be used to match more complex patterns. It's recommended to pass a\n`RegExp` object explicitly.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    exclude: [\n      // exclude /foo/**/bar using regex\n      new RegExp('/foo/.*/bar')\n    ],\n  }\n})\n```\n"
  },
  {
    "path": "docs/content/1.guides/2.multi-sitemaps.md",
    "content": "---\ntitle: Multi Sitemaps\ndescription: Generate multiple sitemaps for different sections of your site.\nrelatedPages:\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n  - path: /docs/sitemap/guides/dynamic-urls\n    title: Dynamic URL Endpoints\n  - path: /docs/sitemap/advanced/chunking-sources\n    title: Sitemap Chunking\n  - path: /docs/sitemap/advanced/performance\n    title: Sitemap Performance\n---\n\n## Introduction\n\nBy default, the module generates a single `/sitemap.xml` file, which works perfectly for most websites.\n\nFor larger sites with thousands of URLs, multiple sitemaps offer several benefits:\n- Easier debugging and management\n- More efficient search engine crawling\n- Better organization of content types\n\n## Enabling Multiple Sitemaps\n\nYou can enable multiple sitemaps using the `sitemaps` option in two ways:\n\n1. **Manual Chunking** (`object`): Best for sites with clear content types (pages, posts, etc) or fewer than 1000 URLs\n2. **Automatic Chunking** (`true`): Best for sites with more than 1000 URLs without clear content types\n\n::code-group\n\n```ts [Manual Chunking]\nexport default defineNuxtConfig({\n  sitemap: {\n    // manually chunk into multiple sitemaps\n    sitemaps: {\n      posts: {\n        include: [\n          '/blog/**',\n        ],\n        // example: give blog posts slightly higher priority (this is optional)\n        defaults: { priority: 0.7 },\n      },\n      pages: {\n        exclude: [\n          '/blog/**',\n        ]\n      },\n    },\n  },\n})\n```\n\n```ts [Automatic Chunking]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: true,\n    // modify the chunk size if you need\n    defaultSitemapsChunkSize: 2000 // default 1000\n  },\n})\n```\n\n::\n\n### Customizing Sitemap URLs\n\nBy default, all multi-sitemaps are served under the `/__sitemap__/` prefix. You can customize this behavior to create cleaner URLs:\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemapsPathPrefix: '/', // or false\n    sitemaps: {\n      // will be available at /sitemap-foo.xml\n      'sitemap-foo': {\n        // ...\n      }\n    }\n  }\n})\n```\n\n## Manual Chunking\n\nManual chunking gives you complete control over how your URLs are distributed across sitemaps. This approach is ideal when you have distinct content types or specific organizational needs.\n\n### Setting Default Values\n\nYou can provide default values for URLs within each sitemap using the `defaults` option:\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts: {\n        // posts low priority\n        defaults: { priority: 0.7 },\n      },\n    },\n  },\n})\n```\n\n### Extending App Sources\n\nWhen you already have all URLs in your single sitemap but want to split them into separate sitemaps, you can extend existing [app sources](/docs/sitemap/getting-started/data-sources) and apply filters.\n\nAvailable options:\n- `includeAppSources`: Include URLs from automatic app sources\n- `include`: Array of glob patterns to include\n- `exclude`: Array of glob patterns to exclude\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      pages: {\n        // extend the nuxt:pages app source\n        includeAppSources: true,\n        // filter the URLs to only include pages\n        exclude: ['/blog/**'],\n      },\n      posts: {\n        // extend the nuxt:pages app source\n        includeAppSources: true,\n        // filter the URLs to only include pages\n        include: ['/blog/**'],\n      },\n    },\n  },\n})\n```\n\n#### Using the `_sitemap` Key\n\nWhen using global sources and need to direct specific URLs to particular sitemaps, use the `_sitemap` key:\n\n::code-group\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sources: [\n      '/api/sitemap-urls'\n    ],\n    sitemaps: {\n      pages: {\n        includeAppSources: true,\n        exclude: ['/**']\n        // ...\n      },\n    },\n  },\n})\n```\n\n```ts [server/api/sitemap-urls.ts]\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/about-us',\n      // will end up in the pages sitemap\n      _sitemap: 'pages',\n    }\n  ]\n})\n```\n\n::\n\n### Managing Custom Sources\n\nFor sitemaps that need to fetch URLs from endpoints, you have two options:\n\n- `urls`: Static URLs to include in the sitemap (avoid for large URL sets)\n- `sources`: Endpoints to fetch [dynamic URLs](/docs/sitemap/guides/dynamic-urls) from (JSON or XML)\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts: {\n        urls() {\n          // resolved when the sitemap is shown\n          return ['/foo', '/bar']\n        },\n        sources: [\n          '/api/sitemap-urls'\n        ]\n      },\n    },\n  },\n})\n```\n\n### Chunking Large Sources\n\nWhen you have sources that return a large number of URLs, you can enable chunking to split them into multiple XML files:\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts: {\n        sources: ['/api/posts'], // returns 10,000 posts\n        chunks: true, // Enable chunking with default size (1000)\n      },\n      products: {\n        sources: ['/api/products'], // returns 50,000 products\n        chunks: 5000, // Chunk into files with 5000 URLs each\n      },\n      articles: {\n        sources: ['/api/articles'],\n        chunks: true,\n        chunkSize: 2000, // Alternative way to specify chunk size\n      }\n    }\n  },\n})\n```\n\nThis will generate:\n- `/sitemap_index.xml` - Lists all sitemaps including chunks\n- `/posts-0.xml` - First 1000 posts\n- `/posts-1.xml` - Next 1000 posts\n- `/products-0.xml` - First 5000 products\n- `/products-1.xml` - Next 5000 products\n- etc.\n\n### Linking External Sitemaps\n\nUse the special `index` key to add external sitemaps to your sitemap index:\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      // generated sitemaps\n      posts: {\n        // ...\n      },\n      pages: {\n        // ...\n      },\n      // extending the index sitemap with an external sitemap\n      index: [\n        { sitemap: 'https://www.google.com/sitemap-pages.xml' }\n      ]\n    }\n  }\n})\n```\n\n## Automatic Chunking\n\nAutomatic chunking divides your sitemap into multiple files based on URL count. This feature:\n- Uses numbered naming convention (`0.xml`, `1.xml`, etc.)\n- Chunks based on `defaultSitemapsChunkSize` (default: 1000 URLs per sitemap)\n- Should be avoided for sites with fewer than 1000 URLs\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    // automatically chunk into multiple sitemaps\n    sitemaps: true,\n    // optionally customize chunk size\n    defaultSitemapsChunkSize: 2000 // default: 1000\n  },\n})\n```\n"
  },
  {
    "path": "docs/content/1.guides/3.i18n.md",
    "content": "---\ntitle: I18n\ndescription: Setting up a sitemap with Nuxt I18n and Nuxt I18n Micro.\nrelatedPages:\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n  - path: /docs/sitemap/guides/dynamic-urls\n    title: Dynamic URL Endpoints\n  - path: /docs/sitemap/advanced/customising-ui\n    title: Customising the UI\n---\n\n## Introduction\n\nThe sitemap module automatically integrates with [@nuxtjs/i18n](https://i18n.nuxtjs.org/) and [nuxt-i18n-micro](https://github.com/s00d/nuxt-i18n-micro) without any extra configuration.\n\nWhile the integration works out of the box, you may need to fine-tune some options depending on your i18n setup.\n\n\n## I18n Modes\n\nThe module supports two main modes for handling internationalized sitemaps:\n\n### Automatic I18n Multi Sitemap\n\nThe module automatically generates a sitemap for each locale when:\n- You're not using the `no_prefix` strategy\n- Or you're using [Different Domains](https://i18n.nuxtjs.org/docs/v7/different-domains)\n\nThis generates the following structure:\n```shell\n./sitemap_index.xml\n./en-sitemap.xml\n./fr-sitemap.xml\n# ...additional locales\n```\n\nKey features:\n- Includes [app sources](/docs/sitemap/getting-started/data-sources) automatically\n- The `nuxt:pages` source determines the correct `alternatives` for your pages\n- To disable app sources, set `excludeAppSources: true`\n\n#### Custom Sitemaps with I18n\n\nYou can add custom sitemaps alongside the automatic i18n multi-sitemap. When any sitemap uses `includeAppSources: true`, the module still generates per-locale sitemaps and merges the `exclude`/`include` filters:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      pages: {\n        includeAppSources: true,\n        exclude: ['/admin/**'],\n      },\n      posts: {\n        sources: ['/api/__sitemap__/posts'],\n      }\n    }\n  }\n})\n```\n\nThis generates:\n```shell\n./sitemap_index.xml\n./en-pages.xml   # locale sitemap with /admin/** excluded\n./fr-pages.xml   # locale sitemap with /admin/** excluded\n./posts.xml      # custom sitemap (kept as-is)\n```\n\nThe sitemap name is preserved with the format `{locale}-{name}`. Sitemaps without `includeAppSources` (like `posts`) remain as separate sitemaps.\n\n### I18n Pages Mode\n\nWhen you enable `i18n.pages` in your i18n configuration, the sitemap module generates a single sitemap using that configuration.\n\nKey differences:\n- Does not include [app sources](/docs/sitemap/getting-started/data-sources) automatically\n- You can add additional URLs using the `sources` option\n\n## Dynamic URLs with i18n\n\nBy default, dynamic URLs you provide won't have i18n data and will only appear in the default locale sitemap.\n\nTo handle i18n for dynamic URLs, use these special options:\n\n### 1. `_i18nTransform` - Automatic Locale Transformation\n\nUse `_i18nTransform: true` to automatically generate URLs for all locales:\n\n```ts [server/api/__sitemap__/urls.ts]\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/about-us',\n      // automatically creates: /en/about-us, /fr/about-us, etc.\n      _i18nTransform: true,\n    }\n  ]\n})\n```\n\n#### Custom Path Translations\n\nIf you have custom path translations defined in your i18n configuration using `pages`, the `_i18nTransform` option will automatically use them:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  i18n: {\n    pages: {\n      about: {\n        en: '/about',\n        fr: '/a-propos',\n        es: '/acerca-de',\n      },\n      services: {\n        en: '/services',\n        fr: '/offres',\n        es: '/servicios',\n      },\n    },\n  },\n})\n```\n\nWith this configuration, when you set `_i18nTransform: true` on a URL:\n\n```ts [server/api/__sitemap__/urls.ts]\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/about', // base path\n      _i18nTransform: true,\n      // automatically generates:\n      // - /about (for en)\n      // - /fr/a-propos (for fr)\n      // - /es/acerca-de (for es)\n    }\n  ]\n})\n```\n\n### 2. `_sitemap` - Specific Locale Assignment\n\nUse `_sitemap` to assign a URL to a specific locale sitemap:\n\n```ts [server/api/__sitemap__/urls.ts]\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/about-us',\n      // only appears in the English sitemap\n      _sitemap: 'en',\n    }\n  ]\n})\n```\n\n## Debugging Hreflang\n\nBy default, hreflang tags aren't visible in the XML stylesheet view. To see them, you'll need to view the page source.\n\nNote: Search engines can still see these tags even if they're not visible in the stylesheet.\n\nTo display hreflang tag counts in the visual interface, customize the columns:\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    xslColumns: [\n      { label: 'URL', width: '50%' },\n      { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },\n      { label: 'Hreflangs', select: 'count(xhtml:link)', width: '25%' },\n    ],\n  }\n})\n```\n\nFor more customization options, see the [Customising UI guide](/docs/sitemap/advanced/customising-ui).\n\n\n## Opting Out of I18n Integration\n\nIf you're using `@nuxtjs/i18n` or `nuxt-i18n-micro` but want the sitemap module to ignore it entirely, set `autoI18n: false`. This generates a single sitemap without locale prefixes or hreflang tags.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    autoI18n: false,\n  },\n})\n```\n"
  },
  {
    "path": "docs/content/1.guides/4.content.md",
    "content": "---\ntitle: Nuxt Content\ndescription: How to use the Nuxt Sitemap module with Nuxt Content.\nrelatedPages:\n  - path: /docs/sitemap/guides/dynamic-urls\n    title: Dynamic URL Endpoints\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n  - path: /docs/sitemap/advanced/loc-data\n    title: Lastmod, Priority, and Changefreq\n---\n\n## Introduction\n\nNuxt Sitemap comes with an integration for Nuxt Content that allows you to configure your sitemap entry straight from your content files directly.\n\n### Supported Content Types\n\nThe sitemap integration works with all content file types supported by Nuxt Content:\n- Markdown (`.md`)\n- YAML (`.yml` / `.yaml`)\n- JSON (`.json`)\n- CSV (`.csv`)\n\n## Setup Nuxt Content v3\n\nAdd `defineSitemapSchema()`{lang=\"ts\"} to your collection's schema to enable the `sitemap` frontmatter key.\n\n```ts [content.config.ts]\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { defineSitemapSchema } from '@nuxtjs/sitemap/content'\nimport { z } from 'zod'\n\nexport default defineContentConfig({\n  collections: {\n    content: defineCollection({\n      type: 'page',\n      source: '**/*.md',\n      schema: z.object({\n        sitemap: defineSitemapSchema(),\n      }),\n    }),\n  },\n})\n```\n\n### Filtering Content\n\nPass a `filter` function to `defineSitemapSchema()` to exclude entries at runtime. This is useful for filtering out draft posts, future content, or any entries that shouldn't appear in the sitemap.\n\n```ts [content.config.ts]\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { defineSitemapSchema } from '@nuxtjs/sitemap/content'\nimport { z } from 'zod'\n\nexport default defineContentConfig({\n  collections: {\n    // The `name` option must match the collection key\n    blog: defineCollection({\n      type: 'page',\n      source: 'blog/**/*.md',\n      schema: z.object({\n        date: z.string().optional(),\n        draft: z.boolean().optional(),\n        sitemap: defineSitemapSchema({\n          name: 'blog',\n          filter: (entry) => {\n            if (entry.draft)\n              return false\n            if (entry.date && new Date(entry.date) > new Date())\n              return false\n            return true\n          },\n        }),\n      }),\n    }),\n  },\n})\n```\n\n::important\nThe `name` option must match the collection key exactly (e.g. if your collection key is `blog`, use `name: 'blog'`). This is how the filter is matched to the correct collection at runtime.\n::\n\nThe `filter` function receives the full content entry including your custom schema fields and should return `true` to include, `false` to exclude.\n\n### Transforming URLs with `onUrl`\n\nUse the `onUrl` callback to transform the sitemap entry for each item in a collection. The callback receives the resolved URL object; mutate it directly to change `loc`, `lastmod`, `priority`, or any other field.\n\nThis is especially useful when using per-locale collections with `@nuxtjs/i18n`. If a collection uses `prefix: '/'` or `prefix: ''` to strip the locale directory from content paths, the sitemap URLs will be missing the locale prefix. Use `onUrl` to re-add it:\n\n```ts [content.config.ts]\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { defineSitemapSchema } from '@nuxtjs/sitemap/content'\nimport { z } from 'zod'\n\nexport default defineContentConfig({\n  collections: {\n    content_en: defineCollection({\n      type: 'page',\n      source: { include: 'en/**', prefix: '/' },\n      schema: z.object({\n        sitemap: defineSitemapSchema(),\n      }),\n    }),\n    content_ja: defineCollection({\n      type: 'page',\n      source: { include: 'ja/**', prefix: '/' },\n      schema: z.object({\n        sitemap: defineSitemapSchema({\n          name: 'content_ja',\n          onUrl(url) {\n            url.loc = `/ja${url.loc}`\n          },\n        }),\n      }),\n    }),\n  },\n})\n```\n\nWithout `onUrl`, both collections would produce `loc: '/about'` for their `about.md` files. With the transform, the ja collection entries correctly produce `loc: '/ja/about'`, allowing the i18n sitemap builder to assign them to the correct per-locale sitemap.\n\nThe callback also receives the full content entry and collection name, so you can use any content field to drive sitemap values:\n\n```ts\nschema: z.object({\n  featured: z.boolean().optional(),\n  sitemap: defineSitemapSchema({\n    name: 'blog',\n    onUrl(url, entry, collection) {\n      url.loc = url.loc.replace('/posts/', '/blog/')\n      url.priority = entry.featured ? 1.0 : 0.5\n    },\n  }),\n})\n```\n\n::important\nThe `name` option must match the collection key exactly (e.g. if your collection key is `content_ja`, use `name: 'content_ja'`).\n::\n\nDue to current Nuxt Content v3 limitations, you must load the sitemap module before the content module.\n\n```ts\nexport default defineNuxtConfig({\n  modules: [\n    '@nuxtjs/sitemap',\n    '@nuxt/content' // <-- Must be after @nuxtjs/sitemap\n  ]\n})\n```\n\n\n## Setup Nuxt Content v2\n\nIn Nuxt Content v2 markdown files require either [Document Driven Mode](https://content.nuxt.com/document-driven/introduction), a `path` key to be set\nin the frontmatter or the `strictNuxtContentPaths` option to be enabled.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  // things just work!\n  content: {\n    documentDriven: true\n  }\n})\n```\n\nIf you're not using `documentDriven` mode and your content paths are the same as their real paths,\nyou can enable `strictNuxtContentPaths` to get the same behaviour.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    strictNuxtContentPaths: true\n  }\n})\n```\n\n### Advanced: Nuxt Content App Source\n\nIf you'd like to set up a more automated Nuxt Content integration and you're not using Document Driven mode, you can add content to the sitemap as you would with [Dynamic URLs](/docs/sitemap/guides/dynamic-urls).\n\nAn example of what this might look like is below, customize to your own needs.\n\n```ts [server/api/__sitemap__/urls.ts]\nimport type { ParsedContent } from '@nuxt/content/dist/runtime/types'\nimport { serverQueryContent } from '#content/server'\nimport { asSitemapUrl, defineSitemapEventHandler } from '#imports'\nimport { defineEventHandler } from 'h3'\n\nexport default defineSitemapEventHandler(async (e) => {\n  const contentList = (await serverQueryContent(e).find()) as ParsedContent[]\n  return contentList\n    .filter(c => c._path.startsWith('_articles'))\n    .map((c) => {\n      return asSitemapUrl({\n        loc: `/blog/${c._path.replace('_articles', '')}`,\n        lastmod: updatedAt\n      })\n    })\n})\n```\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sources: [\n      '/api/__sitemap__/urls'\n    ]\n  }\n})\n```\n\n## Usage\n\n### Frontmatter `sitemap`\n\nUse the `sitemap` key in your frontmatter to add a page to your sitemap.\n\nYou can provide any data that you would normally provide in the sitemap configuration.\n\n#### Markdown Example\n\n```md\n---\nsitemap:\n  loc: /my-page\n  lastmod: 2021-01-01\n  changefreq: monthly\n  priority: 0.8\n---\n\n# My Page\n```\n\n#### YAML Example\n\n```yaml [content/pages/about.yml]\ntitle: About Page\ndescription: Learn more about us\nsitemap:\n  lastmod: 2025-05-13\n  changefreq: monthly\n  priority: 0.8\ncontent: |\n  This is the about page content\n```\n\n#### JSON Example\n\n```json [content/products/widget.json]\n{\n  \"title\": \"Widget Product\",\n  \"price\": 99.99,\n  \"sitemap\": {\n    \"lastmod\": \"2025-05-14\",\n    \"changefreq\": \"weekly\",\n    \"priority\": 0.9\n  }\n}\n```\n\n### Exclude from Sitemap\n\nIf you'd like to exclude a page from the sitemap, you can set `sitemap: false` in the frontmatter or `robots: false`\nif you'd like to exclude it from search engines.\n\n```md\n---\nsitemap: false\nrobots: false\n---\n```\n\n#### Troubleshooting Exclusions\n\nIf `sitemap: false` or `robots: false` aren't working, check the following:\n\n**Nuxt Content v3** — Ensure your collection schema includes `defineSitemapSchema()` in `content.config.ts`:\n\n```ts [content.config.ts]\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { defineSitemapSchema } from '@nuxtjs/sitemap/content'\nimport { z } from 'zod'\n\nexport default defineContentConfig({\n  collections: {\n    content: defineCollection({\n      type: 'page',\n      source: '**/*.md',\n      schema: z.object({\n        sitemap: defineSitemapSchema(),\n      }),\n    }),\n  },\n})\n```\n\n**Nuxt Content v2** — Ensure you have Document Driven mode or `strictNuxtContentPaths` enabled:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  content: {\n    documentDriven: true\n  },\n  // OR\n  sitemap: {\n    strictNuxtContentPaths: true\n  }\n})\n```\n\n**Module load order** — The sitemap module must be loaded before the content module:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  modules: [\n    '@nuxtjs/sitemap', // Must be before @nuxt/content\n    '@nuxt/content'\n  ]\n})\n```\n\nIf pages still appear after these checks, clear `.nuxt` and rebuild.\n"
  },
  {
    "path": "docs/content/1.guides/5.prerendering.md",
    "content": "---\ntitle: Nuxt Prerendering\ndescription: Prerender your pages and have them all automatically added to your sitemap.\nrelatedPages:\n  - path: /docs/sitemap/advanced/images-videos\n    title: Images, Videos, News\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n---\n\n## Introduction\n\nWhen prerendering routes using Nuxt through either `nuxi generate` or using the prerender options, the module\nwill extract data from the generated HTML and add it to the sitemap.\n\nThis can be useful if you have dynamic routes that you want to be included in the sitemap and want to minimise\nyour configuration.\n\n## Extracted HTML Data\n\nThe following data can be extracted from the raw HTML.\n\n- `images` - Adds image entries `<image:image>`{lang=\"xml\"}.\n\nPasses any `<img>`{lang=\"html\"} tags within the `<main>`{lang=\"html\"} tag. Opt-out by disabling `discoverImages`.\n\n- `videos` - Adds video entries `<video:video>`{lang=\"xml\"}.\n\nPasses any `<video>`{lang=\"html\"} tags within the `<main>`{lang=\"html\"} tag. Opt-out by disabling `discoverVideos`.\n\n- `lastmod` - Adds lastmod date `<lastmod>`{lang=\"xml\"}.\n\nUses the [opengraph](https://ogp.me) `article:modified_time` and `article:published_time` meta tag.\n\n## Enabling Nuxt Prerendering\n\nYou will need to use configuration to enable this feature.\n\n```ts\nexport default defineNuxtConfig({\n  nitro: {\n    prerender: {\n      // enabled by default with nuxt generate, not required\n      crawlLinks: true,\n      // add any routes to prerender\n      routes: ['/']\n    }\n  }\n})\n```\n\nYou can also use route rules to enable prerendering for specific routes.\n\n```ts\nexport default defineNuxtConfig({\n  routeRules: {\n    '/': { prerender: true }\n  }\n})\n```\n\n### Prerendering the Sitemap on Build\n\nIf you're using `nuxi build` and want to prerender the sitemap on build, you can add the sitemap path to the `nitro.prerender.routes` option.\n\n```ts\nexport default defineNuxtConfig({\n  nitro: {\n    prerender: {\n      routes: ['/sitemap.xml']\n    }\n  }\n})\n```\n\n### Customizing the prerender data\n\nIf needed, you can customize the prerender data by using the Nitro hooks.\n\nHere is a simple recipe that will extract YouTube video iframes and add them to the sitemap.\n\n```ts\nimport type { ResolvedSitemapUrl } from '#sitemap/types'\n\nexport default defineNuxtConfig({\n  modules: [\n    // run this before the sitemap module\n    (_, nuxt) => {\n      nuxt.hooks.hook('nitro:init', async (nitro) => {\n        nitro.hooks.hook('prerender:generate', async (route) => {\n          const html = route.contents\n          // check for youtube video iframes and append to the videos array\n          const matches = html.match(/<iframe.*?youtube.com\\/embed\\/(.*?)\".*?<\\/iframe>/g)\n          if (matches) {\n            const sitemap = route._sitemap || {} as ResolvedSitemapUrl\n            sitemap.videos = sitemap.videos || []\n            for (const match of matches) {\n              const videoId = match.match(/youtube.com\\/embed\\/(.*?)\" /)[1]\n              sitemap.videos.push({\n                title: 'YouTube Video',\n                description: 'A video from YouTube',\n                content_loc: `https://www.youtube.com/watch?v=${videoId}`,\n                thumbnail_loc: `https://img.youtube.com/vi/${videoId}/0.jpg`,\n              })\n            }\n            // the sitemap module should be able to pick this up\n            route._sitemap = sitemap\n          }\n        })\n      })\n    },\n  ],\n})\n```\n"
  },
  {
    "path": "docs/content/1.guides/6.best-practices.md",
    "content": "---\ntitle: Sitemap.xml Best Practices\ndescription: The best practices for generating a sitemap.xml file.\nnavigation:\n  title: Best Practices\nrelatedPages:\n  - path: /docs/sitemap/advanced/loc-data\n    title: Lastmod, Priority, and Changefreq\n  - path: /docs/sitemap/guides/submitting-sitemap\n    title: Submitting Your Sitemap\n  - path: /learn/controlling-crawlers\n    title: Controlling Web Crawlers\n---\n\n## Set appropriate lastmod\n\nThe `lastmod` field is used to indicate when a page was last updated. This is used by search engines to determine how often to crawl your site.\n\nThis should not change based on code changes, only for updating the content.\n\nFor example, if you have a blog post, the `lastmod` should be updated when the content of the blog post changes.\n\n::callout{icon=\"i-heroicons-exclamation-triangle\" color=\"amber\"}\n**Accuracy is Critical**\n\nGoogle has stated that they may **stop trusting** your `lastmod` dates if they are consistently updated without significant content changes. Ensure your `lastmod` logic is precise.\n::\n\nIt's recommended not to use `autoLastmod: true` as this will use the last time the page was built, which does\nnot always reflect content updates.\n\nLearn more in [Google's sitemap lastmod documentation](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping).\n\n## You probably don't need `changefreq` or `priority`\n\nThese two fields are not used by search engines, and are only used by crawlers to determine how often to crawl your site.\n\nIf you're trying to get your site crawled more often, you should use the `lastmod` field instead.\n\nLearn more in [Google's sitemap best practices](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping).\n\n## Use Zero Runtime when content only changes on deploy\n\nIf your pages only change when you commit and deploy (not at runtime), you don't need runtime sitemap generation. Enable `zeroRuntime` to generate sitemaps at build time and remove ~50KB of sitemap code from your server bundle.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    zeroRuntime: true\n  }\n})\n```\n\nThis is ideal for sites using `nuxt build` where content is static between deployments. If you're using a CMS that updates content without redeploying, you'll need runtime generation.\n\nLearn more in the [Zero Runtime](/docs/sitemap/guides/zero-runtime) guide.\n\n::callout{icon=\"i-heroicons-check-circle\" to=\"/tools/xml-sitemap-validator\"}\n**Check your sitemap** - Validate your sitemap meets Google requirements with our [XML Sitemap Validator](/tools/xml-sitemap-validator).\n::\n\n::checklist{id=\"sitemap-best-practices\" title=\"Quick Checklist\"}\n- Set meaningful lastmod dates based on content changes\n- Skip changefreq and priority (ignored by search engines)\n- Enable zeroRuntime for static sites\n- Submit sitemap to Google Search Console\n::\n"
  },
  {
    "path": "docs/content/1.guides/7.submitting-sitemap.md",
    "content": "---\ntitle: 'Submitting Your Sitemap'\ndescription: 'How to submit your sitemap to Google Search Console to start getting indexed.'\nrelatedPages:\n  - path: /docs/sitemap/getting-started/troubleshooting\n    title: Troubleshooting\n  - path: /docs/sitemap/guides/best-practices\n    title: Best Practices\n  - path: /learn/controlling-crawlers\n    title: Controlling Web Crawlers\n---\n\n## Introduction\n\nWhen going live with a new site and you're looking to get indexed by Google, the best starting point is\nto submit your sitemap to Google Search Console.\n\n> Google Search Console is a free service offered by Google that helps you monitor, maintain, and troubleshoot\nyour site's presence in Google Search results.\n\n::callout{icon=\"i-heroicons-shield-check\" to=\"/tools/xml-sitemap-validator\"}\n**Validate before submitting** - Use our [XML Sitemap Validator](/tools/xml-sitemap-validator) to check for errors before submitting to Google Search Console.\n::\n\n## Submitting Sitemap\n\nGoogle provides a guide on [Submitting your Sitemap to Google](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) which is a great starting point.\n\nYou should index either `/sitemap.xml` or if you're using multiple sitemaps, add `/sitemap_index.xml`.\n\n### Deprecation of Sitemap Pinging\n\nIn January 2024, Google [deprecated the \"ping\" endpoint](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping) (e.g., `http://www.google.com/ping?sitemap=...`).\n\nYou should no longer use this method to notify Google of updates. Instead, rely on:\n1.  **robots.txt reference**: Ensure your `robots.txt` contains the `Sitemap: ...` line (Nuxt Sitemap does this automatically).\n2.  **Google Search Console**: Submit the sitemap once, and Google will crawl it periodically.\n3.  **lastmod**: Keep your `lastmod` dates accurate so Google knows when to recrawl specific URLs.\n\n## Requesting Indexing\n\nIt's important to know that submitting your sitemap does not guarantee that all your pages will be indexed and that it may take\nsome time for Google to crawl and index your pages.\n\nTo speed up the process, you can use the [URL Inspection Tool](https://support.google.com/webmasters/answer/9012289) to request indexing of a specific URL.\n\nIn some cases you may want to expedite the indexing process, for this, you can try out my free open-source tool [Request Indexing](https://requestindexing.com).\n\n## Sitemap Error\n\nWhen submitting a sitemap for the first time you may get see \"Error\". This is because Google previously\ncrawled your site for a sitemap and found nothing.\n\nWhen encountering this it's best to wait a few days and see if the error resolves itself. If not, you can\ntry resubmitting the sitemap or making a [GitHub Issue](https://github.com/nuxt-modules/sitemap).\n"
  },
  {
    "path": "docs/content/1.guides/8.zero-runtime.md",
    "content": "---\ntitle: Zero Runtime\ndescription: Generate sitemaps at build time without runtime overhead.\n---\n\nIf your sitemap URLs only change when you deploy, you don't need to ship sitemap generation code to production. The `zeroRuntime` option generates sitemaps at build time and tree-shakes the generation code from your server bundle.\n\n## Usage\n\nTo enable zero runtime, add the following to your config:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    zeroRuntime: true\n  }\n})\n```\n\nWhen enabled, the module will automatically add `/sitemap.xml` to your prerender routes. The sitemap will be generated during build and served as a static file at runtime.\n\n## How it Works\n\nWith `zeroRuntime: true`:\n\n1. Sitemap routes are automatically added to `nitro.prerender.routes`\n2. Server handlers use dynamic imports gated by `import.meta.prerender`\n3. At build time, the sitemap generation code is tree-shaken from the runtime bundle\n4. Static XML files are served directly without any sitemap code execution\n\n## Development Mode\n\nZero runtime mode still works in development (`nuxt dev`). The sitemap generation code runs normally during development so you can test your configuration.\n\n## Benchmarks\n\nEnabling `zeroRuntime` reduces the server bundle by approximately:\n\n- **~50KB** uncompressed\n- **~5KB** gzip\n\nThis is the sitemap generation code (XML building, URL normalization, source fetching) being tree-shaken from the bundle.\n\n## Limitations\n\n- Runtime sitemap generation is not available - sitemaps are only generated during build\n- Dynamic data sources that require runtime fetching won't work\n- Debug endpoints are disabled in zero runtime mode\n\n## When to Use\n\nZero runtime is ideal when:\n\n- Your pages only change when you commit and deploy\n- You're using `nuxt generate` for a fully static site\n- You want to minimize your server bundle size for edge/serverless\n\n## When Not to Use\n\nAvoid zero runtime when:\n\n- Your CMS updates content without redeploying\n- You have user-generated content that changes frequently\n- Your sitemap URLs depend on runtime data\n"
  },
  {
    "path": "docs/content/2.advanced/0.loc-data.md",
    "content": "---\ntitle: Lastmod, Priority, and Changefreq\ndescription: Configure lastmod, priority, and changefreq values for your sitemap entries.\nrelatedPages:\n  - path: /docs/sitemap/guides/best-practices\n    title: Best Practices\n  - path: /docs/sitemap/guides/dynamic-urls\n    title: Dynamic URL Endpoints\n  - path: /docs/sitemap/guides/prerendering\n    title: Nuxt Prerendering\n---\n\n## Introduction\n\nChanging the `<loc>`{lang=\"xml\"} entry data can be useful for a variety of reasons, such as changing the `changefreq`, `priority`, or `lastmod` values.\n\nIf you're using [Dynamic URLs](/docs/sitemap/guides/dynamic-urls), you can modify the data in the `sitemap` object, otherwise, you will\nneed to override the [app sources](/docs/sitemap/getting-started/data-sources) directly.\n\nWhile modifying these in most cases may be unnecessary, see [Best Practices](/docs/sitemap/guides/best-practices), it can be useful when used right.\n\n## Setting Defaults\n\nWhile this is not recommended, in special circumstances you may wish to set defaults for your sitemap entries:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    defaults: {\n      lastmod: new Date().toISOString(),\n      priority: 0.5,\n      changefreq: 'weekly'\n    }\n  }\n})\n```\n\n## Data Source Merging\n\nYou can provide the page you want to set the `lastmod`, `priority`, or `changefreq` for in your app sources, which includes\nthe `urls` config.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    urls: [\n      {\n        loc: '/about',\n        lastmod: '2023-01-01',\n        priority: 0.3,\n        changefreq: 'daily'\n      }\n    ]\n  }\n})\n```\n\n## Modify Loc Data With Route Rules\n\nTo change the behaviour of your sitemap URLs, you can use [Route rules](https://nuxt.com/docs/api/configuration/nuxt-config/#routerules).\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  routeRules: {\n    // Don't add any /secret/** URLs to the sitemap.xml\n    '/secret/**': { robots: false },\n    // modify the sitemap.xml entry for specific URLs\n    '/about': { sitemap: { changefreq: 'daily', priority: 0.3 } }\n  }\n})\n```\n\nAlternatively, you can use the experimental macro [`defineRouteRules()`{lang=\"ts\"}](https://nuxt.com/docs/api/utils/define-route-rules), which must\nbe enabled.\n\n```vue [pages/index.vue]\n<script setup>\ndefineRouteRules({\n  sitemap: {\n    changefreq: 'daily',\n    priority: 0.3\n  }\n})\n</script>\n```\n\n## Modify Loc Data With Page Meta\n\nYou can configure sitemap entry data directly in your page components using [`definePageMeta()`{lang=\"ts\"}](https://nuxt.com/docs/api/utils/define-page-meta).\n\n```vue [pages/about.vue]\n<script setup>\ndefinePageMeta({\n  sitemap: {\n    changefreq: 'daily',\n    priority: 0.8\n  }\n})\n</script>\n```\n\nTo exclude a page from the sitemap entirely, set `sitemap` to `false`:\n\n```vue [pages/secret.vue]\n<script setup>\ndefinePageMeta({\n  sitemap: false\n})\n</script>\n```\n\nThe `sitemap` key is extracted at build time via Nuxt's `scanPageMeta`, so these values are available without runtime overhead.\n\n## Dynamic lastmod from APIs\n\nWhen fetching dynamic URLs, include lastmod from your data source:\n\n```ts [server/api/__sitemap__/urls.ts]\nimport { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(async () => {\n  const posts = await $fetch('https://api.example.com/posts')\n  return posts.map(post => ({\n    loc: `/blog/${post.slug}`,\n    lastmod: post.updated_at,\n  }))\n})\n```\n\nWith Nuxt Content, set `lastmod` in frontmatter:\n\n```md\n---\nsitemap:\n  lastmod: 2024-01-15\n  changefreq: weekly\n---\n```\n\nThe `lastmod` field accepts `Date` objects, ISO 8601 strings (`'2024-01-15T10:30:00Z'`), or simple date strings (`'2024-01-15'`). Only include it if you have accurate update timestamps — avoid using current date/time for all URLs.\n\n## Lastmod: Prerendering Hints\n\nWhen prerendering your site, you can make use of setting the `article:modified_time` meta tag in your page's head. This\nmeta tag will be used as the `lastmod` value in your sitemap.\n\n```vue [pages/index.vue]\n<script setup>\nuseSeoMeta({\n  // will be inferred as the lastmod value in the sitemap\n  articleModifiedTime: '2023-01-01'\n})\n</script>\n```\n"
  },
  {
    "path": "docs/content/2.advanced/1.images-videos.md",
    "content": "---\ntitle: Images, Videos, News\ndescription: Learn how to add images, videos and news in your sitemap.\nrelatedPages:\n  - path: /docs/sitemap/guides/prerendering\n    title: Nuxt Prerendering\n  - path: /docs/sitemap/guides/best-practices\n    title: Best Practices\n---\n\n## Introduction\n\nThe `image`, `video` and `news` namespaces is added to your sitemap by default, allowing you to configure\nimages, videos and news for your sitemap entries.\n\nWhen prerendering your app, it's possible for the generated sitemap to automatically infer images and videos from your pages.\n\n## Sitemap Images\n\nTo add images to your sitemap, you can use the `images` property on the sitemap entry.\n\nYou can learn more about images in sitemaps on the [Google documentation](https://developers.google.com/search/docs/advanced/sitemaps/image-sitemaps).\n\n```ts\nexport interface ImageEntry {\n  loc: string | URL\n  caption?: string\n  geo_location?: string\n  title?: string\n  license?: string | URL\n}\n```\n\nYou can implement this as follows:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    urls: [\n      {\n        loc: '/blog/my-post',\n        images: [\n          {\n            loc: 'https://example.com/image.jpg',\n            caption: 'My image caption',\n            geo_location: 'My image geo location',\n            title: 'My image title',\n            license: 'My image license',\n          }\n        ]\n      }\n    ]\n  }\n})\n```\n\n### Automatic Image Discovery\n\nThe module can discover images in your page and add them to your sitemap automatically.\n\nFor this to work:\n\n- The page _must_ be prerendered. These images will not be shown in development or if the page is not prerendered.\n- You must wrap your page content with a `<main>`{lang=\"html\"} tag, avoid wrapping shared layouts that include duplicate images.\n\n## Videos\n\nTo add videos to your sitemap, you can use the `videos` property on the sitemap entry.\n\n::callout{icon=\"i-heroicons-exclamation-triangle\" color=\"amber\"}\n**Google Video Indexing Change**\n\nAs of late 2023, Google **only indexes videos if they are the main content of the page**. If a video is supplementary to the text (like a blog post with a video at the bottom), the video itself may not be indexed in video search results, though the page will still be indexed normally.\n::\n\nThe TypeScript interface for videos is as follows:\n\n```ts\nexport interface VideoEntry {\n  title: string\n  thumbnail_loc: string | URL\n  description: string\n  content_loc?: string | URL\n  player_loc?: string | URL\n  duration?: number\n  expiration_date?: Date | string\n  rating?: number\n  view_count?: number\n  publication_date?: Date | string\n  family_friendly?: 'yes' | 'no' | boolean\n  restriction?: Restriction\n  platform?: Platform\n  price?: ({\n    price?: number | string\n    currency?: string\n    type?: 'rent' | 'purchase' | 'package' | 'subscription'\n  })[]\n  requires_subscription?: 'yes' | 'no' | boolean\n  uploader?: {\n    uploader: string\n    info?: string | URL\n  }\n  live?: 'yes' | 'no' | boolean\n  tag?: string | string[]\n  category?: string\n  gallery_loc?: string | URL\n}\n```\n\nYou can learn more about videos in sitemaps on the [Google documentation](https://developers.google.com/search/docs/advanced/sitemaps/video-sitemaps).\n\nYou can implement this as follows:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    urls: [\n      {\n        loc: '/blog/my-post',\n        videos: [\n          {\n            title: 'My video title',\n            thumbnail_loc: 'https://example.com/video.jpg',\n            description: 'My video description',\n            content_loc: 'https://example.com/video.mp4',\n            player_loc: 'https://example.com/video.mp4',\n            duration: 600,\n            expiration_date: '2021-01-01',\n            rating: 4.2,\n            view_count: 1000,\n            publication_date: '2021-01-01',\n            family_friendly: true,\n            restriction: {\n              relationship: 'allow',\n              restriction: 'US CA',\n            },\n            platform: {\n              relationship: 'allow',\n              platform: 'web',\n            },\n            price: [\n              {\n                price: 1.99,\n                currency: 'USD',\n                type: 'rent',\n              }\n            ],\n            requires_subscription: true,\n            uploader: {\n              uploader: 'My video uploader',\n              info: 'https://example.com/uploader',\n            },\n            live: true,\n            tag: ['tag1', 'tag2'],\n          }\n        ]\n      }\n    ]\n  }\n})\n```\n\n### Automatic Video Discovery\n\nLike automatic image discovery, you can opt-in to automatic video discovery including video markup in your `<main>`{lang=\"html\"} tag.\n\nYou are also required to provide a title and description for your video, this can be done using the `data-title` and `data-description` attributes.\n\n```html [Simple]\n<video\n    controls\n    poster=\"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\"\n    width=\"620\"\n    data-title=\"Duck and Cover\"\n    data-description=\"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\"\n>\n    <source\n        src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\"\n        type=\"video/mp4\"\n    />\n    <source\n        src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi\"\n        type=\"video/x-msvideo\"\n    />\n    Sorry, your browser doesn't support embedded videos. However, you can\n    <a href=\"https://archive.org/details/DuckAndCover_185\">download it</a>\n    and watch it with your favorite video player!\n</video>\n```\n\n```html [Full]\n<video\n    controls\n    poster=\"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\"\n    width=\"620\"\n    data-title=\"Duck and Cover\"\n    data-description=\"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\"\n    data-rating=\"4.2\"\n    data-view-count=\"1000\"\n    data-publication-date=\"2021-01-01\"\n    data-family-friendly=\"yes\"\n\n>\n```\n\nEach format would be added to your sitemap in the following format:\n\n```xml\n<video:video>\n    <video:thumbnail_loc>https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg</video:thumbnail_loc>\n    <video:title>Duck and Cover</video:title>\n    <video:description>\n        This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\n    </video:description>\n    <video:content_loc>\n        https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\n    </video:content_loc>\n</video:video>\n```\n\n## News\n\nTo add news to your sitemap, you can use the `news` property on the sitemap entry. Only [Google's News sitemap](https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap) extension is supported.\n\nThe TypeScript interface for news is as follows:\n\n```ts\nexport interface GoogleNewsEntry {\n  title: string\n  publication_date: Date | string\n  publication: {\n    name: string\n    language: string\n  }\n}\n```\n\nYou can implement this as follows:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    urls: [\n      {\n        loc: '/news/nuxt-sitemap-turns-6',\n        news: {\n          title: 'Nuxt Sitemap Turns 6',\n          publication_date: '2021-01-01',\n          publication: {\n            name: 'Nuxt Sitemap',\n            language: 'en',\n          },\n        }\n      }\n    ]\n  }\n})\n```\n\n## Image & Video Opt-out\n\nTo opt-out of this behaviour, you can set the `discoverImages` and `discoverVideos` config to `false` respectively.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    discoverImages: false,\n    discoverVideos: false,\n  }\n})\n```\n\n"
  },
  {
    "path": "docs/content/2.advanced/2.performance.md",
    "content": "---\ntitle: Sitemap Performance\ndescription: Use the default cache engine to keep your sitemaps fast.\nrelatedPages:\n  - path: /docs/sitemap/guides/multi-sitemaps\n    title: Multi Sitemaps\n  - path: /docs/sitemap/advanced/chunking-sources\n    title: Sitemap Chunking\n  - path: /docs/site-config/getting-started/installation\n    title: Nuxt Site Config\n---\n\n## Introduction\n\nFor apps with 100k+ pages, generating a sitemap can be a slow process. As robots will request your sitemap frequently, it's important to keep it fast.\n\nNuxt SEO provides a default cache engine to keep your sitemaps fast and recommendations on how to improve performance.\n\n## Performance Recommendations\n\nWhen dealing with many URLs that are being generated from an external API, the best option is use the `sitemaps`\noption to create [Named Sitemap Chunks](/docs/sitemap/guides/multi-sitemaps).\n\nEach sitemap should contain its own `sources`, this allows other sitemaps to be generated without waiting for this request.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts: {\n        sources: [\n          'https://api.something.com/urls'\n        ]\n      },\n    },\n  },\n})\n```\n\nIf you need to split this up further, you should consider chunking by the type and some pagination format. For example,\nyou can paginate by when posts were created.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts2020: {\n        sources: [\n          'https://api.something.com/urls?filter[yearCreated]=2020'\n        ]\n      },\n      posts2021: {\n        sources: [\n          'https://api.something.com/urls?filter[yearCreated]=2021'\n        ]\n      },\n    },\n  },\n})\n```\n\nAdditionally, you may want to consider the following experimental options that may help with performance:\n- `experimentalCompression` - Gzip's and streams the sitemap\n- `experimentalWarmUp` - Creates the sitemaps when Nitro starts\n\n### Very large sites (100k+ URLs)\n\nFor sites at this scale, two practices matter most:\n\n1. **Cache the source endpoint.** Use `defineCachedEventHandler` on any `/api/*` route fed into `sources`. Without this, every cache miss (and every fresh chunk) re-hits your backend.\n\n2. **Set generous chunk sizes.** Search engines accept up to 50,000 URLs per file. The default `defaultSitemapsChunkSize` of 1000 generates 50× more chunks than necessary; bumping to `5000`–`50000` directly reduces total work and cache entries.\n\nWithin a single sitemap, all chunks share one resolved-URLs computation (sources are fetched, normalised, and sorted once per `cacheMaxAgeSeconds` window — not once per chunk). Splitting one large sitemap into per-shard sitemaps (e.g. one per locale or content type) is still useful when shards have different cache lifetimes or different sources.\n\n## Zero Runtime Mode\n\nIf your sitemap URLs only change when you deploy (not at runtime), you can enable `zeroRuntime` to generate sitemaps at build time and eliminate sitemap generation code from your server bundle.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    zeroRuntime: true\n  }\n})\n```\n\nThis reduces server bundle size by ~50KB. The sitemap is generated once at build time and served as a static file.\n\nSee the [Zero Runtime](/docs/sitemap/guides/zero-runtime) guide for details.\n\n## Sitemap Caching\n\nCaching your sitemap can help reduce the load on your server and improve performance.\n\nBy default, SWR caching is enabled on production environments and sitemaps will be cached for 10 minutes.\n\nThis is configured by overriding your route rules and leveraging the native Nuxt caching.\n\n### Cache Time\n\nYou can change the cache time by setting the `cacheMaxAgeSeconds` option. This affects the `Cache-Control` header sent to browsers and search engines.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    cacheMaxAgeSeconds: 3600 // 1 hour\n  }\n})\n```\n\nIf you want to disable caching, set `cacheMaxAgeSeconds` to `false` or `0`.\n\n`cacheMaxAgeSeconds` controls both the HTTP `Cache-Control` header and the server-side SWR cache TTL. For high-volume sites, raising it to several hours significantly reduces origin load.\n\n### Cache Driver\n\nThe cache engine is set to the Nitro default of the `cache/` path.\n\nIf you want to customise the cache engine, you can set the `runtimeCacheStorage` option.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    // cloudflare kv binding example\n    runtimeCacheStorage: {\n      driver: 'cloudflare-kv-binding',\n      binding: 'OG_IMAGE_CACHE'\n    }\n  }\n})\n```\n"
  },
  {
    "path": "docs/content/2.advanced/3.chunking-sources.md",
    "content": "---\ntitle: Sitemap Chunking\ndescription: Split large sitemap sources into multiple files for performance and search engine limits.\nrelatedPages:\n  - path: /docs/sitemap/guides/multi-sitemaps\n    title: Multi Sitemaps\n  - path: /docs/sitemap/advanced/performance\n    title: Sitemap Performance\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n---\n\n## Introduction\n\nWhen dealing with large datasets, sitemap sources can be chunked into multiple files to:\n- Stay within search engine limits (50MB file size, 50,000 URLs)\n- Improve generation performance\n- Better manage memory usage\n\n## Simple Configuration\n\nEnable chunking on any named sitemap with sources:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts: {\n        sources: ['/api/posts'],\n        chunks: true, // Uses default size of 1000\n      }\n    }\n  }\n})\n```\n\nThis generates:\n```\n/sitemap_index.xml    # Master index\n/posts-0.xml          # First chunk (1-1000)\n/posts-1.xml          # Second chunk (1001-2000)\n...\n```\n\n## Chunk Size Options\n\nConfigure chunk sizes using different approaches:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    // Global default\n    defaultSitemapsChunkSize: 5000,\n\n    sitemaps: {\n      // Using boolean (applies default)\n      posts: {\n        sources: ['/api/posts'],\n        chunks: true,\n      },\n\n      // Using number as size\n      products: {\n        sources: ['/api/products'],\n        chunks: 10000,\n      },\n\n      // Using explicit chunkSize (highest priority)\n      articles: {\n        sources: ['/api/articles'],\n        chunks: true,\n        chunkSize: 2000,\n      }\n    }\n  }\n})\n```\n\n## Skipping the index source fetch (`chunkCount`)\n\nBy default the sitemap index calls your source to count URLs, so it knows how many `<sitemap>` entries to emit. At very large scale this cold-start fetch is the bottleneck. If you already know the number of chunks, declare it upfront and the index will skip the fetch entirely:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts: {\n        sources: ['/api/posts'],\n        chunks: 5000,\n        chunkCount: 100, // 100 chunk entries, no source fetch in the index\n      },\n    },\n  },\n})\n```\n\nPer-chunk renders still fetch on demand and slice. If your data set grows past the declared count, tail entries are unreachable; if it shrinks, trailing chunks render empty. Update the value when your data set changes (or remove it to fall back to fetching).\n\n## Practical Examples\n\n### E-commerce Site\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    defaultSitemapsChunkSize: 10000,\n    sitemaps: {\n      products: {\n        sources: ['/api/products/all'],\n        chunks: 2000,\n      },\n      categories: {\n        sources: ['/api/categories'],\n        chunks: true, // Uses default 10k\n      }\n    }\n  }\n})\n```\n\n### Large Content Site\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      'blog-posts': {\n        sources: ['/api/blog/posts'],\n        chunks: 5000,\n      },\n      'authors': {\n        sources: ['/api/authors'],\n        chunks: false, // Explicitly disable\n      }\n    }\n  }\n})\n```\n\n## Source Implementation\n\nBasic endpoint for sitemap sources:\n\n```ts [server/api/products/all.ts]\nexport default defineEventHandler(async () => {\n  const products = await db.products.findAll({\n    select: ['id', 'slug', 'updatedAt']\n  })\n\n  return products.map(product => ({\n    loc: `/products/${product.slug}`,\n    lastmod: product.updatedAt\n  }))\n})\n```\n\nFor large datasets, use caching and streaming:\n\n```ts [server/api/products/all.ts]\nexport default defineCachedEventHandler(async () => {\n  const products = []\n  const cursor = db.products.cursor({\n    select: ['slug', 'updatedAt']\n  })\n\n  for await (const product of cursor) {\n    products.push({\n      loc: `/products/${product.slug}`,\n      lastmod: product.updatedAt\n    })\n  }\n\n  return products\n}, {\n  maxAge: 60 * 60, // 1 hour cache\n  name: 'sitemap-products'\n})\n```\n\n## Debugging\n\nCheck chunk configuration and performance:\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  sitemap: {\n    debug: true,\n    sitemaps: {\n      products: {\n        sources: ['/api/products'],\n        chunks: 5000\n      }\n    }\n  }\n})\n```\n\nVisit `/__sitemap__/debug.json` to see chunk details and generation metrics.\n"
  },
  {
    "path": "docs/content/2.advanced/4.customising-ui.md",
    "content": "---\ntitle: Customising the UI\ndescription: Change the look and feel of your sitemap.\nrelatedPages:\n  - path: /docs/sitemap/guides/i18n\n    title: I18n Integration\n  - path: /docs/sitemap/api/config\n    title: Config Reference\n---\n\n## Disabling the XSL\n\nWhat you're looking at when you view the sitemap.xml is a XSL file, think of it just like you would a CSS file for HTML.\n\nTo view the real sitemap.xml, you can view the source of the page.\nIf you prefer, you can disable the XSL by setting `xsl` to `false`.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    xsl: false\n  }\n})\n```\n\n## Changing the columns\n\nYou can change the columns that are displayed in the sitemap by modifying the `xslColumns` option.\n\nThese have no effect on SEO and is purely for developer experience.\n\nNote: You must always have a `URL` column at the start.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    xslColumns: [\n      // URL column must always be set, no value needed\n      { label: 'URL', width: '75%' },\n      { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },\n    ],\n  },\n})\n```\n\nThe `select` you provide is an XSL expression that will be evaluated against the sitemap entry.\nIt's recommended to prefix the value with `sitemap:` if in doubt.\n\n### Example: Adding priority and changefreq\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    xslColumns: [\n      { label: 'URL', width: '50%' },\n      { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },\n      { label: 'Priority', select: 'sitemap:priority', width: '12.5%' },\n      { label: 'Change Frequency', select: 'sitemap:changefreq', width: '12.5%' },\n    ],\n  },\n})\n```\n\n### Example: Adding `hreflang`\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    xslColumns: [\n      { label: 'URL', width: '50%' },\n      { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },\n      { label: 'Hreflangs', select: 'count(xhtml:link)', width: '25%' },\n    ],\n  },\n})\n```\n\n## Disabling tips\n\nIn development tips are displayed on the sitemap page to help you get started.\n\nYou can disable these tips by setting the `xslTips` option to `false`.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    xslTips: false,\n  },\n})\n```\n"
  },
  {
    "path": "docs/content/4.api/0.config.md",
    "content": "---\ntitle: Config\ndescription: Configure the sitemap module.\n---\n\n## `enabled`\n\n- Type: `boolean`{lang=\"ts\"}\n- Default: `true`{lang=\"ts\"}\n\nWhether to generate the sitemap.\n\n## `sortEntries`\n\n- Type: `boolean`{lang=\"ts\"}\n- Default: `true`{lang=\"ts\"}\n\nWhether the sitemap entries should be sorted or be shown in the order they were added.\n\nWhen enabled the entries will be sorted by the `loc`, they will be sorted by the path segment\ncount and then alphabetically using `String.localeCompare` to ensure numbers are sorted correctly.\n\n## `sources`\n\n- Type: `SitemapSource[]`{lang=\"ts\"}\n- Default: `[]`{lang=\"ts\"}\n\nThe sources to use for the sitemap. See [Data Sources](/docs/sitemap/getting-started/data-sources) and [Dynamic URL Endpoint](/docs/sitemap/guides/dynamic-urls) for details.\n\n## `excludeAppSources`\n\n- Type: `true | AppSourceContext[]`{lang=\"ts\"}\n- Default: `[]`{lang=\"ts\"}\n\nWhether to exclude [app sources](/docs/sitemap/getting-started/data-sources) from the sitemap.\n\n## `appendSitemaps`\n\n- Type: `(string | { sitemap: string, lastmod?: string })[]`{lang=\"ts\"}\n- Default: `undefined`{lang=\"ts\"}\n\nSitemaps to append to the sitemap index.\n\nThis will only do anything when using multiple sitemaps.\n\n## `autoLastmod`\n\n- Type: `boolean`{lang=\"ts\"}\n- Default: `false`{lang=\"ts\"}\n\nSets the current date as the default `lastmod` for all entries that don't already have one.\n\n## `sitemaps`\n\n- Type: `boolean | Record<string, SitemapConfig> & { index?: (string | SitemapIndexEntry)[] }`\n- Default: `false`\n\nWhether to generate multiple sitemaps.\n\nEach sitemap can have the following options:\n\n### SitemapConfig\n\n#### `sources`\n- Type: `SitemapSource[]`\n- Default: `[]`\n\nData sources for this specific sitemap.\n\n#### `chunks`\n- Type: `boolean | number`\n- Default: `undefined`\n\nEnable chunking for sitemap sources. This splits large collections of URLs from sources into multiple smaller sitemap files to stay within search engine limits.\n\n- Set to `true` to enable chunking with the default chunk size (from `defaultSitemapsChunkSize` or 1000)\n- Set to a positive number to use that as the chunk size (e.g., `5000` for 5000 URLs per chunk)\n- Set to `false` or leave undefined to disable chunking\n\nNote: Chunking only applies to URLs from `sources`. Direct URLs in the `urls` property are not chunked.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      products: {\n        sources: ['/api/products'],\n        chunks: 5000 // Split into files with 5000 URLs each\n      }\n    }\n  }\n})\n```\n\n#### `chunkSize`\n- Type: `number`\n- Default: `undefined`\n\nExplicitly set the chunk size for this sitemap. Takes precedence over the `chunks` property when both are specified.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      posts: {\n        sources: ['/api/posts'],\n        chunks: true, // Enable chunking\n        chunkSize: 2500 // Use 2500 URLs per chunk\n      }\n    }\n  }\n})\n```\n\nSee the [Chunking Sources](/docs/sitemap/advanced/chunking-sources) guide for more details.\n\n#### `urls`\n- Type: `SitemapUrlInput[] | (() => SitemapUrlInput[] | Promise<SitemapUrlInput[]>)`\n- Default: `[]`\n\nURLs to include in this sitemap. Can be strings or sitemap URL objects.\n\n#### `include`\n- Type: `(string | RegExp | { regex: string })[]`\n- Default: `undefined`\n\nFilter URLs to include in this sitemap.\n\n#### `exclude`\n- Type: `(string | RegExp | { regex: string })[]`\n- Default: `undefined`\n\nFilter URLs to exclude from this sitemap.\n\n#### `defaults`\n- Type: `SitemapItemDefaults`\n- Default: `{}`\n\nDefault values for all URLs in this sitemap.\n\n#### `includeAppSources`\n- Type: `boolean`\n- Default: `false`\n\nWhether to include automatic app sources in this sitemap.\n\nSee [Multi Sitemaps](/docs/sitemap/guides/multi-sitemaps) for details.\n\n## `defaultSitemapsChunkSize`\n\n- Type: `number | false`\n- Default: `1000`\n\nThe default chunk size when chunking is enabled for multi-sitemaps. This value is used when:\n- A sitemap has `chunks: true` (without specifying a number)\n- No `chunkSize` is explicitly set for the sitemap\n\nSet to `false` to disable chunking by default for all sitemaps.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    defaultSitemapsChunkSize: 5000,\n    sitemaps: {\n      // These will use 5000 as chunk size\n      posts: {\n        sources: ['/api/posts'],\n        chunks: true\n      },\n      // This overrides the default\n      products: {\n        sources: ['/api/products'],\n        chunks: 10000\n      }\n    }\n  }\n})\n```\n\n## `defaults`\n\n- Type: `object`\n- Default: `{}`\n\nDefault values for the sitemap.xml entries. See [sitemaps.org](https://www.sitemaps.org/protocol.html) for all available options.\n\n## `urls`\n\n- Type: `MaybeFunction<MaybePromise<SitemapUrlInput[]>>`\n- Default: `[]`\n\nProvide custom URLs to be included in the sitemap.xml.\n\n## `include`\n\n- Type: `(string | RegExp | { regex: string })[]`\n- Default: `[]`\n\nFilter routes that match the given rules. If empty, all routes are included. See the [Filtering URLs](/docs/sitemap/guides/filtering-urls) guide for details.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    include: [\n      '/my-hidden-url'\n    ]\n  }\n})\n```\n\n## `exclude`\n\n- Type: `(string | RegExp | { regex: string })[]`\n- Default: `['/_**']`\n\nFilter routes that match the given rules. The pattern `/_nuxt/**` is always added dynamically from `app.buildAssetsDir`, and `/__nuxt_content/**` is added when Nuxt Content v3 is detected. See the [Filtering URLs](/docs/sitemap/guides/filtering-urls) guide for details.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    exclude: [\n      '/my-secret-section/**'\n    ]\n  }\n})\n```\n\n## `xsl`\n\n- Type: `string | false`\n- Default: `/__sitemap__/style.xsl`\n\nThe path to the XSL stylesheet for the sitemap.xml. Set to `false` to disable.\n\n## `discoverImages`\n\n- Type: `boolean`\n- Default: `true`\n\nWhether to discover images from routes when prerendering.\n\n## `discoverVideos`\n\n- Type: `boolean`\n- Default: `true`\n\nWhether to discover videos from routes when prerendering.\n\n## `autoI18n`\n\n- Type: `undefined | boolean | { locales: NormalisedLocales; defaultLocale: string; strategy: 'prefix' | 'prefix_except_default' | 'prefix_and_default' | 'no_prefix'; differentDomains?: boolean; pages?: Record<string, Record<string, string | false>> }`\n- Default: `undefined`\n\nAutomatically add alternative language prefixes for each entry with the given prefixes. Set to `false` to disable.\n\nWhen using the @nuxtjs/i18n module, this will automatically be set to the configured `locales` when left `undefined`.\n\n## `sitemapName`\n\n- Type: `string`\n- Default: `sitemap.xml`\n\nModify the name of the root sitemap.\n\nNote: This only works when you're not using the multiple `sitemaps` option.\n\n## `strictNuxtContentPaths`\n\n- Type: `boolean`\n- Default: `false`\n\nWhether the paths within nuxt/content match their real paths. This is useful when you're using the `nuxt/content` module\nwithout documentDriven mode.\n\n## `cacheMaxAgeSeconds`\n\n- Type: `number | false`\n- Default: `60 * 10`\n\nThe time in seconds to cache the sitemaps. Set to `false` to disable caching.\n\n## `sitemapsPathPrefix`\n\n- Type: `string | false`\n- Default: `/__sitemap__/`\n\nThe path prefix for the sitemaps when using multiple sitemaps.\n\n## `runtimeCacheStorage`\n\n- Type: `boolean | (Record<string, any> & { driver: string })`\n- Default: `true`\n\nThe storage engine to use for the cache. See [Performance](/docs/sitemap/advanced/performance) for details.\n\n## `xslColumns`\n\n- Type: ``({ label: string; width: `${string}%`; select?: string })[]``\n- Default:\n```json\n[\n  { \"label\": \"URL\", \"width\": \"50%\" },\n  { \"label\": \"Images\", \"width\": \"25%\", \"select\": \"count(image:image)\" },\n  { \"label\": \"Last Updated\", \"width\": \"25%\", \"select\": \"concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))\" }\n]\n```\n\nThe columns to display in the XSL stylesheet.\n\n## `xslTips`\n\n- Type: `boolean`\n- Default: `true`\n\nWhether to include tips on how to use the sitemap in the XSL stylesheet.\n\n## `experimentalWarmUp`\n\n- Type: `boolean`\n- Default: `false`\n\nShould the sitemaps be warmed up when Nitro starts. This can be useful for large sitemaps.\n\n## `experimentalCompression`\n\n- Type: `boolean`\n- Default: `false`\n\nShould the sitemaps be compressed and streamed when the request accepts it.\n\n## `credits`\n\n- Type: `boolean`\n- Default: `true`\n\nWhether to include a comment on the sitemaps on how it was generated.\n\n## `minify`\n\n- Type: `boolean`{lang=\"ts\"}\n- Default: `false`{lang=\"ts\"}\n\nWhether to minify the sitemap.xml.\n\n## `debug`\n\n- Type: `boolean`\n- Default: `false`\n\nEnable to see debug logs and API endpoint.\n\nThe route at `/__sitemap__/debug.json` will be available in non-production environments.\n\nSee the [Troubleshooting](/docs/sitemap/getting-started/troubleshooting) guide for details.\n\n## `zeroRuntime`\n\n- Type: `boolean`\n- Default: `false`\n\nWhen enabled, sitemap generation only runs during prerendering. The sitemap building code is tree-shaken from the runtime bundle, reducing server bundle size by ~50KB.\n\nRequires sitemaps to be prerendered. When enabled, `/sitemap.xml` is automatically added to `nitro.prerender.routes`.\n\nSee the [Zero Runtime](/docs/sitemap/guides/zero-runtime) guide for details.\n"
  },
  {
    "path": "docs/content/4.api/1.nuxt-hooks.md",
    "content": "---\ntitle: Nuxt Hooks\ndescription: Build-time Nuxt hooks provided by @nuxtjs/sitemap.\n---\n\n## `'sitemap:prerender:done'`{lang=\"ts\"}\n\n**Type:** `(ctx: { options: ModuleRuntimeConfig, sitemaps: { name: string, readonly content: string }[] }) => void | Promise<void>`{lang=\"ts\"}\n\nCalled after sitemap prerendering completes. Useful for modules that need to emit extra files based on the generated sitemaps.\n\n**Context:**\n\n- `options` - The resolved module runtime configuration\n- `sitemaps` - Array of rendered sitemaps with their route name and XML content (content is lazily loaded from disk)\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  hooks: {\n    'sitemap:prerender:done': async ({ sitemaps }) => {\n      // Log sitemap info\n      for (const sitemap of sitemaps) {\n        console.log(`Sitemap ${sitemap.name}: ${sitemap.content.length} bytes`)\n      }\n    }\n  }\n})\n```\n\n::note\nThis hook only runs at build time during `nuxt generate` or `nuxt build` with prerendering enabled.\n::\n"
  },
  {
    "path": "docs/content/5.nitro-api/nitro-hooks.md",
    "content": "---\ntitle: Nitro Hooks\ndescription: Learn how to use Nitro Hooks to customize your sitemap entries.\nrelatedPages:\n  - path: /docs/sitemap/getting-started/data-sources\n    title: Data Sources\n  - path: /docs/sitemap/guides/dynamic-urls\n    title: Dynamic URL Endpoints\n  - path: /docs/sitemap/guides/multi-sitemaps\n    title: Multi Sitemaps\n---\n\nNitro hooks can be added to modify the output of your sitemaps at runtime.\n\n## `'sitemap:input'`{lang=\"ts\"}\n\n**Type:** `async (ctx: { event: H3Event; urls: SitemapUrlInput[]; sitemapName: string }) => void | Promise<void>`{lang=\"ts\"}\n\nTriggers once the raw list of URLs is collected from sources.\n\nThis hook is best used for inserting new URLs into the sitemap.\n\n```ts [server/plugins/sitemap.ts]\nimport { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:input', async (ctx) => {\n    // SitemapUrlInput is either a string\n    ctx.urls.push('/foo')\n    // or an object with loc, changefreq, and priority\n    ctx.urls.push({\n      loc: '/bar',\n      changefreq: 'daily',\n      priority: 0.8,\n    })\n  })\n})\n```\n\n## `'sitemap:resolved'`{lang=\"ts\"}\n\n**Type:** `async (ctx: { event: H3Event; urls: ResolvedSitemapUrl[]; sitemapName: string }) => void | Promise<void>`{lang=\"ts\"}\n\nTriggered once the final structure of the XML is generated, provides the URLs as objects.\n\nFor new URLs it's recommended to use `sitemap:input` instead. Use this hook for modifying entries or removing them.\n\n```ts [server/plugins/sitemap.ts]\nimport { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:resolved', async (ctx) => {\n    // single sitemap example - just add the url directly\n    ctx.urls.push({\n      loc: '/my-secret-url',\n      changefreq: 'daily',\n      priority: 0.8,\n    })\n    // multi sitemap example - filter for a sitemap name\n    if (ctx.sitemapName === 'posts') {\n      ctx.urls.push({\n        loc: '/posts/my-post',\n        changefreq: 'daily',\n        priority: 0.8,\n      })\n    }\n  })\n})\n```\n\n## `'sitemap:index-resolved'`{lang=\"ts\"}\n\n**Type:** `async (ctx: { event: H3Event; sitemaps: { sitemap: string, lastmod?: string }[] }) => void | Promise<void>`{lang=\"ts\"}\n\nTriggered once the final structure of the sitemap index is generated, provides the sitemaps as objects.\n\n```ts [server/plugins/sitemap.ts]\nimport { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:index-resolved', async (ctx) => {\n    // add a new sitemap to the index\n    ctx.sitemaps.push({\n      sitemap: 'https://mysite.com/my-sitemap.xml',\n      lastmod: new Date().toISOString(),\n    })\n  })\n})\n```\n\n## `'sitemap:output'`{lang=\"ts\"}\n\n**Type:** `async (ctx: { event: H3Event; sitemap: string; sitemapName: string }) => void | Promise<void>`{lang=\"ts\"}\n\nTriggered before the sitemap is sent to the client.\nIt provides the sitemap as a XML string.\n\n```ts [server/plugins/sitemap.ts]\nimport { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:output', async (ctx) => {\n    // append a comment credit to the footer of the xml\n    ctx.sitemap = `${ctx.sitemap}\\n<!-- Sitemap output test-->`\n  })\n})\n```\n\n## `'sitemap:sources'`{lang=\"ts\"}\n\n**Type:** `async (ctx: { event: H3Event; sitemapName: string; sources: SitemapSourceInput[] }) => void | Promise<void>`{lang=\"ts\"}\n\nTriggered before resolving sitemap sources. This hook allows you to:\n- Add new sources dynamically\n- Remove sources\n- Modify source configurations including fetch options and headers\n\nThis hook runs before sources are resolved, providing full control over the source list.\n\n```ts [server/plugins/sitemap.ts]\nimport { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:sources', async (ctx) => {\n    // Add a source that will be fetched\n    ctx.sources.push('/api/dynamic-urls')\n\n    // Add a source with fetch options\n    ctx.sources.push(['/api/authenticated-urls', { headers: { 'X-Api-Key': 'secret' } }])\n\n    // Add a resolved source with URLs directly (no fetch needed)\n    ctx.sources.push({\n      context: { name: 'my-custom-source' },\n      urls: ['/page-1', '/page-2', { loc: '/page-3', priority: 0.8 }],\n    })\n\n    // Modify existing sources to add headers\n    ctx.sources = ctx.sources.map((source) => {\n      if (typeof source === 'object' && 'fetch' in source && source.fetch) {\n        const [url, options = {}] = Array.isArray(source.fetch) ? source.fetch : [source.fetch, {}]\n\n        // Add headers from original request\n        const authHeader = ctx.event.node.req.headers.authorization\n        if (authHeader) {\n          options.headers = options.headers || {}\n          options.headers.Authorization = authHeader\n        }\n\n        source.fetch = [url, options]\n      }\n      return source\n    })\n\n    // Filter out sources\n    ctx.sources = ctx.sources.filter((source) => {\n      if (typeof source === 'string')\n        return !source.includes('skip-this')\n      return true\n    })\n  })\n})\n```\n\n## Recipes\n\n### Modify Sitemap `xmlns` attribute\n\nFor some search engines, you may need to add a custom `xmlns` attribute to the sitemap. You can do this with a simple\nsearch and replace in the `sitemap:output` hook.\n\n```ts [server/plugins/sitemap.ts]\nimport { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:output', async (ctx) => {\n    ctx.sitemap = ctx.sitemap.replace('<urlset ', '<urlset xmlns:mobile=\"http://www.baidu.com/schemas/sitemap-mobile/1/\" ')\n  })\n})\n```\n\n### Modify Video Entries For Host\n\nSometimes you'll want to include the videos from your markup automatically but exclude some of them based on the host.\n\n```ts\nimport { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:resolved', (ctx) => {\n    ctx.urls.map((url) => {\n      if (url.videos?.length) {\n        url.videos = url.videos.filter((video) => {\n          if (video.content_loc) {\n            const url = new URL(video.content_loc)\n            return url.host.startsWith('www.youtube.com')\n          }\n          return false\n        })\n      }\n      return url\n    })\n  })\n})\n```\n"
  },
  {
    "path": "docs/content/5.releases/3.v8.md",
    "content": "---\nnavigation:\n  title: v8.0.0\ntitle: Nuxt Sitemap v8.0.0\ndescription: Release notes for v8.0.0 of Nuxt Sitemap.\n---\n\n## Introduction\n\nThe v8 release focuses on a fully rewritten devtools experience and several quality of life improvements for Nuxt Content v3 and i18n users.\n\n## ⚠️ Breaking Changes\n\n### Site Config v4\n\nNuxt Site Config is a module used internally by Nuxt Sitemap.\n\nThe major update to v4.0.0 shouldn't have any direct effect on your site, however, you may want to double-check\nthe [breaking changes](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v4.0.0).\n\n### `asSitemapCollection()` Deprecated\n\nThe `asSitemapCollection()` composable has been replaced by `defineSitemapSchema()`. The old API still works but will log a deprecation warning.\n\n```diff\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { z } from 'zod'\n- import { asSitemapCollection } from '@nuxtjs/sitemap/content'\n+ import { defineSitemapSchema } from '@nuxtjs/sitemap/content'\n\nexport const collections = {\n-  content: defineCollection(asSitemapCollection({\n-    type: 'page',\n-    source: '**/*.md',\n-    schema: z.object({ title: z.string() })\n-  }))\n+  content: defineCollection({\n+    type: 'page',\n+    source: '**/*.md',\n+    schema: z.object({\n+      title: z.string(),\n+      sitemap: defineSitemapSchema()\n+    })\n+  })\n}\n```\n\n## 🚀 New Features\n\n### `defineSitemapSchema()` Composable\n\nA new composable for Nuxt Content v3 that provides a cleaner API for integrating sitemap configuration into your content collections. Supports `filter`, `onUrl`, and `name` options.\n\n```ts\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { z } from 'zod'\nimport { defineSitemapSchema } from '@nuxtjs/sitemap/content'\n\nexport const collections = {\n  content: defineCollection({\n    type: 'page',\n    source: '**/*.md',\n    schema: z.object({\n      title: z.string(),\n      sitemap: defineSitemapSchema({\n        filter: entry => !entry.path?.startsWith('/draft'),\n        onUrl: (url) => {\n          // customize URL entries\n          return url\n        }\n      })\n    })\n  })\n}\n```\n\n### `definePageMeta` Sitemap Configuration\n\nYou can now configure sitemap options directly in your page components using `definePageMeta`.\n\n```vue\n<script setup>\ndefinePageMeta({\n  sitemap: {\n    changefreq: 'daily',\n    priority: 0.8\n  }\n})\n</script>\n```\n\n### i18n Multi-Sitemap with Custom Sitemaps\n\nCustom sitemaps with `includeAppSources: true` are now automatically expanded per locale, generating `{locale}-{name}` formatted sitemaps.\n\n### Debug Production Endpoint\n\nA new `/__sitemap__/debug-production.json` endpoint is available in development mode, allowing you to inspect what the production sitemap output will look like during development.\n\n## 🐛 Bug Fixes\n\n- **Content v3**: Filter `.navigation` paths from sitemap URL generation\n- **Content v3**: Guard `afterParse` hook to prevent silent HMR failures\n- **i18n**: Include base URL in multi-sitemap redirect\n- **i18n**: Fix exclude filters when base URL and i18n prefixes are present\n- **i18n**: Respect `autoI18n: false` to generate single sitemap instead of per-locale sitemaps\n- **Types**: Use `robots` instead of `index` in route rules type definition\n- **Chunked sitemaps**: Fix path resolution with `sitemapsPathPrefix: '/'`\n\n## ⚡ Performance\n\n- Replaced `chalk` with `consola/utils` for a smaller bundle\n- Use `URL.canParse()` instead of try/catch `new URL()` for URL validation\n- Use `addPrerenderRoutes()` API instead of manual route pushing\n"
  },
  {
    "path": "docs/content/5.releases/4.v7.md",
    "content": "---\nnavigation:\n  title: v7.0.0\ntitle: Nuxt Sitemap v7.0.0\ndescription: Release notes for v7.0.0 of Nuxt Sitemap.\n---\n\n## Introduction\n\nThe v7 major of Nuxt Sitemap is a simple release to remove deprecations and add support for the [Nuxt SEO v2 stable](/announcement).\n\n## :icon{name=\"i-noto-warning\"} Breaking Features\n\n### Site Config v3\n\nNuxt Site Config is a module used internally by Nuxt Sitemap.\n\nThe major update to v3.0.0 shouldn't have any direct effect on your site, however, you may want to double-check\nthe [breaking changes](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v3.0.0).\n\n### Removed `inferStaticPagesAsRoutes` config\n\nIf you set this value to `false` previously, you will need to change it to the below:\n\n```diff\nexport default defineNuxtConfig({\n\tsitemap: {\n-    \tinferStaticPagesAsRoutes: false,\n+    \texcludeAppSources: ['nuxt:pages', 'nuxt:route-rules', 'nuxt:prerender']\n\t}\n})\n```\n\n### Removed `dynamicUrlsApiEndpoint` config\n\nThe `sources` config supports multiple API endpoints and allows you to provide custom fetch options, use this instead.\n\n```diff\nexport default defineNuxtConfig({\n\tsitemap: {\n-    \tdynamicUrlsApiEndpoint: '/__sitemap/urls',\n+    \tsources: ['/__sitemap/urls']\n\t}\n})\n```\n\n### Removed `cacheTtl` config\n\nPlease use the `cacheMaxAgeSeconds` as its a clearer config.\n\n```diff\nexport default defineNuxtConfig({\n\tsitemap: {\n-    \tcacheTtl: 10000,\n+    \tcacheMaxAgeSeconds: 10000\n\t}\n})\n```\n\n### Removed `index` route rule / Nuxt Content support\n\nIf you were using the `index: false` in either route rules or your Nuxt Content markdown files, you will need to update this to use the `robots` key.\n\n```diff\nexport default defineNuxtConfig({\n  routeRules: {\n    // use the `index` shortcut for simple rules\n-    '/secret/**': { index: false },\n+    '/secret/**': { robots: false },\n  }\n})\n```\n"
  },
  {
    "path": "docs/content/5.releases/5.v6.md",
    "content": "---\nnavigation:\n  title: v6.0.0\ntitle: Nuxt Sitemap v6.0.0\ndescription: Release notes for v6.0.0 of Nuxt Sitemap.\n---\n\n## Introduction\n\nThe v6 represents hopefully the last major that the module will undergo. It brings many underlying\nlogic improvements which aim to solve stability and performance issues and set up the module to support\nchunked multi-sitemaps in the future.\n\n## 🚨 Breaking Change\n\n### Google Search Console\n\nIf you're using multi-sitemaps it's important to check Google Search Console after the update and verify you haven't submitted the old multi-sitemap paths. If so, you should update them\n\n### Sitemap Output\n\nPlease verify your sitemap output after the update. Many changes have been made to the underlying logic and it's important to verify that your sitemap is still being generated correctly.\n\n## Changelog\n\n### &nbsp;&nbsp;&nbsp;🚨 Breaking Changes\n\n- Rewrite i18n resolving and url normalizing &nbsp;-&nbsp; by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/319 [<samp>(fab7e)</samp>](https://github.com/nuxt-modules/sitemap/commit/fab7e9e)\n- New multi sitemaps paths &nbsp;-&nbsp; by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/320 [<samp>(bb7d9)</samp>](https://github.com/nuxt-modules/sitemap/commit/bb7d9c7)\n\n### &nbsp;&nbsp;&nbsp;🚀 Features\n\n- `sitemapsPathPrefix` config &nbsp;-&nbsp; by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/325 [<samp>(4b94c)</samp>](https://github.com/nuxt-modules/sitemap/commit/4b94c3d)\n- Add minify xml option &nbsp;-&nbsp; by @Henvy-Mango in https://github.com/nuxt-modules/sitemap/issues/336 [<samp>(f9197)</samp>](https://github.com/nuxt-modules/sitemap/commit/f919726)\n- **i18n**: Support Nuxt I18n v9 &nbsp;-&nbsp; by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/351 [<samp>(92d96)</samp>](https://github.com/nuxt-modules/sitemap/commit/92d9610)\n\n### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes\n\n- Better filtering of file URLs &nbsp;-&nbsp; by @harlan-zw [<samp>(27a95)</samp>](https://github.com/nuxt-modules/sitemap/commit/27a95be)\n- Check for `robots` route rules &nbsp;-&nbsp; by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/321 [<samp>(ae455)</samp>](https://github.com/nuxt-modules/sitemap/commit/ae455da)\n- Map `include`, `exclude` to i18n pages &nbsp;-&nbsp; by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/322 [<samp>(a7c04)</samp>](https://github.com/nuxt-modules/sitemap/commit/a7c04bc)\n- Fallback to prerender sitemap on vercel edge &nbsp;-&nbsp; by @harlan-zw [<samp>(33598)</samp>](https://github.com/nuxt-modules/sitemap/commit/33598c8)\n- Support `SERVER_PRESET` to detect env &nbsp;-&nbsp; by @harlan-zw [<samp>(295c9)</samp>](https://github.com/nuxt-modules/sitemap/commit/295c98f)\n- Handle null `loc`'s &nbsp;-&nbsp; by @harlan-zw [<samp>(c0666)</samp>](https://github.com/nuxt-modules/sitemap/commit/c066610)\n- `useNitroApp` import warning &nbsp;-&nbsp; by @harlan-zw [<samp>(f5ab8)</samp>](https://github.com/nuxt-modules/sitemap/commit/f5ab878)\n- Preset not being resolved when using `--target` &nbsp;-&nbsp; by @harlan-zw [<samp>(2f6bc)</samp>](https://github.com/nuxt-modules/sitemap/commit/2f6bca8)\n- Broken regex for `<NuxtImage>` components &nbsp;-&nbsp; by @harlan-zw [<samp>(469e7)</samp>](https://github.com/nuxt-modules/sitemap/commit/469e7bd)\n- Ensure `loc` is always a string &nbsp;-&nbsp; by @harlan-zw [<samp>(de9ec)</samp>](https://github.com/nuxt-modules/sitemap/commit/de9ecc2)\n- Improve entry `loc` normalizing &nbsp;-&nbsp; by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/354 [<samp>(6ef8d)</samp>](https://github.com/nuxt-modules/sitemap/commit/6ef8dcd)\n- **i18n**:\n  - Support excluded locales &nbsp;-&nbsp; by @Xenossolitarius and **ipesic** in https://github.com/nuxt-modules/sitemap/issues/331 [<samp>(f9ba0)</samp>](https://github.com/nuxt-modules/sitemap/commit/f9ba056)\n  - Reverse only locales logic &nbsp;-&nbsp; by @Xenossolitarius and **ipesic** in https://github.com/nuxt-modules/sitemap/issues/346 [<samp>(cc86a)</samp>](https://github.com/nuxt-modules/sitemap/commit/cc86a0c)\n  - Broken trailing slashes config when using `differentDomains` &nbsp;-&nbsp; by @harlan-zw [<samp>(e8799)</samp>](https://github.com/nuxt-modules/sitemap/commit/e879913)\n  - Broken dedupe of loc and alternatives &nbsp;-&nbsp; by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/352 [<samp>(2b164)</samp>](https://github.com/nuxt-modules/sitemap/commit/2b16423)\n- **module**:\n  - Prevent false positive warning about ignored root keys &nbsp;-&nbsp; by @madebyfabian in https://github.com/nuxt-modules/sitemap/issues/338 [<samp>(e4543)</samp>](https://github.com/nuxt-modules/sitemap/commit/e45432b)\n- **prerendering**:\n  - Prefer runtime site url validation &nbsp;-&nbsp; by @harlan-zw [<samp>(779d1)</samp>](https://github.com/nuxt-modules/sitemap/commit/779d100)\n\n##### &nbsp;&nbsp;&nbsp;&nbsp;[View changes on GitHub](https://github.com/nuxt-modules/sitemap/compare/v5.3.5...v6.0.0)\n"
  },
  {
    "path": "docs/content/5.releases/6.v5.md",
    "content": "---\nnavigation:\n  title: v5.0.0\ntitle: Nuxt Sitemap v5.0.0\ndescription: Release notes for v5.0.0 of Nuxt Sitemap.\n---\n\n## 🚨 Breaking Changes\n\n### Package Renamed to `@nuxtjs/sitemap`\n\nThis module is now the official Sitemap module for Nuxt. To properly\nreflect this, the package has been renamed to `@nuxtjs/sitemap` from `nuxt-simple-sitemap` and\nthe GitHub repository has been moved to [nuxt-modules/sitemap](https://github.com/nuxt-modules/sitemap).\n\n1. Update the dependency\n\n```diff\n{\n  \"dependencies\": {\n-    \"nuxt-simple-sitemap\": \"*\"\n+    \"@nuxtjs/sitemap\": \"^5.0.0\"\n  }\n}\n```\n\n2. Update your `nuxt.config`.\n\n```diff\nexport default defineNuxtConfig({\n  modules: [\n-   'nuxt-simple-sitemap'\n+  '@nuxtjs/sitemap'\n  ]\n})\n```\n\n## Features :rocket:\n\n## &nbsp;&nbsp;&nbsp;🐞 Bug Fixes\n\n### Improved Cache Debugging\n\n- Set browser cache time to match `cacheMaxAgeSeconds` &nbsp;-&nbsp; by @harlan-zw [<samp>(00d17)</samp>](https://github.com/nuxt-modules/sitemap/commit/00d176e)\n- Iso timestamp for debugging cache &nbsp;-&nbsp; by @harlan-zw [<samp>(db3f3)</samp>](https://github.com/nuxt-modules/sitemap/commit/db3f337)\n- Cache headers for prerendered sitemap &nbsp;-&nbsp; by @harlan-zw [<samp>(57bef)</samp>](https://github.com/nuxt-modules/sitemap/commit/57bef21)\n- More explicit caching &nbsp;-&nbsp; by @harlan-zw [<samp>(328b7)</samp>](https://github.com/nuxt-modules/sitemap/commit/328b737)\n\n### More Consistent DevTools UI\n\nThe DevTools has been updated to match the branding of the other Nuxt SEO module DevTools. [<samp>(bc4ae)</samp>](https://github.com/nuxt-modules/sitemap/commit/bc4aebc)\n\n### Others\n\n- Redirect multi sitemap `sitemap.xml` using route rules &nbsp;-&nbsp; by @harlan-zw [<samp>(e1bee)</samp>](https://github.com/nuxt-modules/sitemap/commit/e1bee81)\n\n##### &nbsp;&nbsp;&nbsp;&nbsp;[View changes on GitHub](https://github.com/nuxt-modules/sitemap/compare/v4.4.1...v5.0.0)\n"
  },
  {
    "path": "docs/content/5.releases/7.v4.md",
    "content": "---\nnavigation:\n  title: v4.0.0\ntitle: Nuxt Sitemap v4.0.0\ndescription: Release notes for v4.0.0 of Nuxt Sitemap.\n---\n\n## Background\n\nOver the last couple of months I've had many issues reported with similar themes:\n- Dynamic URLs are hard to work with\n- It's difficult to get multiple sitemaps to show the correct URLs\n- I18n has many small issues\n\nI hope this release can resolve these. It has required replacing much of the underlying logic, please test your sitemaps after upgrading.\n\n## Features :rocket:\n\n### 🥫 Sitemap Sources\n\nThe v4 introduces the official concept of 'sources' to your sitemaps.\n\nEvery URL within your sitemap will belong to a source. A source will either be a User source or a Application source.\n\nThis concept existed before v4 in different forms, v4 aims to clean them up and make working with them much easier.\n\nFor full documentation see [Sitemap Sources](/docs/sitemap/getting-started/data-sources).\n\n### 🤝 Nuxt Dev Tools Integration\n\nNuxt Sitemap now has a dedicated tab in Nuxt Dev Tools to help you debug.\n\n<details open=\"\" class=\"details-reset border rounded-2\">\n  <summary class=\"px-3 py-2\">\n    <svg aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-device-camera-video\">\n    <path d=\"M16 3.75v8.5a.75.75 0 0 1-1.136.643L11 10.575v.675A1.75 1.75 0 0 1 9.25 13h-7.5A1.75 1.75 0 0 1 0 11.25v-6.5C0 3.784.784 3 1.75 3h7.5c.966 0 1.75.784 1.75 1.75v.675l3.864-2.318A.75.75 0 0 1 16 3.75Zm-6.5 1a.25.25 0 0 0-.25-.25h-7.5a.25.25 0 0 0-.25.25v6.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-6.5ZM11 8.825l3.5 2.1v-5.85l-3.5 2.1Z\"></path>\n</svg>\n    <span aria-label=\"Video description nuxt-simple-sitemap-devtools.webm\" class=\"m-1\">nuxt-simple-sitemap-devtools.webm</span>\n    <span class=\"dropdown-caret\"></span>\n  </summary>\n\n  <video src=\"https://user-images.githubusercontent.com/5326365/282252319-269d8421-0704-4336-81a6-dd597fe80d38.webm\" data-canonical-src=\"https://user-images.githubusercontent.com/5326365/282252319-269d8421-0704-4336-81a6-dd597fe80d38.webm\" controls=\"controls\" muted=\"muted\" class=\"d-block rounded-bottom-2 border-top width-fit\" style=\"max-height:640px; min-height: 200px\">\n\n  </video>\n</details>\n\n### 💬 More i18n Improvements\n\n- Locale domain support ([#155](https://github.com/nuxt-modules/sitemap/issues/155))\n- Support pages opt-outed using `defineI18nRoute(false)` ([#126](https://github.com/nuxt-modules/sitemap/issues/126))\n- Only add trusted i18n routes, will use meta tags when prerendering\n- Less aggressive filtering\n- Opt-in to transform dynamic URLs `__i18nTransform: true`\n\nSee the updated [i18n documentation](/docs/sitemap/guides/i18n)\n\n### 🚀 Caching Improvements\n\nNow utilises native route rules. By default will set up SWR rules for 10 minutes.\n\nLearn more on the [Sitemap Caching](/docs/sitemap/advanced/performance) guide.\n\n## Other Improvements\n\n### Nitro Composables for better types\n\nWhen creating an API endpoint that returns URLs you should use the new [`defineSitemapEventHandler()`{lang=\"ts\"}](/docs/sitemap/nitro-api/nitro-hooks) function for full TypeScript support.\n\n```ts\n// api/sitemap.ts\nexport default defineSitemapEventHandler(() => {\n  return ['/foo']\n})\n```\n\n### Prerendering Improvements\n\nPreviously prerendering was done in a Node context, this will now run in a Nitro context which will provide better consistency between prerender and runtime environments.\n\n### Video Support\n\nVideo entries are now supported properly. ([#159](https://github.com/nuxt-modules/sitemap/issues/159))\n\n## ⚠️ Deprecations\n\n- `cacheTtl` is deprecated, you should use `cacheMaxAgeSeconds` which is more explicit.\n- `inferStaticPagesAsRoutes` is deprecated, if you were using this to opt-out of pages, you should use `excludeAppSources: true`\n\n## ☠️ Breaking Changes\n\n### Nuxt Hooks no longer supported\n\nIf you were using Nuxt hooks to modify the prerendered sitemap, you will need to migrate these to Nitro hooks.\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  hooks: {\n    // old - no longer supported\n    'sitemap:resolved': function (ctx) {},\n    'sitemap:output': function (ctx) {}\n  },\n})\n```\n\n```ts [server/plugins/sitemap]\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:output', async (ctx) => {\n    // supported!\n  })\n})\n```\n\n### Multi Sitemap App Sources\n\nBy default, app sources will no longer be included in multi sitemap implementations. You will need to use `includeAppSources: true` to re-enable it. See [Extending App Sources](/docs/sitemap/guides/multi-sitemaps#extending-app-sources) for more information.\n\n### Removed deprecations\n\n- The hook `sitemap:prerender` has been removed. You should use `sitemap:resolved` instead.\n- The config `trailingSlash` and `siteUrl` has been removed. You should use site config, see [Setting Site Config](/docs/site-config/guides/how-it-works).\n- The config `autoAlternativeLangPrefixes` has been removed. If you'd like to set up automatic alternative language prefixes use `__i18nTransform`.\n\n## Support my work\n\nThis release took over 40 hours.\nIf technical SEO developer experience in Nuxt is important to you, consider [supporting my work](https://github.com/sponsors/harlan-zw) on Nuxt SEO.\n"
  },
  {
    "path": "docs/content/5.releases/8.v3.md",
    "content": "---\ntitle: v3.0.0\ndescription: Release notes for v3.0.0.\n---\n\n## Features :rocket:\n\n### 🤝 Stable I18n Integration\n\nFully supporting i18n sites through the `sitemap` module has been a long requested feature.\n\nV2 Partially supported it, but in v3 support is fully integrated with build-time macros support and more.\n\n### 🚀  Default Caching\n\nCaching is now enabled by default for production sitemaps. Sitemaps will be cached for 1 hour.\n\nYou can change the cache time or disable caching by setting the `cacheTtl` config.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    cacheTtl: 5 * 60 * 60 * 1000 // 5 hours\n  }\n})\n```\n\nYou can also provide your own cache instance by setting the `runtimeCacheStorage` config.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    runtimeCacheStorage: {\n      driver: 'redis',\n      host: 'localhost',\n      port: 6379,\n      db: 0,\n    }\n  }\n})\n```\n\nLearn more on the [Performance](/docs/sitemap/advanced/performance) guide.\n\n### Improved XML Stylesheet\n\nThe UI of the sitemap.xml page has been improved slightly. It now features more useful tips and links.\n\nYou can also customise the stylesheet with the new following config:\n- `xslTips` - Toggle the tips displayed on the sitemap.xml pages.\n- `xslColumns` - Customise the columns displayed on the sitemap.xml pages.\n\nFor example, you can change the columns to only show the `loc` and `lastmod` columns.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    xslColumns: [\n      // URL column must always be set, no value needed\n      { label: 'URL', width: '75%' },\n      { label: 'Last Modified', value: 'sitemap:lastmod', width: '25%' },\n    ],\n  },\n})\n```\n\nLearn more on the [Customising the UI](/docs/sitemap/advanced/customising-ui) guide.\n\n### Debug Mode\n\nA `debug` config has been added\nwhich will give access to a custom endpoint at `/api/__sitemap__/debug` which will show you\nhow your sitemap is being generated.\n\nWhen you build your site with debug on, a `/__sitemap__/debug.json` page will be generated.\n\nThis is disabled by default and should only be enabled for debugging purposes.\n\n## Other Improvements\n\n## New Hook: `sitemap:output`\n\n**Type:** `async (ctx: { sitemap: string; sitemapName: string }) => void | Promise<void>`\n\nThis will let you modify the string content of the final sitemap before it is returned from the server.\n\nCan be ran in both Nitro (runtime) and Nuxt (prerendering).\n\n## New Hook: `sitemap:resolved`\n\n**Type:** `async (ctx: { sitemap: FullSitemapEntry[]; sitemapName: string }) => void | Promise<void>`\n\nThis will let you modify the final sitemap before it is turned into a string.\n\nCan be ran in both Nitro (runtime) and Nuxt (prerendering).\n\n### Individual multi-sitemap API endpoints  `dynamicUrlsApiEndpoint`\n\n- Type: `boolean | string`{lang=\"ts\"}\n- Default: `false`{lang=\"ts\"}\n\nYou can now give each sitemap a unique API endpoint to fetch URLs from.\n\n```ts\nexport default defineNuxtConfig({\n  sitemap: {\n    sitemaps: {\n      foo: {\n        dynamicUrlsApiEndpoint: '/api/foo-sitemap'\n      },\n      bar: {\n        dynamicUrlsApiEndpoint: '/api/bar-sitemap'\n      },\n    },\n  }\n})\n```\n\n### New Config: `strictNuxtContentPaths`\n\n- Type: `boolean`{lang=\"ts\"}\n- Default: `false`{lang=\"ts\"}\n\nEnable when the paths of your nuxt/content md files match the routing.\n\nThis will automatically add sitemap content to the sitemap.\n\nThis is similar behaviour to using `nuxt/content` with `documentDriven: true`.\n\n### New Config: `credits`\n\n- Type: `boolean`{lang=\"ts\"}\n- Default: `true`{lang=\"ts\"}\n\nAllows you to remove the \"Generate by Nuxt Sitemap\" comment from the generated sitemap.\n\n### New Config: `xslTips`\n\n- Type: `boolean`{lang=\"ts\"}\n- Default: `true`{lang=\"ts\"}\n\nToggle the tips displayed on the sitemap.xml pages.\n\n## Deprecation\n\n- `trailingSlash` has been deprecated\n- `siteUrl` has been deprecated\n- `autoAlternativeLangPrefixes` is now disabled by default. If you want to enable it, you need to set it to `true` explicitly.\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import antfu from '@antfu/eslint-config'\nimport harlanzw from 'eslint-plugin-harlanzw'\n\nexport default antfu(\n  {\n    type: 'lib',\n    ignores: [\n      'CLAUDE.md',\n      'test/fixtures/**',\n      'playground/**',\n      'docs/**',\n      'benchmark/**',\n    ],\n    rules: {\n      'no-use-before-define': 'off',\n      'node/prefer-global/process': 'off',\n      'node/prefer-global/buffer': 'off',\n      'ts/explicit-function-return-type': 'off',\n      'e18e/prefer-static-regex': 'off',\n    },\n  },\n  {\n    files: ['**/test/**/*.ts', '**/test/**/*.js'],\n    rules: {\n      'ts/no-unsafe-function-type': 'off',\n      'no-console': 'off',\n      'antfu/no-top-level-await': 'off',\n    },\n  },\n  ...harlanzw({ link: true, nuxt: true, vue: true }),\n  {\n    files: ['**/server/**/*.ts', '**/src/**/*.ts'],\n    rules: {\n      'harlanzw/vue-no-faux-composables': 'off',\n    },\n  },\n  {\n    files: ['examples/**/package.json'],\n    rules: {\n      'pnpm/json-enforce-catalog': 'off',\n      'pnpm/json-valid-catalog': 'off',\n      'pnpm/json-prefer-workspace-settings': 'off',\n    },\n  },\n)\n"
  },
  {
    "path": "examples/basic/app/app.vue",
    "content": "<template>\n  <div>\n    <h1>Sitemap Basic Example</h1>\n    <nav>\n      <NuxtLink to=\"/\">\n        Home\n      </NuxtLink>\n      <NuxtLink to=\"/about\">\n        About\n      </NuxtLink>\n      <NuxtLink to=\"/contact\">\n        Contact\n      </NuxtLink>\n    </nav>\n    <p>\n      <a href=\"/sitemap.xml\">View Sitemap</a>\n    </p>\n    <NuxtPage />\n  </div>\n</template>\n"
  },
  {
    "path": "examples/basic/app/pages/about.vue",
    "content": "<template>\n  <div>\n    <h2>About</h2>\n    <p>This is the about page.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/basic/app/pages/contact.vue",
    "content": "<template>\n  <div>\n    <h2>Contact</h2>\n    <p>This is the contact page.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/basic/app/pages/index.vue",
    "content": "<template>\n  <div>\n    <h2>Home</h2>\n    <p>Welcome to the basic sitemap example.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/basic/nuxt.config.ts",
    "content": "export default defineNuxtConfig({\n  modules: ['@nuxtjs/sitemap'],\n\n  site: {\n    url: 'https://example.com',\n  },\n\n  compatibilityDate: '2025-01-01',\n})\n"
  },
  {
    "path": "examples/basic/package.json",
    "content": "{\n  \"name\": \"nuxtjs-sitemap-basic-example\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@nuxtjs/sitemap\": \"latest\",\n    \"nuxt\": \"^4.4.4\",\n    \"vue\": \"^3.5.33\"\n  }\n}\n"
  },
  {
    "path": "examples/basic/tsconfig.json",
    "content": "{\n  \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
  },
  {
    "path": "examples/dynamic-urls/app/app.vue",
    "content": "<template>\n  <div>\n    <h1>Sitemap Dynamic URLs Example</h1>\n    <nav>\n      <NuxtLink to=\"/\">\n        Home\n      </NuxtLink>\n      <NuxtLink to=\"/blog/post-1\">\n        Post 1\n      </NuxtLink>\n      <NuxtLink to=\"/blog/post-2\">\n        Post 2\n      </NuxtLink>\n      <NuxtLink to=\"/blog/post-3\">\n        Post 3\n      </NuxtLink>\n    </nav>\n    <p>\n      <a href=\"/sitemap.xml\">View Sitemap</a>\n    </p>\n    <p>Dynamic URLs are generated via server/api/_sitemap-urls.ts.</p>\n    <NuxtPage />\n  </div>\n</template>\n"
  },
  {
    "path": "examples/dynamic-urls/app/pages/blog/[slug].vue",
    "content": "<template>\n  <div>\n    <h2>Blog Post: {{ $route.params.slug }}</h2>\n    <p>This is a dynamically generated blog post page.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/dynamic-urls/app/pages/index.vue",
    "content": "<template>\n  <div>\n    <h2>Home</h2>\n    <p>Welcome to the dynamic URLs sitemap example.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/dynamic-urls/nuxt.config.ts",
    "content": "export default defineNuxtConfig({\n  modules: ['@nuxtjs/sitemap'],\n\n  site: {\n    url: 'https://example.com',\n  },\n\n  compatibilityDate: '2025-01-01',\n})\n"
  },
  {
    "path": "examples/dynamic-urls/package.json",
    "content": "{\n  \"name\": \"nuxtjs-sitemap-dynamic-urls-example\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@nuxtjs/sitemap\": \"latest\",\n    \"nuxt\": \"^4.4.4\",\n    \"vue\": \"^3.5.33\"\n  }\n}\n"
  },
  {
    "path": "examples/dynamic-urls/server/api/_sitemap-urls.ts",
    "content": "export default defineSitemapEventHandler(() => {\n  return [\n    { loc: '/blog/post-1', lastmod: new Date() },\n    { loc: '/blog/post-2', lastmod: new Date() },\n    { loc: '/blog/post-3', lastmod: new Date() },\n  ]\n})\n"
  },
  {
    "path": "examples/dynamic-urls/tsconfig.json",
    "content": "{\n  \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
  },
  {
    "path": "examples/i18n/app/app.vue",
    "content": "<template>\n  <div>\n    <h1>Sitemap i18n Example</h1>\n    <nav>\n      <NuxtLink to=\"/\">\n        Home\n      </NuxtLink>\n      <NuxtLink to=\"/about\">\n        About\n      </NuxtLink>\n      <NuxtLink to=\"/contact\">\n        Contact\n      </NuxtLink>\n    </nav>\n    <p>\n      <a href=\"/sitemap.xml\">View Sitemap</a>\n    </p>\n    <p>Sitemap includes alternate hreflang entries for en, fr, and de locales.</p>\n    <NuxtPage />\n  </div>\n</template>\n"
  },
  {
    "path": "examples/i18n/app/pages/about.vue",
    "content": "<template>\n  <div>\n    <h2>About</h2>\n    <p>This is the about page.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/i18n/app/pages/contact.vue",
    "content": "<template>\n  <div>\n    <h2>Contact</h2>\n    <p>This is the contact page.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/i18n/app/pages/index.vue",
    "content": "<template>\n  <div>\n    <h2>Home</h2>\n    <p>Welcome to the i18n sitemap example.</p>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/i18n/nuxt.config.ts",
    "content": "export default defineNuxtConfig({\n  modules: ['@nuxtjs/sitemap', '@nuxtjs/i18n'],\n\n  site: {\n    url: 'https://example.com',\n  },\n\n  i18n: {\n    locales: [\n      { code: 'en', name: 'English' },\n      { code: 'fr', name: 'Français' },\n      { code: 'de', name: 'Deutsch' },\n    ],\n    defaultLocale: 'en',\n    strategy: 'prefix_except_default',\n  },\n\n  compatibilityDate: '2025-01-01',\n})\n"
  },
  {
    "path": "examples/i18n/package.json",
    "content": "{\n  \"name\": \"nuxtjs-sitemap-i18n-example\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@nuxtjs/i18n\": \"^10.3.0\",\n    \"@nuxtjs/sitemap\": \"latest\",\n    \"nuxt\": \"^4.4.4\",\n    \"vue\": \"^3.5.33\"\n  }\n}\n"
  },
  {
    "path": "examples/i18n/tsconfig.json",
    "content": "{\n  \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@nuxtjs/sitemap\",\n  \"type\": \"module\",\n  \"version\": \"8.0.15\",\n  \"packageManager\": \"pnpm@10.33.2\",\n  \"description\": \"Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.\",\n  \"author\": {\n    \"name\": \"Harlan Wilton\",\n    \"email\": \"harlan@harlanzw.com\",\n    \"url\": \"https://harlanzw.com/\"\n  },\n  \"license\": \"MIT\",\n  \"funding\": \"https://github.com/sponsors/harlan-zw\",\n  \"homepage\": \"https://github.com/nuxt-modules/sitemap#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/nuxt-modules/sitemap.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/nuxt-modules/sitemap/issues\"\n  },\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/types.d.mts\",\n      \"import\": \"./dist/module.mjs\"\n    },\n    \"./content\": \"./dist/content.mjs\",\n    \"./utils\": \"./dist/utils.mjs\"\n  },\n  \"main\": \"./dist/module.mjs\",\n  \"typesVersions\": {\n    \"*\": {\n      \".\": [\n        \"./dist/types.d.mts\"\n      ],\n      \"content\": [\n        \"./dist/content.d.mts\"\n      ],\n      \"utils\": [\n        \"./dist/utils.d.mts\"\n      ]\n    }\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  },\n  \"scripts\": {\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"client:build\": \"nuxt generate devtools\",\n    \"prepack\": \"pnpm run build\",\n    \"devtools\": \"nuxt dev devtools --port 3030\",\n    \"build\": \"nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build && npm run client:build\",\n    \"dev\": \"nuxt dev playground\",\n    \"prepare:fixtures\": \"nuxt prepare test/fixtures/basic && nuxt prepare test/fixtures/i18n && nuxt prepare test/fixtures/i18n-micro\",\n    \"dev:build\": \"nuxt build playground\",\n    \"dev:prepare\": \"nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground\",\n    \"release\": \"pnpm build && bumpp -x \\\"npx changelogen --output=CHANGELOG.md\\\"\",\n    \"test\": \"vitest run && pnpm run test:attw\",\n    \"test:unit\": \"vitest --project=unit\",\n    \"test:attw\": \"attw --pack\",\n    \"typecheck\": \"nuxt typecheck\",\n    \"prepare\": \"skilld prepare || true\"\n  },\n  \"peerDependencies\": {\n    \"zod\": \">=3\"\n  },\n  \"peerDependenciesMeta\": {\n    \"zod\": {\n      \"optional\": true\n    }\n  },\n  \"dependencies\": {\n    \"@nuxt/kit\": \"catalog:\",\n    \"consola\": \"catalog:\",\n    \"defu\": \"catalog:\",\n    \"fast-xml-parser\": \"catalog:\",\n    \"nuxt-site-config\": \"catalog:\",\n    \"nuxtseo-shared\": \"catalog:\",\n    \"ofetch\": \"catalog:\",\n    \"pathe\": \"catalog:\",\n    \"pkg-types\": \"catalog:\",\n    \"radix3\": \"catalog:\",\n    \"ufo\": \"catalog:\",\n    \"ultrahtml\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@antfu/eslint-config\": \"catalog:\",\n    \"@arethetypeswrong/cli\": \"catalog:\",\n    \"@nuxt/content\": \"catalog:\",\n    \"@nuxt/devtools-kit\": \"catalog:\",\n    \"@nuxt/module-builder\": \"catalog:\",\n    \"@nuxt/test-utils\": \"catalog:\",\n    \"@nuxt/ui\": \"catalog:\",\n    \"@nuxtjs/i18n\": \"catalog:\",\n    \"@nuxtjs/robots\": \"catalog:\",\n    \"@nuxtjs/sitemap\": \"workspace:*\",\n    \"@vue/test-utils\": \"catalog:\",\n    \"better-sqlite3\": \"catalog:\",\n    \"bumpp\": \"catalog:\",\n    \"eslint\": \"catalog:\",\n    \"eslint-plugin-harlanzw\": \"catalog:\",\n    \"execa\": \"catalog:\",\n    \"happy-dom\": \"catalog:\",\n    \"nuxt\": \"catalog:\",\n    \"nuxt-i18n-micro\": \"catalog:\",\n    \"nuxtseo-layer-devtools\": \"catalog:\",\n    \"semver\": \"catalog:\",\n    \"sirv\": \"catalog:\",\n    \"std-env\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"vitest\": \"catalog:\",\n    \"vue-tsc\": \"catalog:\",\n    \"zod\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "patches/@nuxtjs__mdc.patch",
    "content": "diff --git a/dist/module.mjs b/dist/module.mjs\nindex e028b1a7fba50c413a0e5e40fd545d998917620a..a74d8a45a98dced151f1fc141f24c16746e0ab25 100644\n--- a/dist/module.mjs\n+++ b/dist/module.mjs\n@@ -350,7 +350,7 @@ const module = defineNuxtModule({\n       filename: \"mdc-image-component.mjs\",\n       write: true,\n       getContents: ({ app }) => {\n-        const image = app.components.find((c) => c.pascalName === \"NuxtImg\" && !c.filePath.includes(\"nuxt/dist/app\"));\n+        const image = app.components.find((c) => c.pascalName === \"NuxtImg\" && !c.filePath.includes(\"nuxt/dist/app\") && !c.filePath.includes(\"nuxt-nightly/dist/app\"));\n         return image ? `export { default } from \"${image.filePath}\"` : 'export default \"img\"';\n       }\n     });\n"
  },
  {
    "path": "playground/.nuxtrc",
    "content": "imports.autoImport=true\n"
  },
  {
    "path": "playground/app.vue",
    "content": "<script setup lang=\"ts\">\nimport { useI18n, useSiteConfig } from '#imports'\n\nconst i18n = useI18n()\nconst locale = i18n.locale\n\nconst languageItems = [\n  [\n    { label: 'English', click() { i18n.setLocale('en') } },\n    { label: 'French', click() { i18n.setLocale('fr') } },\n  ],\n]\n\nconst siteConfig = useSiteConfig({\n  name: 'Sitemap',\n})\n</script>\n\n<template>\n  <UApp>\n    <div class=\"flex flex-col min-h-screen\">\n      <header class=\"sticky top-0 z-50 w-full backdrop-blur border-b border-(--ui-border) bg-(--ui-bg)/75\">\n        <UContainer class=\"py-3\">\n          <div class=\"flex items-center justify-between\">\n            <NuxtLink\n              to=\"/\"\n              class=\"flex items-end gap-1.5 font-bold text-xl text-(--ui-text-highlighted)\"\n            >\n              <UIcon\n                name=\"i-logos-nuxt-icon\"\n                class=\"size-8\"\n              />\n              Nuxt\n              <span class=\"text-(--ui-primary)\">\n                {{ siteConfig.name }}\n              </span>\n            </NuxtLink>\n            <UDropdownMenu :items=\"languageItems\">\n              <UButton\n                color=\"neutral\"\n                variant=\"outline\"\n                :label=\"locale\"\n                trailing-icon=\"i-lucide-chevron-down\"\n              />\n            </UDropdownMenu>\n          </div>\n        </UContainer>\n      </header>\n      <main class=\"flex-grow\">\n        <UContainer class=\"py-6\">\n          <NuxtPage />\n        </UContainer>\n      </main>\n    </div>\n  </UApp>\n</template>\n"
  },
  {
    "path": "playground/assets/css/main.css",
    "content": "@import \"tailwindcss\";\n@import \"@nuxt/ui\";\n"
  },
  {
    "path": "playground/content/_partial.md",
    "content": "# bar\n"
  },
  {
    "path": "playground/content/bar.md",
    "content": "# bar\n"
  },
  {
    "path": "playground/content/foo.md",
    "content": "---\nsitemap:\n  priority: 0.5\n---\n\n# foo\n"
  },
  {
    "path": "playground/content/posts/bar.md",
    "content": "---\nsitemap:\n  loc: /blog/posts/bar\n  lastmod: 2021-10-20\n---\n# bar\n"
  },
  {
    "path": "playground/content/posts/foo.md",
    "content": "# foo\n"
  },
  {
    "path": "playground/nuxt.config.ts",
    "content": "import { defineNuxtConfig } from 'nuxt/config'\nimport NuxtSitemap from '../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxtjs/robots',\n    '@nuxtjs/i18n',\n    '@nuxt/content',\n    '@nuxt/ui',\n  ],\n\n  css: ['~/assets/css/main.css'],\n\n  site: {\n    url: 'https://sitemap-edge-demo.vercel.app/',\n  },\n\n  ignorePrefix: 'ignore-',\n\n  routeRules: {\n    '/api/prerendered': {\n      prerender: true,\n    },\n    '/secret': {\n      robots: false,\n    },\n    '/users-test/*': {\n      sitemap: {\n        lastmod: new Date(2023, 1, 21, 4, 50, 52),\n        changefreq: 'weekly',\n        priority: 0.3,\n        images: [],\n      },\n    },\n    '/should-not-be-in-sitemap/*': {},\n    '/about-redirect': {\n      redirect: '/about',\n    },\n    '/about': {\n      sitemap: {\n        lastmod: '2023-01-21',\n        changefreq: 'daily',\n        priority: 0.3,\n        images: [\n          {\n            loc: 'https://example.com/image.jpg',\n          },\n          {\n            loc: 'https://example.com/image2.jpg',\n          },\n        ],\n      },\n    },\n  },\n\n  experimental: {\n    inlineRouteRules: true,\n  },\n\n  compatibilityDate: '2025-01-17',\n\n  nitro: {\n    typescript: {\n      internalPaths: true,\n    },\n    plugins: ['plugins/sitemap.ts'],\n    prerender: {\n      routes: [\n        // '/sitemap_index.xml',\n        '/prerender',\n        '/prerender-video',\n        '/should-be-in-sitemap',\n        '/foo.bar/',\n        '/test.doc',\n        '/api/prerendered',\n      ],\n      failOnError: false,\n    },\n  },\n\n  i18n: {\n    locales: ['en', 'fr'],\n    defaultLocale: 'en',\n  },\n\n  // app: {\n  //   baseURL: '/base'\n  // },\n\n  sitemap: {\n    debug: true,\n    // sitemapName: 'test.xml',\n    minify: false,\n    cacheMaxAgeSeconds: 10,\n    xslColumns: [\n      { label: 'URL', width: '50%' },\n      { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },\n      { label: 'Hreflangs', select: 'count(xhtml:link)', width: '25%' },\n    ],\n    experimentalWarmUp: true,\n    urls: [\n      '/manual-url-test',\n      { loc: '/bad-lastmod', lastmod: 'not-a-date' },\n      { loc: '/bad-changefreq', changefreq: 'sometimes' as any },\n      { loc: '/bad-priority', priority: 5 },\n    ],\n    sources: [\n      '/some-invalid-url',\n      ['https://api.example.com/pages/urls', { headers: { Authorization: 'Bearer <token>' } }],\n    ],\n    defaultSitemapsChunkSize: 10,\n    sitemaps: {\n      posts: {\n        includeAppSources: true,\n        urls: async () => {\n          await new Promise((then) => {\n            setTimeout(then, 5000)\n          })\n          return ['/slow-url']\n        },\n        include: ['/slow-url', '/en/blog/**', '/fr/blog/**', '/blog/**'],\n      },\n      pages: {\n        includeAppSources: true,\n        sources: [\n          '/api/sitemap-foo',\n          'https://example.com/invalid.json',\n        ],\n        exclude: ['/en/blog/**', '/fr/blog/**', '/blog/**', /.*hide-me.*/g],\n        urls: [\n          {\n            loc: '/about',\n            lastmod: '2023-02-21T08:50:52.000Z',\n            alternatives: [\n              {\n                href: '/fr/about',\n                hreflang: 'fr',\n              },\n            ],\n            images: [\n              {\n                loc: 'https://example.com/image-3.jpg',\n              },\n            ],\n          },\n        ],\n      },\n      index: [\n        { sitemap: 'https://www.example.com/sitemap-pages.xml' },\n      ],\n    },\n  },\n})\n"
  },
  {
    "path": "playground/pages/.ignored/test.vue",
    "content": "<template>\n  <div>shouldn't be added</div>\n</template>\n"
  },
  {
    "path": "playground/pages/[...slug].vue",
    "content": "<template>\n  <p>{{ $route.params.slug }}</p>\n</template>\n"
  },
  {
    "path": "playground/pages/_dir/robots.txt",
    "content": "User-agent: *\nDisallow: /blocked-by-robots-txt\n"
  },
  {
    "path": "playground/pages/about.vue",
    "content": "<script setup lang=\"ts\">\n</script>\n\n<template>\n  <div>\n    About page\n    <img\n      src=\"https://res.cloudinary.com/dl6o1xpyq/image/upload/f_jpg,q_auto:best,dpr_auto,w_240,h_240/images/harlan-wilton\"\n      alt=\"Harlan Wilton\"\n    >\n  </div>\n</template>\n"
  },
  {
    "path": "playground/pages/api/foo.vue",
    "content": "<template>\n  <div>hello world</div>\n</template>\n"
  },
  {
    "path": "playground/pages/blocked-by-robots-txt/foo.vue",
    "content": "<template>\n  <div>This should be blocked by @nuxtjs/robots integration automatically.</div>\n</template>\n"
  },
  {
    "path": "playground/pages/blog/[id].vue",
    "content": "<template>\n  <div>Hello world</div>\n</template>\n"
  },
  {
    "path": "playground/pages/blog/categories.vue",
    "content": "<template>\n  <div>temp</div>\n</template>\n"
  },
  {
    "path": "playground/pages/blog/index.vue",
    "content": "<template>\n  <div>temp</div>\n</template>\n"
  },
  {
    "path": "playground/pages/blog/tags/edit.vue",
    "content": "<template>\n  <div>edit</div>\n</template>\n"
  },
  {
    "path": "playground/pages/blog/tags/new.vue",
    "content": "<template>\n  <div>new</div>\n</template>\n"
  },
  {
    "path": "playground/pages/blog/tags.vue",
    "content": "<template>\n  <div>temp</div>\n</template>\n"
  },
  {
    "path": "playground/pages/blog.vue",
    "content": "<template>\n  <div>temp</div>\n</template>\n"
  },
  {
    "path": "playground/pages/foo.bar.vue",
    "content": "<template>\n  <div>foo.bar</div>\n</template>\n"
  },
  {
    "path": "playground/pages/hidden-path-but-in-sitemap/index.vue",
    "content": "<template>\n  <div>\n    <p>You found me</p>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/pages/hide-me.vue",
    "content": "<template>\n  <div>hide-me</div>\n</template>\n"
  },
  {
    "path": "playground/pages/ignore-foo.vue",
    "content": "<template>\n  <div>foo</div>\n</template>\n"
  },
  {
    "path": "playground/pages/index.vue",
    "content": "<script setup lang=\"ts\">\n// does not work (yet?)\ndefinePageMeta({\n  sitemap: {\n    lastmod: 'broken',\n    changefreq: 'foo',\n  },\n})\n</script>\n\n<template>\n  <div>\n    <h1 class=\"text-2xl font-bold mb-6\">\n      Sitemap Playground\n    </h1>\n    <div class=\"grid grid-cols-2 gap-3 max-w-lg\">\n      <UButton to=\"/about\" variant=\"soft\" color=\"neutral\">\n        About\n      </UButton>\n      <UButton to=\"/blog\" variant=\"soft\" color=\"neutral\">\n        Blog\n      </UButton>\n      <UButton to=\"/new-page\" variant=\"soft\" color=\"neutral\">\n        New Page\n      </UButton>\n      <UButton to=\"/secret\" variant=\"soft\" color=\"neutral\">\n        Secret (robots: false)\n      </UButton>\n      <UButton to=\"/ignore-foo\" variant=\"soft\" color=\"neutral\">\n        ignore-foo\n      </UButton>\n      <UButton to=\"/users-prerender\" variant=\"soft\" color=\"neutral\">\n        Dynamic Pre-render\n      </UButton>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/pages/new-page.vue",
    "content": "<template>\n  <div>\n    New page\n  </div>\n</template>\n"
  },
  {
    "path": "playground/pages/prerender-video.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div>\n    Pre-render Video Discovery Page\n\n    <!-- Control Video with src, should auto-discover -->\n    <video\n      controls\n      src=\"https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4\"\n      poster=\"https://archive.org/download/BigBuckBunny_124/__ia_thumb.jpg\"\n      width=\"620\"\n      data-title=\"Big Buck Bunny\"\n      data-description=\"Big Buck Bunny in DivX 720p.\"\n    >\n      Sorry, your browser doesn't support embedded videos, but don't worry, you\n      can\n      <a href=\"https://archive.org/details/BigBuckBunny_124\">download it</a>\n      and watch it with your favorite video player!\n    </video>\n\n    <!-- Control Video with source, should auto-discover -->\n    <video\n      controls\n      poster=\"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\"\n      width=\"620\"\n      data-title=\"Duck and Cover\"\n      data-description=\"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\"\n    >\n      <source\n        src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\"\n        type=\"video/mp4\"\n      >\n      <source\n        src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi\"\n        type=\"video/x-msvideo\"\n      >\n      Sorry, your browser doesn't support embedded videos, but don't worry, you\n      can\n      <a href=\"https://archive.org/details/DuckAndCover_185\">download it</a>\n      and watch it with your favorite video player!\n    </video>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/pages/prerender.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div>\n    Pre-render Image Discovery Page\n\n    <!-- Control Image, should auto-discover -->\n    <img\n      src=\"https://res.cloudinary.com/dl6o1xpyq/image/upload/f_jpg,q_auto:best,dpr_auto,w_240,h_240/images/harlan-wilton\"\n      alt=\"Harlan Wilton\"\n    >\n\n    <!-- Should NOT auto-discover -->\n    <img\n      src=\"data:image/jpeg;base64,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\"\n    >\n  </div>\n</template>\n"
  },
  {
    "path": "playground/pages/secret.vue",
    "content": "<script setup lang=\"ts\">\n</script>\n\n<template>\n  <div>\n    <div>\n      Secret page, not for robots.\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/pages/users-[group]/[id].vue",
    "content": "<template>\n  <div>Hello world</div>\n</template>\n"
  },
  {
    "path": "playground/pages/users-[group]/index.vue",
    "content": "<template>\n  <div>Hello world</div>\n</template>\n"
  },
  {
    "path": "playground/server/api/_sitemap-urls.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    '/users-lazy/1',\n    '/users-lazy/2',\n    '/users-lazy/3',\n    ...posts.map(post => ({\n      loc: `/blog/post-${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "playground/server/api/fetch.ts",
    "content": "import type { asSitemapUrl } from '#imports'\nimport { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(async () => {\n  return $fetch<ReturnType<typeof asSitemapUrl>[]>('/api/sitemap-urls-to-be-confumsed-by-fetch')\n})\n"
  },
  {
    "path": "playground/server/api/multi-sitemap-sources/bar.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/bar/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "playground/server/api/multi-sitemap-sources/foo.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/foo/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "playground/server/api/prerendered.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(async () => {\n  return { foo: 'bar' }\n})\n"
  },
  {
    "path": "playground/server/api/sitemap-bar.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    '/bar/1',\n    '/bar/2',\n    '/bar/3',\n  ]\n})\n"
  },
  {
    "path": "playground/server/api/sitemap-foo.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    '/foo/1',\n    '/foo/2',\n    '/foo/3',\n  ]\n})\n"
  },
  {
    "path": "playground/server/api/sitemap-urls-to-be-confumsed-by-fetch.ts",
    "content": "import { withLeadingSlash } from 'ufo'\nimport { defineEventHandler } from '#imports'\n\nexport default defineEventHandler(() => {\n  return $fetch<{ title: string }[]>('https://jsonplaceholder.typicode.com/posts').then((res) => {\n    return res.map(post => ({\n      invalidAttr: 'foo',\n      loc: withLeadingSlash(post.title.replace(' ', '-')),\n    }))\n  })\n})\n"
  },
  {
    "path": "playground/server/plugins/sitemap.ts",
    "content": "import { defineNitroPlugin } from '#imports'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:output', async () => {\n    // eslint-disable-next-line no-console\n    console.log('Sitemap SSR hook')\n  })\n  nitroApp.hooks.hook('sitemap:index-resolved', (ctx) => {\n    // eslint-disable-next-line no-console\n    console.log('Sitemap index resolved hook', ctx)\n  })\n})\n"
  },
  {
    "path": "playground/server/routes/__sitemap.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  const posts = Array.from({ length: 3 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/blog/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "playground/server/tsconfig.json",
    "content": "{\n  // https://v3.nuxtjs.org/concepts/typescript\n  \"extends\": \"../.nuxt/tsconfig.server.json\"\n}\n"
  },
  {
    "path": "playground/tsconfig.json",
    "content": "{\n  \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "shellEmulator: true\n\ntrustPolicy: no-downgrade\n\ntrustPolicyExclude:\n  - chokidar\n  - semver\npackages:\n  - devtools\n  - test/fixtures/**\n  - playground\n  - benchmark\n  - '!examples/**'\noverrides:\n  '@vitejs/plugin-vue': ^6.0.6\n  vite: ^8.0.10\npatchedDependencies:\n  '@nuxtjs/mdc': patches/@nuxtjs__mdc.patch\ncatalog:\n  '@antfu/eslint-config': ^8.2.0\n  '@arethetypeswrong/cli': ^0.18.2\n  '@iconify-json/carbon': ^1.2.20\n  '@iconify-json/simple-icons': ^1.2.80\n  '@nuxt/content': ^3.13.0\n  '@nuxt/devtools-kit': 4.0.0-alpha.3\n  '@nuxt/kit': ^4.4.4\n  '@nuxt/module-builder': ^1.0.2\n  '@nuxt/test-utils': ^4.0.3\n  '@nuxt/ui': ^4.7.1\n  '@nuxtjs/i18n': ^10.3.0\n  '@nuxtjs/robots': ^6.0.8\n  '@vue/test-utils': ^2.4.10\n  '@vueuse/core': ^14.3.0\n  autocannon: ^8.0.0\n  better-sqlite3: ^12.9.0\n  bumpp: ^11.0.1\n  consola: ^3.4.2\n  defu: ^6.1.7\n  eslint: ^10.2.1\n  eslint-plugin-harlanzw: ^0.12.1\n  execa: ^9.6.1\n  fast-xml-parser: ^5.7.2\n  happy-dom: ^20.9.0\n  nuxt: ^4.4.4\n  nuxt-i18n-micro: ^3.17.5\n  nuxt-site-config: ^4.0.8\n  nuxtseo-layer-devtools: ^5.1.3\n  nuxtseo-shared: ^5.1.3\n  ofetch: ^1.5.1\n  pathe: ^2.0.3\n  pkg-types: ^2.3.1\n  radix3: ^1.1.2\n  semver: ^7.7.4\n  sirv: ^3.0.2\n  std-env: ^4.1.0\n  typescript: ^6.0.3\n  ufo: ^1.6.4\n  ultrahtml: ^1.6.0\n  vitest: ^4.1.5\n  vue: ^3.5.33\n  vue-router: ^5.0.6\n  vue-tsc: ^3.2.7\n  zod: ^4.4.1\n\nonlyBuiltDependencies:\n  - better-sqlite3\n  - esbuild\ntrustPolicyIgnoreAfter: 262800\n"
  },
  {
    "path": "src/content.ts",
    "content": "import type { Collection, PageCollectionItemBase } from '@nuxt/content'\nimport type { TypeOf } from 'zod'\nimport { createContentSchemaFactory } from 'nuxtseo-shared/content'\nimport { z } from 'zod'\n\ndeclare global {\n  // eslint-disable-next-line vars-on-top\n  var __sitemapCollectionFilters: Map<string, (entry: any) => boolean> | undefined\n  // eslint-disable-next-line vars-on-top\n  var __sitemapCollectionOnUrlFns: Map<string, (url: any, entry: any, collection: string) => void> | undefined\n}\n\nif (!globalThis.__sitemapCollectionFilters)\n  globalThis.__sitemapCollectionFilters = new Map()\nif (!globalThis.__sitemapCollectionOnUrlFns)\n  globalThis.__sitemapCollectionOnUrlFns = new Map()\n\nconst collectionFilters = globalThis.__sitemapCollectionFilters\nconst collectionOnUrlFns = globalThis.__sitemapCollectionOnUrlFns\n\nexport interface DefineSitemapSchemaOptions<TEntry = Record<string, unknown>> {\n  z?: typeof z\n  name?: string\n  filter?: (entry: PageCollectionItemBase & SitemapSchema & TEntry) => boolean\n  onUrl?: (\n    url: { loc: string, lastmod?: string | Date, changefreq?: string, priority?: number, images?: { loc: string }[], videos?: { content_loc: string }[], [key: string]: unknown },\n    entry: PageCollectionItemBase & SitemapSchema & TEntry,\n    collection: string,\n  ) => void\n}\n\nconst { defineSchema, asCollection, schema } = createContentSchemaFactory({\n  fieldName: 'sitemap',\n  label: 'sitemap',\n  docsUrl: 'https://nuxtseo.com/sitemap/guides/content',\n  buildSchema: _z => buildSitemapObjectSchema(_z),\n  onDefineSchema: (options: DefineSitemapSchemaOptions) => {\n    if ('type' in options || 'source' in options)\n      throw new Error('[sitemap] `defineSitemapSchema()` returns a schema field, not a collection wrapper. Use it inside your schema: `schema: z.object({ sitemap: defineSitemapSchema() })`. See https://nuxtseo.com/sitemap/guides/content')\n    warnIfZodMismatch(options?.z)\n    if (options?.filter || options?.onUrl) {\n      if (!options.name)\n        throw new Error('[sitemap] `name` is required when using `filter` or `onUrl` in defineSitemapSchema()')\n      if (options.filter)\n        collectionFilters.set(options.name, options.filter)\n      if (options.onUrl)\n        collectionOnUrlFns.set(options.name, options.onUrl)\n    }\n  },\n}, z)\n\nexport { defineSchema as defineSitemapSchema, schema }\n\nexport type SitemapSchema = TypeOf<typeof schema>\n\n/** @deprecated Use `defineSitemapSchema()` in your collection schema instead. See https://nuxtseo.com/sitemap/guides/content */\nexport function asSitemapCollection<T>(collection: Collection<T>, options?: DefineSitemapSchemaOptions<T>): Collection<T> {\n  if (options?.filter || options?.onUrl) {\n    if (!options.name)\n      throw new Error('[sitemap] `name` is required when using `filter` or `onUrl` in asSitemapCollection()')\n    if (options.filter)\n      collectionFilters.set(options.name, options.filter)\n    if (options.onUrl)\n      collectionOnUrlFns.set(options.name, options.onUrl)\n  }\n  try {\n    return asCollection(collection) as Collection<T>\n  }\n  catch (e) {\n    console.warn(\n      `[sitemap] Failed to apply sitemap schema to collection. This is likely a Zod version mismatch.`,\n      `Pass your Zod instance explicitly: \\`defineSitemapSchema({ z })\\`. See https://nuxtseo.com/sitemap/guides/content`,\n      `Error: ${(e as Error).message}`,\n    )\n    return collection\n  }\n}\n\nlet _hasWarnedZodMismatch = false\nfunction warnIfZodMismatch(userZ?: typeof z) {\n  if (_hasWarnedZodMismatch || userZ)\n    return\n  // Detect mixed zod versions: zod 3 uses `_def` without `def`, zod 4 has both\n  const testSchema = z.object({}) as any\n  const hasV3 = '_def' in testSchema && !('def' in testSchema)\n  const hasV4 = 'def' in testSchema\n  // If both zod 3 and zod 4 are installed, `import { z } from 'zod'` may resolve\n  // to a different version than what @nuxt/content uses internally\n  if (hasV3 || hasV4) {\n    // Only warn if we can detect the version (always true), but the user hasn't passed z\n    // The real check: does our z produce schemas compatible with @nuxt/content's detectSchemaVendor?\n    // We can't know for sure without importing content, so just warn if zod 3 is resolved\n    // since @nuxt/content v3.12+ expects zod 4\n    if (hasV3) {\n      _hasWarnedZodMismatch = true\n      console.warn(\n        `[sitemap] Zod 3 detected but @nuxt/content v3 expects Zod 4.`,\n        `Pass your zod instance explicitly: \\`defineSitemapSchema({ z })\\`.`,\n        `See https://nuxtseo.com/sitemap/guides/content`,\n      )\n    }\n  }\n}\n\nfunction buildSitemapObjectSchema(_z: typeof z) {\n  return _z.object({\n    loc: _z.string().optional(),\n    lastmod: _z.date().optional(),\n    changefreq: _z.union([_z.literal('always'), _z.literal('hourly'), _z.literal('daily'), _z.literal('weekly'), _z.literal('monthly'), _z.literal('yearly'), _z.literal('never')]).optional(),\n    priority: _z.number().optional(),\n    images: _z.array(_z.object({\n      loc: _z.string(),\n      caption: _z.string().optional(),\n      geo_location: _z.string().optional(),\n      title: _z.string().optional(),\n      license: _z.string().optional(),\n    })).optional(),\n    videos: _z.array(_z.object({\n      content_loc: _z.string(),\n      player_loc: _z.string().optional(),\n      duration: _z.string().optional(),\n      expiration_date: _z.date().optional(),\n      rating: _z.number().optional(),\n      view_count: _z.number().optional(),\n      publication_date: _z.date().optional(),\n      family_friendly: _z.boolean().optional(),\n      tag: _z.string().optional(),\n      category: _z.string().optional(),\n      restriction: _z.object({\n        relationship: _z.literal('allow').optional(),\n        value: _z.string().optional(),\n      }).optional(),\n      gallery_loc: _z.string().optional(),\n      price: _z.string().optional(),\n      requires_subscription: _z.boolean().optional(),\n      uploader: _z.string().optional(),\n    })).optional(),\n  }).optional()\n}\n"
  },
  {
    "path": "src/devtools.ts",
    "content": "import type { Resolver } from '@nuxt/kit'\nimport type { Nuxt } from 'nuxt/schema'\nimport type { ModuleOptions } from './module'\nimport { useNuxt } from '@nuxt/kit'\nimport { setupDevToolsUI as _setupDevToolsUI } from 'nuxtseo-shared/devtools'\n\nexport function setupDevToolsUI(_options: ModuleOptions, resolve: Resolver['resolve'], nuxt: Nuxt = useNuxt()) {\n  _setupDevToolsUI(\n    { route: '/__nuxt-sitemap', name: 'sitemap', title: 'Sitemap', icon: 'carbon:load-balancer-application' },\n    resolve,\n    nuxt,\n  )\n}\n"
  },
  {
    "path": "src/module.ts",
    "content": "import type { FileAfterParseHook } from '@nuxt/content'\nimport type { NitroRouteConfig } from 'nitropack/types'\nimport type {\n  ModuleOptions as _ModuleOptions,\n  AppSourceContext,\n  AutoI18nConfig,\n  FilterInput,\n  I18nIntegrationOptions,\n  ModuleRuntimeConfig,\n  MultiSitemapEntry,\n  SitemapDefinition,\n  SitemapSourceBase,\n  SitemapSourceInput,\n  SitemapSourceResolved,\n  SitemapUrl,\n} from './runtime/types'\nimport {\n  addPrerenderRoutes,\n  addServerHandler,\n  addServerImports,\n  addServerPlugin,\n  createResolver,\n  defineNuxtModule,\n  getNuxtModuleVersion,\n  hasNuxtModule,\n  hasNuxtModuleCompatibility,\n  resolveModule,\n  useLogger,\n} from '@nuxt/kit'\nimport { defu } from 'defu'\nimport { installNuxtSiteConfig } from 'nuxt-site-config/kit'\nimport { isPathFile } from 'nuxt-site-config/urls'\nimport { dirname } from 'pathe'\nimport { readPackageJSON } from 'pkg-types'\nimport { joinURL, withBase, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash, withTrailingSlash } from 'ufo'\nimport { setupDevToolsUI } from './devtools'\nimport { includesSitemapRoot, setupPrerenderHandler } from './prerender'\nimport { normaliseDate } from './runtime/server/sitemap/urlset/normalise'\nimport { registerTypeTemplates } from './templates'\nimport { normalizeFilters } from './utils-internal/filter'\nimport {\n  generatePathForI18nPages,\n  normalizeLocales,\n  splitPathForI18nLocales,\n} from './utils-internal/i18n'\nimport { createNitroPromise, createPagesPromise, getNuxtModuleOptions, isNuxtGenerate, resolveNitroPreset, resolveNuxtContentVersion } from './utils-internal/kit'\nimport { convertNuxtPagesToSitemapEntries, generateExtraRoutesFromNuxtConfig, resolveUrls } from './utils-internal/nuxtSitemap'\n\ndeclare global {\n  // eslint-disable-next-line vars-on-top\n  var __sitemapCollectionFilters: Map<string, (entry: any) => boolean> | undefined\n  // eslint-disable-next-line vars-on-top\n  var __sitemapCollectionOnUrlFns: Map<string, (url: any, entry: any, collection: string) => void> | undefined\n}\n\nexport type * from './runtime/types'\n\nexport interface ModuleOptions extends _ModuleOptions {}\n\nexport interface ModuleHooks {\n  /**\n   * Hook called after the prerender of the sitemaps is done.\n   */\n  'sitemap:prerender:done': (ctx: {\n    options: ModuleRuntimeConfig\n    sitemaps: { name: string, readonly content: string }[]\n  }) => void | Promise<void>\n}\n\ndeclare module '@nuxt/schema' {\n  interface NuxtHooks extends ModuleHooks {}\n}\n\nexport default defineNuxtModule<ModuleOptions>({\n  meta: {\n    name: '@nuxtjs/sitemap',\n    compatibility: {\n      nuxt: '>=3.9.0',\n    },\n    configKey: 'sitemap',\n  },\n  moduleDependencies: {\n    '@nuxtjs/i18n': {\n      version: '>=8',\n      optional: true,\n    },\n    'nuxt-i18n-micro': {\n      version: '>=1',\n      optional: true,\n    },\n    'nuxt-site-config': {\n      version: '>=3.2',\n    },\n    '@nuxt/content': {\n      version: '>=2',\n      optional: true,\n    },\n    '@nuxtjs/robots': {\n      version: '>=4',\n      optional: true,\n    },\n  },\n  defaults: {\n    enabled: true,\n    credits: true,\n    cacheMaxAgeSeconds: 60 * 10, // cache for 10 minutes\n    minify: false,\n    debug: false,\n    defaultSitemapsChunkSize: 1000,\n    autoLastmod: false,\n    discoverImages: true,\n    discoverVideos: true,\n    urls: [],\n    sortEntries: true,\n    sitemapsPathPrefix: '/__sitemap__/',\n    xsl: '/__sitemap__/style.xsl',\n    xslTips: true,\n    strictNuxtContentPaths: false,\n    runtimeCacheStorage: true,\n    sitemapName: 'sitemap.xml',\n    // cacheControlHeader: 'max-age=600, must-revalidate',\n    defaults: {},\n    // index sitemap options filtering\n    include: [],\n    exclude: ['/_**'],\n    // sources\n    sources: [],\n    excludeAppSources: [],\n    zeroRuntime: false,\n  },\n  async setup(config, nuxt) {\n    const { resolve } = createResolver(import.meta.url)\n    const { name, version } = await readPackageJSON(resolve('../package.json'))\n    const logger = useLogger(name)\n    logger.level = (config.debug || nuxt.options.debug) ? 4 : 3\n    if (config.enabled === false) {\n      logger.debug('The module is disabled, skipping setup.')\n      return\n    }\n    // /_nuxt/\n    config.exclude!.push(`${withTrailingSlash(nuxt.options.app.buildAssetsDir)}**`)\n    nuxt.options.alias['#sitemap'] = resolve('./runtime')\n    nuxt.options.nitro.alias = nuxt.options.nitro.alias || {}\n    nuxt.options.nitro.alias['#sitemap'] = resolve('./runtime')\n    nuxt.options.experimental.extraPageMetaExtractionKeys = nuxt.options.experimental.extraPageMetaExtractionKeys || []\n    nuxt.options.experimental.extraPageMetaExtractionKeys.push('sitemap')\n    config.xslColumns = config.xslColumns || [\n      { label: 'URL', width: '50%' },\n      { label: 'Images', width: '25%', select: 'count(image:image)' },\n      {\n        label: 'Last Updated',\n        width: '25%',\n        select: 'concat(substring(sitemap:lastmod,0,11),concat(\\' \\', substring(sitemap:lastmod,12,5)),concat(\\' \\', substring(sitemap:lastmod,20,6)))',\n      },\n    ]\n\n    if (config.autoLastmod) {\n      config.defaults = config.defaults || {}\n      config.defaults.lastmod = normaliseDate(new Date())\n    }\n\n    // warn about bad config\n    const normalizedSitemaps = typeof config.sitemaps === 'boolean' ? {} : config.sitemaps || {}\n    if (!nuxt.options._prepare && Object.keys(normalizedSitemaps).length) {\n      // if the only key of config.sitemaps is `index` then we can skip this logic\n      const isSitemapIndexOnly = typeof normalizedSitemaps?.index !== 'undefined' && Object.keys(normalizedSitemaps).length === 1\n      if (!isSitemapIndexOnly) {\n        // if the user is doing multi-sitempas using the sitemaps config, we warn when root keys are used as they won't do anything\n        const warnForIgnoredKey = (key: string) => {\n          logger.warn(`You are using multiple-sitemaps but have provided \\`sitemap.${key}\\` in your Nuxt config. This will be ignored, please move it to the child sitemap config.`)\n          logger.warn('Learn more at: https://nuxtseo.com/sitemap/guides/multi-sitemaps')\n        }\n\n        switch (true) {\n          case (config?.sources?.length || 0) > 0:\n            warnForIgnoredKey('sources')\n            break\n          case config?.includeAppSources !== undefined:\n            warnForIgnoredKey('includeAppSources')\n            break\n        }\n      }\n    }\n\n    // for trailing slashes / canonical absolute urls\n    await installNuxtSiteConfig()\n    const userGlobalSources: SitemapSourceInput[] = [\n      ...config.sources || [],\n    ]\n    const appGlobalSources: (SitemapSourceBase | SitemapSourceResolved)[] = []\n\n    nuxt.options.nitro.storage = nuxt.options.nitro.storage || {}\n    // provide cache storage for prerendering\n    if (config.runtimeCacheStorage && !nuxt.options.dev && typeof config.runtimeCacheStorage === 'object')\n      nuxt.options.nitro.storage.sitemap = config.runtimeCacheStorage\n\n    if (!config.sitemapName.endsWith('xml')) {\n      const newName = `${config.sitemapName.split('.')[0]}.xml`\n      logger.warn(`You have provided a \\`sitemapName\\` that does not end with \\`.xml\\`. This is not supported by search engines, renaming to \\`${newName}\\`.`)\n      config.sitemapName = newName\n    }\n    config.sitemapName = withoutLeadingSlash(config.sitemapName)\n\n    let usingMultiSitemaps = !!config.sitemaps\n\n    let isI18nMapped = false\n    let nuxtI18nConfig = {} as I18nIntegrationOptions\n    let resolvedAutoI18n: false | AutoI18nConfig = typeof config.autoI18n === 'boolean' ? false : config.autoI18n || false\n    const hasDisabledAutoI18n = typeof config.autoI18n === 'boolean' && !config.autoI18n\n    let normalisedLocales: AutoI18nConfig['locales'] = []\n    let usingI18nPages = false\n    const i18nModule = ['@nuxtjs/i18n', 'nuxt-i18n-micro'].find(s => hasNuxtModule(s))\n    if (i18nModule) {\n      const i18nVersion = await getNuxtModuleVersion(i18nModule)\n      if (i18nVersion && i18nModule === '@nuxtjs/i18n' && !await hasNuxtModuleCompatibility(i18nModule, '>=8'))\n        logger.warn(`You are using ${i18nModule} v${i18nVersion}. For the best compatibility, please upgrade to ${i18nModule} v8.0.0 or higher.`)\n      nuxtI18nConfig = (await getNuxtModuleOptions(i18nModule) || {}) as I18nIntegrationOptions\n      normalisedLocales = normalizeLocales(nuxtI18nConfig)\n      usingI18nPages = !!Object.keys(nuxtI18nConfig.pages || {}).length\n      if (usingI18nPages && !hasDisabledAutoI18n) {\n        const i18nPagesSources: SitemapSourceBase = {\n          context: {\n            name: `${i18nModule}:pages`,\n            description: 'Generated from your i18n.pages config.',\n            tips: [\n              'You can disable this with `autoI18n: false`.',\n            ],\n          },\n          urls: [],\n        }\n        for (const pageLocales of Object.values(nuxtI18nConfig?.pages as Record<string, Record<string, string>>)) {\n          for (const localeCode in pageLocales) {\n            const locale = normalisedLocales.find(l => l.code === localeCode)\n            // add root entry for default locale and ignore dynamic routes\n            if (!locale || !pageLocales[localeCode] || pageLocales[localeCode].includes('['))\n              continue\n\n            // add to sitemap\n            const alternatives = Object.keys(pageLocales)\n              // @ts-expect-error untyped\n              .filter(l => pageLocales[l] !== false) // filter out disabled routes\n              .map(l => ({\n                hreflang: normalisedLocales.find(nl => nl.code === l)?._hreflang || l,\n                // @ts-expect-error untyped\n                href: generatePathForI18nPages({ localeCode: l, pageLocales: pageLocales[l], nuxtI18nConfig, normalisedLocales }),\n              }))\n            // @ts-expect-error untyped\n            if (alternatives.length && nuxtI18nConfig.defaultLocale && pageLocales[nuxtI18nConfig.defaultLocale] && pageLocales[nuxtI18nConfig.defaultLocale] !== false)\n              // @ts-expect-error untyped\n              alternatives.push({ hreflang: 'x-default', href: generatePathForI18nPages({ normalisedLocales, localeCode: nuxtI18nConfig.defaultLocale, pageLocales: pageLocales[nuxtI18nConfig.defaultLocale], nuxtI18nConfig }) })\n            i18nPagesSources.urls!.push({\n              _sitemap: locale._sitemap,\n              loc: generatePathForI18nPages({ normalisedLocales, localeCode, pageLocales: pageLocales[localeCode], nuxtI18nConfig }),\n              alternatives,\n            })\n            // add extra loc with the default locale code prefix on prefix and default strategy\n            if (nuxtI18nConfig.strategy === 'prefix_and_default' && localeCode === nuxtI18nConfig.defaultLocale) {\n              i18nPagesSources.urls!.push({\n                _sitemap: locale._sitemap,\n                loc: generatePathForI18nPages({ normalisedLocales, localeCode, pageLocales: pageLocales[localeCode], nuxtI18nConfig, forcedStrategy: 'prefix' }),\n                alternatives,\n              })\n            }\n          }\n        }\n        appGlobalSources.push(i18nPagesSources)\n        // pages will be wrong\n        if (Array.isArray(config.excludeAppSources))\n          config.excludeAppSources.push('nuxt:pages')\n      }\n      else {\n        if (!normalisedLocales.length)\n          logger.warn(`You are using ${i18nModule} but have not configured any locales, this will cause issues with ${name}. Please configure \\`locales\\`.`)\n      }\n      const hasSetAutoI18n = typeof config.autoI18n === 'object' && Object.keys(config.autoI18n).length\n      const hasI18nConfigForAlternatives = nuxtI18nConfig.differentDomains || usingI18nPages || (nuxtI18nConfig.strategy !== 'no_prefix' && nuxtI18nConfig.locales)\n      if (!hasSetAutoI18n && !hasDisabledAutoI18n && hasI18nConfigForAlternatives) {\n        resolvedAutoI18n = {\n          differentDomains: nuxtI18nConfig.differentDomains,\n          defaultLocale: nuxtI18nConfig.defaultLocale!,\n          locales: normalisedLocales,\n          strategy: nuxtI18nConfig.strategy as 'prefix' | 'prefix_except_default' | 'prefix_and_default',\n          // @ts-expect-error untyped\n          pages: nuxtI18nConfig.pages,\n        }\n      }\n      let canI18nMap = !hasDisabledAutoI18n && config.sitemaps !== false && nuxtI18nConfig.strategy !== 'no_prefix'\n      if (typeof config.sitemaps === 'object') {\n        const sitemapEntries = Object.entries(config.sitemaps).filter(([k]) => k !== 'index')\n        const isSitemapIndexOnly = sitemapEntries.length === 0\n        // Allow i18n mapping if any sitemap has includeAppSources\n        const hasIncludeAppSources = sitemapEntries.some(([_, v]) => v && typeof v === 'object' && (v as SitemapDefinition).includeAppSources)\n        if (!isSitemapIndexOnly && !hasIncludeAppSources)\n          canI18nMap = false\n      }\n      // if they haven't set `sitemaps` explicitly then we can set it up automatically for them\n      if (canI18nMap && resolvedAutoI18n) {\n        const existingSitemaps: Record<string, unknown> = typeof config.sitemaps === 'object' ? config.sitemaps : {}\n        const i18nSitemaps: Array<{ name: string, cfg: SitemapDefinition }> = []\n        const nonI18nSitemaps: Record<string, unknown> = {}\n\n        // Process existing sitemaps - separate includeAppSources from others\n        for (const [name, cfg] of Object.entries(existingSitemaps)) {\n          if (name === 'index')\n            continue\n          if (cfg && typeof cfg === 'object' && (cfg as SitemapDefinition).includeAppSources) {\n            i18nSitemaps.push({ name, cfg: cfg as SitemapDefinition })\n          }\n          else {\n            // Keep non-includeAppSources sitemaps as-is\n            nonI18nSitemaps[name] = cfg\n          }\n        }\n\n        // Build new sitemaps config\n        const newSitemaps: Record<string, unknown> = {\n          index: [...((existingSitemaps.index as unknown[]) || []), ...(config.appendSitemaps || [])],\n        }\n\n        // Expand each includeAppSources sitemap to per-locale sitemaps\n        // If no custom sitemaps defined, use standard locale names (e.g., \"en\")\n        // If custom sitemaps defined, use \"{locale}-{name}\" format (e.g., \"en-pages\")\n        const hasCustomI18nSitemaps = i18nSitemaps.length > 0\n        if (hasCustomI18nSitemaps) {\n          for (const { name, cfg } of i18nSitemaps) {\n            for (const locale of resolvedAutoI18n.locales) {\n              newSitemaps[`${locale._sitemap}-${name}`] = {\n                includeAppSources: true,\n                ...(cfg.exclude?.length && { exclude: cfg.exclude }),\n                ...(cfg.include?.length && { include: cfg.include }),\n              }\n            }\n          }\n        }\n        else {\n          // Default behavior: create standard locale sitemaps\n          for (const locale of resolvedAutoI18n.locales) {\n            newSitemaps[locale._sitemap] = { includeAppSources: true }\n          }\n        }\n\n        // Add back non-i18n sitemaps\n        Object.assign(newSitemaps, nonI18nSitemaps)\n\n        // @ts-expect-error untyped\n        config.sitemaps = newSitemaps\n\n        isI18nMapped = true\n        usingMultiSitemaps = true\n      }\n    }\n\n    // @ts-expect-error untyped\n    nuxt.hooks.hook('robots:config', (robotsConfig) => {\n      robotsConfig.sitemap.push(usingMultiSitemaps ? '/sitemap_index.xml' : `/${config.sitemapName}`)\n    })\n    // avoid issues with module order\n    nuxt.hooks.hook('modules:done', async () => {\n      const robotsModuleName = ['nuxt-simple-robots', '@nuxtjs/robots'].find(s => hasNuxtModule(s))\n      let needsRobotsPolyfill = true\n      if (robotsModuleName) {\n        const robotsVersion = await getNuxtModuleVersion(robotsModuleName)\n        // we want to keep versions in sync\n        if (robotsVersion && !await hasNuxtModuleCompatibility(robotsModuleName, '>=4'))\n          logger.warn(`You are using ${robotsModuleName} v${robotsVersion}. For the best compatibility, please upgrade to ${robotsModuleName} v4.0.0 or higher.`)\n        else\n          needsRobotsPolyfill = false\n      }\n      // this is added in v4 of Nuxt Robots\n      if (needsRobotsPolyfill) {\n        nuxt.options.nitro.alias = nuxt.options.nitro.alias || {}\n        nuxt.options.nitro.alias['#internal/nuxt-robots'] = resolve('./runtime/server/robots-polyfill')\n        addServerImports([{\n          name: 'getPathRobotConfig',\n          as: 'getPathRobotConfig',\n          from: resolve('./runtime/server/robots-polyfill/getPathRobotConfig'),\n        }])\n      }\n    })\n\n    registerTypeTemplates()\n    // check if the user provided route /api/_sitemap-urls exists\n    const prerenderedRoutes = (nuxt.options.nitro.prerender?.routes || []) as string[]\n    let prerenderSitemap = isNuxtGenerate() || includesSitemapRoot(config.sitemapName, prerenderedRoutes)\n\n    if (resolveNitroPreset() === 'vercel-edge') {\n      logger.warn('Runtime sitemaps are not supported on Vercel Edge, falling back to prerendering sitemaps.')\n      prerenderSitemap = true\n    }\n\n    // zeroRuntime forces prerendering\n    if (config.zeroRuntime && !prerenderSitemap) {\n      prerenderSitemap = true\n      addPrerenderRoutes('/sitemap.xml')\n      if (!nuxt.options.dev)\n        logger.info('`zeroRuntime` enabled - sitemap routes will be prerendered.')\n    }\n    // base path for route handlers\n    const routesPath = config.zeroRuntime\n      ? './runtime/server/routes/__zero-runtime'\n      : './runtime/server/routes'\n\n    const routeRules: NitroRouteConfig = {}\n    nuxt.options.nitro.routeRules = nuxt.options.nitro.routeRules || {}\n    if (prerenderSitemap) {\n      // add route rules for sitemap xmls so they're rendered properly\n      routeRules.headers = {\n        'Content-Type': 'text/xml; charset=UTF-8',\n        'Cache-Control': config.cacheMaxAgeSeconds ? `public, max-age=${config.cacheMaxAgeSeconds}, must-revalidate` : 'no-cache, no-store',\n        'X-Sitemap-Prerendered': new Date().toISOString(),\n      }\n    }\n    // The xsl handler sets its own Content-Type header, so no routeRule needed for it.\n    // Only register the per-sitemap routeRules entries when they actually carry content.\n    // An empty {} rule still gets matched on every request via the routeRules matcher,\n    // adding measurable overhead on unrelated routes for no benefit.\n    const hasRouteRuleContent = Object.keys(routeRules).length > 0\n    if (usingMultiSitemaps) {\n      nuxt.options.nitro.routeRules['/sitemap.xml'] = { redirect: withBase('/sitemap_index.xml', nuxt.options.app.baseURL) }\n      if (hasRouteRuleContent) {\n        nuxt.options.nitro.routeRules['/sitemap_index.xml'] = routeRules\n        if (typeof config.sitemaps === 'object') {\n          for (const k in config.sitemaps) {\n            if (k === 'index')\n              continue\n            nuxt.options.nitro.routeRules[joinURL(config.sitemapsPathPrefix || '', `/${k}.xml`)] = routeRules\n\n            const sitemapConfig = config.sitemaps[k]!\n            if (sitemapConfig.chunks)\n              nuxt.options.nitro.routeRules[joinURL(config.sitemapsPathPrefix || '', `/${k}-*.xml`)] = routeRules\n          }\n        }\n        else {\n          nuxt.options.nitro.routeRules[joinURL(config.sitemapsPathPrefix || '', `/[0-9]+.xml`)] = routeRules\n        }\n      }\n    }\n    else if (hasRouteRuleContent) {\n      nuxt.options.nitro.routeRules[`/${config.sitemapName}`] = routeRules\n    }\n\n    // skip experimental runtime plugins in zeroRuntime mode\n    if (config.zeroRuntime && (config.experimentalWarmUp || config.experimentalCompression))\n      logger.warn('`experimentalWarmUp` and `experimentalCompression` are ignored in zeroRuntime mode.')\n    if (!config.zeroRuntime) {\n      if (config.experimentalWarmUp)\n        addServerPlugin(resolve('./runtime/server/plugins/warm-up'))\n      if (config.experimentalCompression)\n        addServerPlugin(resolve('./runtime/server/plugins/compression'))\n    }\n\n    // @ts-expect-error untyped\n    const isNuxtContentDocumentDriven = (!!nuxt.options.content?.documentDriven || config.strictNuxtContentPaths)\n    const contentVersion = await resolveNuxtContentVersion()\n    const isNuxtContentV3 = contentVersion && contentVersion.version === 3\n    const nuxtV3Collections = new Set<string>()\n    const isNuxtContentV2 = contentVersion && contentVersion.version === 2\n    if (isNuxtContentV3) {\n      // check if content was loaded first\n      if (nuxt.options._installedModules.some(m => m.meta.name === 'Content')) {\n        logger.warn('You have loaded `@nuxt/content` before `@nuxtjs/sitemap`, this may cause issues with the integration. Please ensure `@nuxtjs/sitemap` is loaded first.')\n      }\n      // // exclude /__nuxt_content\n      config.exclude!.push('/__nuxt_content/**')\n      const needsCustomAlias = await hasNuxtModuleCompatibility('@nuxt/content', '<3.6.0')\n      if (needsCustomAlias) {\n        nuxt.options.alias['#sitemap/content-v3-nitro-path'] = resolve(dirname(resolveModule('@nuxt/content')), 'runtime/nitro')\n        nuxt.options.alias['@nuxt/content/nitro'] = resolve('./runtime/server/content-compat')\n      }\n      nuxt.hooks.hook('content:file:afterParse' as any, (ctx: FileAfterParseHook) => {\n        try {\n          const content = ctx.content as any as {\n            body: { value: [string, Record<string, any>][] }\n            sitemap?: Partial<SitemapUrl> | false\n            path: string\n            updatedAt?: string\n          } & Record<string, any>\n          nuxtV3Collections.add(ctx.collection.name)\n          // ignore .dot files and paths\n          if (String(ctx.content.path).includes('/.')) {\n            ctx.content.sitemap = null\n            return\n          }\n          if (!ctx.collection.fields || !('sitemap' in ctx.collection.fields)) {\n            ctx.content.sitemap = null\n            return\n          }\n          // support sitemap: false\n          if (typeof content.sitemap !== 'undefined' && !content.sitemap) {\n            ctx.content.sitemap = null\n            return\n          }\n          if (ctx.content.robots === false) {\n            ctx.content.sitemap = null\n            return\n          }\n          // add any top level images\n          const images: SitemapUrl['images'] = []\n          if (config.discoverImages) {\n            images.push(...(content.body?.value\n              ?.filter(c =>\n                ['image', 'img', 'nuxtimg', 'nuxt-img'].includes(c[0]),\n              )\n              .filter(c => c[1]?.src)\n              .map(c => ({ loc: c[1].src })) || []),\n            )\n          }\n          // Note: videos only supported through prerendering for simpler logic\n\n          const lastmod = content.seo?.articleModifiedTime || content.updatedAt\n          const defaults: Partial<SitemapUrl> = {\n            loc: content.path,\n          }\n          if (images.length > 0)\n            defaults.images = images\n          if (lastmod)\n            defaults.lastmod = lastmod\n          ctx.content.sitemap = defu(typeof content.sitemap === 'object' ? content.sitemap : {}, defaults) as Partial<SitemapUrl>\n        }\n        catch (e) {\n          logger.warn(`Failed to process sitemap data for content file (collection: ${ctx.collection?.name}, path: ${ctx.content?.path}), skipping.`, e)\n        }\n      })\n\n      // inject filter functions and loc prefixes as virtual modules\n      nuxt.hook('nitro:config', (nitroConfig) => {\n        const filterEntries: string[] = []\n        if (globalThis.__sitemapCollectionFilters) {\n          for (const [name, filterFn] of globalThis.__sitemapCollectionFilters.entries())\n            filterEntries.push(`filters.set(${JSON.stringify(name)}, ${filterFn.toString()})`)\n        }\n        const onUrlEntries: string[] = []\n        if (globalThis.__sitemapCollectionOnUrlFns) {\n          for (const [name, fn] of globalThis.__sitemapCollectionOnUrlFns.entries())\n            onUrlEntries.push(`onUrlFns.set(${JSON.stringify(name)}, ${fn.toString()})`)\n        }\n\n        nitroConfig.virtual = nitroConfig.virtual || {}\n        nitroConfig.virtual['#sitemap/content-filters'] = `export const filters = new Map()\\n${filterEntries.join('\\n')}`\n        nitroConfig.virtual['#sitemap/content-on-url'] = `export const onUrlFns = new Map()\\n${onUrlEntries.join('\\n')}`\n      })\n      addServerHandler({\n        route: '/__sitemap__/nuxt-content-urls.json',\n        handler: resolve('./runtime/server/routes/__sitemap__/nuxt-content-urls-v3'),\n      })\n      if (config.strictNuxtContentPaths) {\n        logger.warn('You have set `strictNuxtContentPaths: true` but are using @nuxt/content v3. This is not required, please remove it.')\n      }\n      appGlobalSources.push({\n        context: {\n          name: '@nuxt/content@v3:urls',\n          description: 'Generated from your markdown files.',\n          tips: nuxtV3Collections.size\n            ? [`Parsing the following collections: ${Array.from(nuxtV3Collections).join(', ')}`]\n            : ['No collections found. Make sure your content collections have a `path` field.'],\n        },\n        fetch: '/__sitemap__/nuxt-content-urls.json',\n      })\n    }\n    else if (isNuxtContentV2) {\n      addServerPlugin(resolve('./runtime/server/plugins/nuxt-content-v2'))\n      addServerHandler({\n        route: '/__sitemap__/nuxt-content-urls.json',\n        handler: resolve('./runtime/server/routes/__sitemap__/nuxt-content-urls-v2'),\n      })\n      const tips: string[] = []\n      // @ts-expect-error untyped\n      if (nuxt.options.content?.documentDriven)\n        tips.push('Enabled because you\\'re using `@nuxt/content` with `documentDriven: true`.')\n      else if (config.strictNuxtContentPaths)\n        tips.push('Enabled because you\\'ve set `config.strictNuxtContentPaths: true`.')\n      else\n        tips.push('You can provide a `sitemap` key in your markdown frontmatter to configure specific URLs. Make sure you include a `loc`.')\n\n      appGlobalSources.push({\n        context: {\n          name: '@nuxt/content@v2:urls',\n          description: 'Generated from your markdown files.',\n          tips,\n        },\n        fetch: '/__sitemap__/nuxt-content-urls.json',\n      })\n    }\n\n    // config -> sitemaps\n    const sitemaps: ModuleRuntimeConfig['sitemaps'] = {}\n\n    if (usingMultiSitemaps) {\n      addServerHandler({\n        route: '/sitemap_index.xml',\n        handler: resolve(`${routesPath}/sitemap_index.xml`),\n        lazy: true,\n        middleware: false,\n      })\n      if (config.sitemapsPathPrefix && config.sitemapsPathPrefix !== '/') {\n        addServerHandler({\n          route: joinURL(config.sitemapsPathPrefix, `/**:sitemap`),\n          handler: resolve(`${routesPath}/sitemap/[sitemap].xml`),\n          lazy: true,\n          middleware: false,\n        })\n      }\n      else {\n        // when prefix is '/' or false, register individual sitemap routes\n        // and explicit chunk routes since h3 doesn't support wildcard patterns\n        const sitemapNames = Object.keys(config.sitemaps || {})\n        let hasChunkedSitemaps = false\n        for (const sitemapName of sitemapNames) {\n          if (sitemapName === 'index')\n            continue\n          const sitemapConfig = config.sitemaps![sitemapName as keyof typeof config.sitemaps] as MultiSitemapEntry[string]\n\n          // Register the base sitemap route\n          addServerHandler({\n            route: withLeadingSlash(`${sitemapName}.xml`),\n            handler: resolve(`${routesPath}/sitemap/[sitemap].xml`),\n            lazy: true,\n            middleware: false,\n          })\n\n          if (sitemapConfig.chunks)\n            hasChunkedSitemaps = true\n        }\n\n        // For chunked sitemaps, register individual routes for each chunk index\n        // since h3 doesn't support wildcard patterns like /sitemap-*.xml at root level.\n        // This is a limitation when using sitemapsPathPrefix: '/' - we pre-register routes\n        // for up to 20 chunks per sitemap (20,000 URLs with default chunk size of 1000).\n        // For larger sitemaps, use a different prefix like '/sitemaps/' instead of '/'.\n        if (hasChunkedSitemaps) {\n          const maxChunks = 20\n          for (const sitemapName of sitemapNames) {\n            if (sitemapName === 'index')\n              continue\n            const sitemapConfig = config.sitemaps![sitemapName as keyof typeof config.sitemaps] as MultiSitemapEntry[string]\n            if (sitemapConfig.chunks) {\n              for (let i = 0; i < maxChunks; i++) {\n                addServerHandler({\n                  route: `/${sitemapName}-${i}.xml`,\n                  handler: resolve(`${routesPath}/sitemap/[sitemap].xml`),\n                  lazy: true,\n                  middleware: false,\n                })\n              }\n            }\n          }\n        }\n      }\n      sitemaps.index = {\n        sitemapName: 'index',\n        _route: withBase('sitemap_index.xml', nuxt.options.app.baseURL || '/'),\n        // @ts-expect-error untyped\n        sitemaps: [...(config.sitemaps!.index || []), ...(config.appendSitemaps || [])],\n      }\n      if (typeof config.sitemaps === 'object') {\n        for (const sitemapName in config.sitemaps) {\n          if (sitemapName === 'index')\n            continue\n          const definition = config.sitemaps[sitemapName] as MultiSitemapEntry[string]\n          const sitemapConfig = defu(\n            {\n              sitemapName,\n              _route: withBase(joinURL(config.sitemapsPathPrefix || '', `${sitemapName}.xml`), nuxt.options.app.baseURL || '/'),\n              _hasSourceChunk: typeof definition.urls !== 'undefined' || definition.sources?.length,\n            },\n            { ...definition, urls: undefined, sources: undefined },\n            { include: config.include, exclude: config.exclude },\n          ) as ModuleRuntimeConfig['sitemaps'][string]\n\n          // Set up chunking if enabled\n          if (definition.chunks) {\n            // Validate chunk configuration\n            let chunkSize = config.defaultSitemapsChunkSize || 1000\n\n            if (typeof definition.chunks === 'number') {\n              if (definition.chunks <= 0) {\n                logger.warn(`Invalid chunks value (${definition.chunks}) for sitemap \"${sitemapName}\". Using default.`)\n              }\n              else {\n                chunkSize = definition.chunks\n              }\n            }\n\n            if (definition.chunkSize !== undefined) {\n              if (typeof definition.chunkSize !== 'number' || definition.chunkSize <= 0) {\n                logger.warn(`Invalid chunkSize value (${definition.chunkSize}) for sitemap \"${sitemapName}\". Using default.`)\n              }\n              else {\n                chunkSize = definition.chunkSize // chunkSize takes precedence\n              }\n            }\n\n            sitemapConfig._isChunking = true\n            sitemapConfig._chunkSize = chunkSize\n            sitemapConfig.chunks = definition.chunks\n            sitemapConfig.chunkSize = definition.chunkSize\n          }\n\n          sitemaps[sitemapName as keyof typeof sitemaps] = sitemapConfig\n        }\n      }\n      else {\n        // we have to register it as a middleware we can't match the URL pattern\n        sitemaps.chunks = {\n          sitemapName: 'chunks',\n          defaults: config.defaults,\n          include: config.include,\n          exclude: config.exclude,\n          includeAppSources: true,\n        }\n      }\n    }\n    else {\n      // note: we don't need urls for the root sitemap, only child sitemaps\n      sitemaps[config.sitemapName] = <SitemapDefinition> {\n        sitemapName: config.sitemapName,\n        route: withBase(config.sitemapName, nuxt.options.app.baseURL || '/'), // will contain the xml\n        defaults: config.defaults,\n        include: config.include,\n        exclude: config.exclude,\n        includeAppSources: true,\n      }\n    }\n\n    // for each sitemap, we need to transform the include and exclude\n    // if the include or exclude has a URL without a locale prefix, then we insert all locale prefixes\n    if (resolvedAutoI18n && usingI18nPages && !hasDisabledAutoI18n) {\n      const pages = nuxtI18nConfig?.pages || {} as Record<string, Record<string, string>>\n      for (const sitemapName in sitemaps) {\n        if (['index', 'chunks'].includes(sitemapName))\n          continue\n        const sitemap = sitemaps[sitemapName]!\n        function mapToI18nPages(path: FilterInput): FilterInput[] {\n          if (typeof path !== 'string')\n            return [path]\n          const withoutSlashes = withoutTrailingSlash(withoutLeadingSlash(path)).replace('/index', '')\n          if (pages && withoutSlashes in pages) {\n            const pageLocales = pages[withoutSlashes]\n            if (pageLocales) {\n              return Object.keys(pageLocales).map(localeCode => withLeadingSlash(generatePathForI18nPages({\n                localeCode,\n                pageLocales: pageLocales[localeCode] as string,\n                nuxtI18nConfig,\n                normalisedLocales,\n              })))\n            }\n          }\n          let match = [path]\n          // alternatively see if the path matches the default locale within\n          Object.values(pages).forEach((pageLocales) => {\n            // @ts-expect-error untyped\n            if (pageLocales && nuxtI18nConfig.defaultLocale in pageLocales && pageLocales[nuxtI18nConfig.defaultLocale] === path)\n              match = Object.keys(pageLocales).map(localeCode => withLeadingSlash(generatePathForI18nPages({ localeCode, pageLocales: pageLocales[localeCode], nuxtI18nConfig, normalisedLocales })))\n          })\n          return match\n        }\n        sitemap.include = (sitemap.include || []).flatMap(path => mapToI18nPages(path))\n        sitemap.exclude = (sitemap.exclude || []).flatMap(path => mapToI18nPages(path))\n      }\n    }\n    if (resolvedAutoI18n && resolvedAutoI18n.locales && resolvedAutoI18n.strategy !== 'no_prefix') {\n      const i18n = resolvedAutoI18n\n      for (const sitemapName in sitemaps) {\n        if (['index', 'chunks'].includes(sitemapName))\n          continue\n        const sitemap = sitemaps[sitemapName]!\n        sitemap.include = (sitemap.include || []).map(path => splitPathForI18nLocales(path, i18n)).flat()\n        sitemap.exclude = (sitemap.exclude || []).map(path => splitPathForI18nLocales(path, i18n)).flat()\n      }\n    }\n    for (const sitemapName in sitemaps) {\n      const sitemap = sitemaps[sitemapName]!\n      // we need to normalize the RegExp to a string because of the useRuntimeConfig can't jsonify it\n      // note: this needs to occur after i18n has extended the rules\n      sitemap.include = normalizeFilters(sitemap.include)\n      sitemap.exclude = normalizeFilters(sitemap.exclude)\n    }\n\n    const runtimeConfig: ModuleRuntimeConfig = {\n      isI18nMapped,\n      sitemapName: config.sitemapName,\n      isMultiSitemap: usingMultiSitemaps,\n      excludeAppSources: config.excludeAppSources,\n      cacheMaxAgeSeconds: nuxt.options.dev ? 0 : config.cacheMaxAgeSeconds,\n\n      autoLastmod: config.autoLastmod,\n      defaultSitemapsChunkSize: config.defaultSitemapsChunkSize,\n\n      minify: config.minify,\n      sortEntries: config.sortEntries,\n      debug: config.debug,\n      // needed for nuxt/content integration and prerendering\n      discoverImages: config.discoverImages,\n      discoverVideos: config.discoverVideos,\n      sitemapsPathPrefix: config.sitemapsPathPrefix,\n\n      /* @nuxt/content */\n      isNuxtContentDocumentDriven,\n\n      /* xsl styling */\n      xsl: config.xsl,\n      xslTips: config.xslTips,\n      xslColumns: config.xslColumns,\n      credits: config.credits,\n      version: version!,\n      sitemaps,\n    }\n    if (resolvedAutoI18n)\n      runtimeConfig.autoI18n = resolvedAutoI18n\n    if (hasDisabledAutoI18n)\n      runtimeConfig.hasDisabledAutoI18n = true\n\n    // Split into a small dynamic slice (kept in runtimeConfig for env-var overrides)\n    // and a large static slice (emitted as a virtual module). Nitro deep-clones the\n    // entire runtimeConfig on the first useRuntimeConfig(event) per request, so anything\n    // sitting in there is per-request overhead for every route in the app, not just sitemap routes.\n    const dynamicRuntimeConfig = {\n      cacheMaxAgeSeconds: runtimeConfig.cacheMaxAgeSeconds,\n      debug: runtimeConfig.debug,\n    }\n    // cacheMaxAgeSeconds is duplicated: dynamic copy lets users override the HTTP cache header via\n    // env vars at runtime; static copy is read at server startup to size the in-memory cache layer\n    // (defineCachedFunction takes maxAge as a static option, not a runtime callback).\n    const { debug: _d, ...staticRuntimeConfig } = runtimeConfig\n    // @ts-expect-error untyped\n    nuxt.options.runtimeConfig.sitemap = dynamicRuntimeConfig\n    nuxt.hook('nitro:config', (nitroConfig) => {\n      nitroConfig.virtual = nitroConfig.virtual || {}\n      nitroConfig.virtual['#sitemap-virtual/static-config.mjs']\n        = `export default ${JSON.stringify(staticRuntimeConfig)}`\n    })\n\n    // debug endpoints - skip in production zeroRuntime as they pull in full sitemap code\n    if ((config.debug || nuxt.options.dev) && !(config.zeroRuntime && !nuxt.options.dev)) {\n      addServerHandler({\n        route: '/__sitemap__/debug.json',\n        handler: resolve('./runtime/server/routes/__sitemap__/debug'),\n      })\n      if (nuxt.options.dev) {\n        addServerHandler({\n          route: '/__sitemap__/debug-production.json',\n          handler: resolve('./runtime/server/routes/__sitemap__/debug-production'),\n        })\n      }\n\n      // Register handlers for all sitemaps in dev/debug mode\n      if (usingMultiSitemaps) {\n        addServerHandler({\n          route: '/__sitemap__/**:sitemap',\n          handler: resolve('./runtime/server/routes/sitemap/[sitemap].xml'),\n          lazy: true,\n          middleware: true,\n        })\n      }\n\n      setupDevToolsUI(config, resolve)\n    }\n\n    const imports: typeof nuxt.options.imports.imports = [\n      {\n        from: resolve('./runtime/server/composables/defineSitemapEventHandler'),\n        name: 'defineSitemapEventHandler',\n      },\n      {\n        from: resolve('./runtime/server/composables/asSitemapUrl'),\n        name: 'asSitemapUrl',\n      },\n    ]\n    addServerImports(imports)\n\n    // we may not have pages\n    const pagesPromise = createPagesPromise()\n    const nitroPromise = createNitroPromise()\n    let resolvedConfigUrls = false\n\n    const isValidPrerenderRoute = (r: any) => {\n      // avoid adding fallback pages to sitemap\n      if (['/200.html', '/404.html', '/index.html'].includes(r.route) || r.error || isPathFile(r.route))\n        return false\n      return r.contentType?.includes('text/html')\n    }\n\n    const generateGlobalSources = async () => {\n      const { routeRules } = generateExtraRoutesFromNuxtConfig()\n      const nitro = await nitroPromise\n      const prerenderedRoutes = nitro._prerenderedRoutes || []\n      const prerenderUrlsFinal = [\n        ...prerenderedRoutes\n          .filter(isValidPrerenderRoute)\n          .map(r => r._sitemap)\n          .filter(entry => entry && (typeof entry === 'string' || entry._sitemap !== false)),\n      ]\n      if (config.debug) {\n        logger.info('Prerendered routes:', prerenderUrlsFinal)\n      }\n      const pageSource = convertNuxtPagesToSitemapEntries(await pagesPromise, {\n        isI18nMapped,\n        autoLastmod: config.autoLastmod,\n        defaultLocale: nuxtI18nConfig.defaultLocale || 'en',\n        strategy: nuxtI18nConfig.strategy || 'no_prefix',\n        routesNameSeparator: nuxtI18nConfig.routesNameSeparator,\n        normalisedLocales,\n        filter: {\n          include: normalizeFilters(config.include) as (string | RegExp)[],\n          exclude: normalizeFilters(config.exclude) as (string | RegExp)[],\n        },\n        isI18nMicro: i18nModule === 'nuxt-i18n-micro',\n        autoI18n: !!resolvedAutoI18n,\n      })\n      if (!pageSource.length) {\n        pageSource.push(nuxt.options.app.baseURL || '/')\n      }\n      // Dedupe: remove pages that were prerendered (prerender data takes precedence)\n      // but merge page meta sitemap data (from definePageMeta) into prerendered entries\n      const allPrerenderedPaths = new Set(\n        prerenderedRoutes\n          .filter(isValidPrerenderRoute)\n          .map(r => r.route),\n      )\n      const pageSourceByPath = new Map<string, (typeof pageSource)[number]>()\n      for (const p of pageSource) {\n        if (typeof p !== 'string' && p.loc)\n          pageSourceByPath.set(p.loc, p)\n      }\n      // merge definePageMeta sitemap data into prerendered entries\n      for (let i = 0; i < prerenderUrlsFinal.length; i++) {\n        const entry = prerenderUrlsFinal[i]\n        if (!entry || typeof entry === 'string')\n          continue\n        const pageEntry = pageSourceByPath.get(entry.loc)\n        if (pageEntry && typeof pageEntry !== 'string') {\n          prerenderUrlsFinal[i] = defu(entry, pageEntry) as typeof entry\n        }\n      }\n      const dedupedPageSource = pageSource.filter((p) => {\n        const path = typeof p === 'string' ? p : p.loc\n        return !allPrerenderedPaths.has(path)\n      })\n      if (!resolvedConfigUrls && config.urls) {\n        const urls = await resolveUrls(config.urls, { path: 'sitemap:urls', logger })\n        if (urls.length) {\n          userGlobalSources.push({\n            context: {\n              name: 'sitemap:urls',\n              description: 'Set with the `sitemap.urls` config.',\n            },\n            urls,\n          })\n        }\n        resolvedConfigUrls = true\n      }\n      const globalSources: SitemapSourceInput[] = [\n        ...userGlobalSources.map((s) => {\n          if (typeof s === 'string' || Array.isArray(s)) {\n            return <SitemapSourceBase> {\n              sourceType: 'user',\n              fetch: s,\n            }\n          }\n          s.sourceType = 'user'\n          return s\n        }),\n        ...(config.excludeAppSources === true\n          ? []\n          : <typeof appGlobalSources>[\n            ...appGlobalSources,\n            {\n              context: {\n                name: 'nuxt:pages',\n                description: 'Generated from your static page files.',\n                tips: [\n                  'Can be disabled with `{ excludeAppSources: [\\'nuxt:pages\\'] }`.',\n                ],\n              },\n              urls: dedupedPageSource,\n            },\n            {\n              context: {\n                name: 'nuxt:route-rules',\n                description: 'Generated from your route rules config.',\n                tips: [\n                  'Can be disabled with `{ excludeAppSources: [\\'nuxt:route-rules\\'] }`.',\n                ],\n              },\n              urls: routeRules,\n            },\n            {\n              context: {\n                name: 'nuxt:prerender',\n                description: 'Generated at build time when prerendering.',\n                tips: [\n                  'Can be disabled with `{ excludeAppSources: [\\'nuxt:prerender\\'] }`.',\n                ],\n              },\n              urls: prerenderUrlsFinal,\n            },\n          ])\n          .filter(s =>\n            !(config.excludeAppSources as AppSourceContext[]).includes(s.context.name as AppSourceContext)\n            && (!!s.urls?.length || !!s.fetch))\n          .map((s) => {\n            s.sourceType = 'app'\n            return s\n          }),\n      ]\n      return globalSources\n    }\n\n    const extraSitemapModules = typeof config.sitemaps == 'object' ? Object.keys(config.sitemaps).filter(n => n !== 'index') : []\n    const sitemapSources: Record<string, SitemapSourceInput[]> = {}\n    const generateChildSources = async () => {\n      for (const sitemapName of extraSitemapModules) {\n        sitemapSources[sitemapName] = sitemapSources[sitemapName] || []\n        const definition = (config.sitemaps as Record<string, SitemapDefinition>)[sitemapName] as SitemapDefinition\n        if (!sitemapSources[sitemapName].length) {\n          if (definition.urls) {\n            sitemapSources[sitemapName].push({\n              context: {\n                name: `sitemaps:${sitemapName}:urls`,\n                description: 'Set with the `sitemap.urls` config.',\n              },\n              urls: await resolveUrls(definition.urls, { path: `sitemaps:${sitemapName}:urls`, logger }),\n            })\n          }\n          sitemapSources[sitemapName].push(...(definition.sources || [])\n            .map((s) => {\n              if (typeof s === 'string' || Array.isArray(s)) {\n                return <SitemapSourceBase> {\n                  sourceType: 'user',\n                  fetch: s,\n                }\n              }\n              s.sourceType = 'user'\n              return s\n            }),\n          )\n        }\n      }\n      return sitemapSources\n    }\n\n    nuxt.hooks.hook('nitro:config', (nitroConfig) => {\n      nitroConfig.virtual = nitroConfig.virtual || {}\n\n      // Always provide read-sources module stub (real implementation added by prerender.ts when needed)\n      if (!nitroConfig.virtual['#sitemap-virtual/read-sources.mjs']) {\n        nitroConfig.virtual['#sitemap-virtual/read-sources.mjs'] = `\nexport async function readSourcesFromFilesystem() {\n  return null\n}\n`\n      }\n\n      // Skip virtual templates when prerendering - sources are written to filesystem instead\n      // In dev mode, always generate sources even if prerenderSitemap is true (e.g. zeroRuntime)\n      if (prerenderSitemap && !nuxt.options.dev) {\n        nitroConfig.virtual['#sitemap-virtual/global-sources.mjs'] = `export const sources = []`\n        nitroConfig.virtual[`#sitemap-virtual/child-sources.mjs`] = `export const sources = {}`\n      }\n      else {\n        // Virtual templates generate sources data - will be cached in storage on first use\n        nitroConfig.virtual['#sitemap-virtual/global-sources.mjs'] = async () => {\n          const globalSources = await generateGlobalSources()\n          return `export const sources = ${JSON.stringify(globalSources, null, 4)}`\n        }\n\n        nitroConfig.virtual![`#sitemap-virtual/child-sources.mjs`] = async () => {\n          const childSources = await generateChildSources()\n          return `export const sources = ${JSON.stringify(childSources, null, 4)}`\n        }\n      }\n    })\n\n    // always add the styles\n    if (config.xsl === '/__sitemap__/style.xsl') {\n      addServerHandler({\n        route: config.xsl,\n        handler: resolve('./runtime/server/routes/sitemap.xsl'),\n      })\n      config.xsl = withBase(config.xsl, nuxt.options.app.baseURL)\n\n      if (prerenderSitemap)\n        addPrerenderRoutes(config.xsl)\n    }\n\n    // either this will redirect to sitemap_index or will render the main sitemap.xml\n    addServerHandler({\n      route: `/${config.sitemapName}`,\n      handler: resolve(`${routesPath}/sitemap.xml`),\n    })\n\n    setupPrerenderHandler({ runtimeConfig, logger, generateGlobalSources, generateChildSources, prerenderSitemap })\n\n    // suggest zeroRuntime when no dynamic sources detected\n    if (!config.zeroRuntime && !nuxt.options.dev && !nuxt.options._prepare) {\n      const hasDynamicSource = (source: SitemapSourceInput) =>\n        typeof source === 'string' || Array.isArray(source) || !!(source as SitemapSourceBase).fetch\n\n      const globalHasFetch = (config.sources || []).some(hasDynamicSource)\n      const sitemapsHaveFetch = typeof config.sitemaps === 'object'\n        && Object.values(config.sitemaps).some(s => s && 'sources' in s && (s.sources || []).some(hasDynamicSource))\n\n      if (!globalHasFetch && !sitemapsHaveFetch)\n        logger.info('No dynamic sources detected. Consider enabling `zeroRuntime` to reduce server bundle size. See https://nuxtseo.com/sitemap/guides/zero-runtime')\n    }\n  },\n})\n"
  },
  {
    "path": "src/prerender.ts",
    "content": "import type { Nuxt } from '@nuxt/schema'\nimport type { ConsolaInstance } from 'consola'\nimport type { Nitro, PrerenderRoute } from 'nitropack'\nimport type { ModuleRuntimeConfig, SitemapUrl } from './runtime/types'\nimport { readFileSync } from 'node:fs'\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { useNuxt } from '@nuxt/kit'\nimport { colors } from 'consola/utils'\nimport { defu } from 'defu'\nimport { withSiteUrl } from 'nuxt-site-config/kit'\nimport { dirname } from 'pathe'\nimport { withBase } from 'ufo'\nimport { splitForLocales } from './runtime/utils-pure'\nimport { isNuxtGenerate } from './utils-internal/kit'\nimport { parseHtmlExtractSitemapMeta } from './utils/parseHtmlExtractSitemapMeta'\n\nfunction formatPrerenderRoute(route: PrerenderRoute) {\n  let str = `  ├─ ${route.route} (${route.generateTimeMS}ms)`\n\n  if (route.error) {\n    const errorColor = colors[route.error.statusCode === 404 ? 'yellow' : 'red']\n    const errorLead = '└──'\n    str += `\\n  │ ${errorLead} ${errorColor(route.error.message)}`\n  }\n\n  return colors.gray(str)\n}\n\nexport function includesSitemapRoot(sitemapName: string, routes: string[]) {\n  return routes.includes(`/__sitemap__/`) || routes.includes(`/sitemap.xml`) || routes.includes(`/${sitemapName}`) || routes.includes('/sitemap_index.xml')\n}\n\nconst NuxtRedirectHtmlRegex = /<!DOCTYPE html><html><head><meta http-equiv=\"refresh\" content=\"0; url=([^\"]+)\"><\\/head><\\/html>/ // eslint-disable-line regexp/no-unused-capturing-group\n\nexport function setupPrerenderHandler(_options: { runtimeConfig: ModuleRuntimeConfig, logger: ConsolaInstance, generateGlobalSources: () => Promise<any>, generateChildSources: () => Promise<any>, prerenderSitemap: boolean }, nuxt: Nuxt = useNuxt()) {\n  const { runtimeConfig: options, logger, generateGlobalSources, generateChildSources, prerenderSitemap } = _options\n  nuxt.options.nitro.prerender = nuxt.options.nitro.prerender || {}\n  nuxt.options.nitro.prerender.routes = nuxt.options.nitro.prerender.routes || []\n  const shouldHookIntoPrerender = prerenderSitemap || (nuxt.options.nitro.prerender.routes.length && nuxt.options.nitro.prerender.crawlLinks)\n  if (isNuxtGenerate() && options.debug) {\n    nuxt.options.nitro.prerender.routes.push('/__sitemap__/debug.json')\n    logger.info('Adding debug route for sitemap generation:', colors.cyan('/__sitemap__/debug.json'))\n  }\n  // need to filter it out of the config as we render it after all other routes\n  if (!shouldHookIntoPrerender) {\n    return\n  }\n  nuxt.options.nitro.prerender.routes = nuxt.options.nitro.prerender.routes.filter(r => r && !includesSitemapRoot(options.sitemapName, [r]))\n\n  const runtimeAssetsPath = join(nuxt.options.rootDir, 'node_modules/.cache/nuxt/sitemap')\n\n  // Setup virtual module for reading sources - must be in nitro:config to be bundled\n  nuxt.hooks.hook('nitro:config', (nitroConfig) => {\n    nitroConfig.virtual = nitroConfig.virtual || {}\n    nitroConfig.virtual['#sitemap-virtual/read-sources.mjs'] = `\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'pathe'\n\nexport async function readSourcesFromFilesystem(filename) {\n  if (!import.meta.prerender) {\n    return null\n  }\n  const path = join(${JSON.stringify(runtimeAssetsPath)}, filename)\n  const data = await readFile(path, 'utf-8').catch(() => null)\n  return data ? JSON.parse(data) : null\n}\n`\n  })\n\n  nuxt.hooks.hook('nitro:init', async (nitro) => {\n    nitro.hooks.hook('prerender:generate', async (route) => {\n      const html = route.contents\n      // extract alternatives from the html\n      if (!route.fileName?.endsWith('.html') || !html || ['/200.html', '/404.html'].includes(route.route))\n        return\n      // ignore redirects\n      if (NuxtRedirectHtmlRegex.test(html)) {\n        return\n      }\n\n      const extractedMeta = parseHtmlExtractSitemapMeta(html, {\n        images: options.discoverImages,\n        videos: options.discoverVideos,\n        // TODO configurable?\n        lastmod: true,\n        // when autoI18n is enabled, let the sitemap builder generate alternatives\n        // based on i18n config instead of extracting from HTML (which can be incomplete)\n        // when autoI18n is explicitly disabled, don't extract alternatives from HTML at all\n        alternatives: !options.autoI18n && !options.hasDisabledAutoI18n,\n        resolveUrl(s) {\n          // if the match is relative\n          return s.startsWith('/') ? withSiteUrl(s) : s\n        },\n      })\n\n      // skip if route is blocked from indexing\n      if (extractedMeta === null) {\n        route._sitemap = {\n          loc: route.route,\n          _sitemap: false,\n        }\n        return\n      }\n\n      // maybe the user already provided a _sitemap on the route\n      route._sitemap = defu(route._sitemap, {\n        loc: route.route,\n      })\n      // we need to figure out which sitemap this belongs to\n      if (options.autoI18n && Object.keys(options.sitemaps).length > 1) {\n        const path = route.route\n        const match = splitForLocales(path, options.autoI18n.locales.map(l => l.code))\n        // if it's missing a locale then we put it in the default locale sitemap\n        const locale = match[0] || options.autoI18n.defaultLocale\n        if (options.isI18nMapped) {\n          const { _sitemap } = options.autoI18n.locales.find(l => l.code === locale) || { _sitemap: locale }\n          // this will filter the results to only the sitemap that matches the locale\n          route._sitemap._sitemap = _sitemap\n        }\n      }\n\n      route._sitemap = defu(extractedMeta, route._sitemap) as SitemapUrl\n    })\n    nitro.hooks.hook('prerender:done', async () => {\n      const globalSources = await generateGlobalSources()\n      const childSources = await generateChildSources()\n\n      // Write to filesystem for prerender consumption\n      await mkdir(runtimeAssetsPath, { recursive: true })\n      await writeFile(join(runtimeAssetsPath, 'global-sources.json'), JSON.stringify(globalSources))\n      await writeFile(join(runtimeAssetsPath, 'child-sources.json'), JSON.stringify(childSources))\n\n      const sitemapEntry = options.isMultiSitemap\n        ? '/sitemap_index.xml' // this route adds prerender hints for child sitemaps\n        : `/${Object.keys(options.sitemaps)[0]}`\n      const sitemaps = await prerenderSitemapsFromEntry(nitro, sitemapEntry)\n      await nuxt.hooks.callHook('sitemap:prerender:done' as any, { options, sitemaps })\n    })\n  })\n}\n\nasync function prerenderSitemapsFromEntry(nitro: Nitro, entry: string) {\n  const sitemaps: { name: string, get content(): string }[] = []\n  const queue = [entry]\n  const processed = new Set<string>()\n  while (queue.length) {\n    const route = queue.shift()!\n    if (processed.has(route))\n      continue\n    processed.add(route)\n    const { filePath, prerenderUrls } = await prerenderRoute(nitro, route)\n    sitemaps.push({\n      name: route,\n      get content() {\n        return readFileSync(filePath, { encoding: 'utf8' })\n      },\n    })\n    queue.push(...prerenderUrls)\n  }\n  return sitemaps\n}\n\nexport async function prerenderRoute(nitro: Nitro, route: string) {\n  const start = Date.now()\n  const _route: PrerenderRoute = { route, fileName: route }\n  const encodedRoute = encodeURI(route)\n  const fetchUrl = withBase(encodedRoute, nitro.options.baseURL)\n  const res = await globalThis.$fetch.raw(\n    fetchUrl,\n    {\n      headers: { 'x-nitro-prerender': encodedRoute },\n      retry: nitro.options.prerender.retry,\n      retryDelay: nitro.options.prerender.retryDelay,\n    },\n  )\n  const header = (res.headers.get('x-nitro-prerender') || '') as string\n  const prerenderUrls = header\n    .split(',')\n    .map(i => decodeURIComponent(i.trim()))\n    .filter(Boolean)\n  const filePath = join(nitro.options.output.publicDir, _route.fileName!)\n  await mkdir(dirname(filePath), { recursive: true })\n  const data = res._data\n  if (data === undefined)\n    throw new Error(`No data returned from '${fetchUrl}'`)\n  const content = filePath.endsWith('json') || typeof data === 'object'\n    ? JSON.stringify(data)\n    : data as string\n  await writeFile(filePath, content, 'utf8')\n  _route.generateTimeMS = Date.now() - start\n  nitro._prerenderedRoutes!.push(_route)\n  nitro.logger.log(formatPrerenderRoute(_route))\n  return { filePath, prerenderUrls }\n}\n"
  },
  {
    "path": "src/runtime/server/composables/asSitemapUrl.ts",
    "content": "import type { SitemapUrlInput } from '../../types'\n\nexport function asSitemapUrl(url: SitemapUrlInput | Record<string, any>): SitemapUrlInput {\n  return url as SitemapUrlInput\n}\n"
  },
  {
    "path": "src/runtime/server/composables/defineSitemapEventHandler.ts",
    "content": "import type { EventHandlerRequest, EventHandlerResponse } from 'h3'\nimport type { SitemapUrlInput } from '../../types'\nimport { defineEventHandler } from 'h3'\n\nexport const defineSitemapEventHandler: typeof defineEventHandler<EventHandlerRequest, EventHandlerResponse<SitemapUrlInput[]>> = defineEventHandler\n"
  },
  {
    "path": "src/runtime/server/content-compat.ts",
    "content": "// @ts-expect-error untyped\nimport { queryCollectionWithEvent } from '#sitemap/content-v3-nitro-path'\n\nexport const queryCollection = queryCollectionWithEvent\n"
  },
  {
    "path": "src/runtime/server/kit.ts",
    "content": "import type { NitroRouteRules } from 'nitropack'\nimport { defu } from 'defu'\nimport { useRuntimeConfig } from 'nitropack/runtime'\nimport { createRouter as createRadixRouter, toRouteMatcher } from 'radix3'\nimport { parseURL, withoutBase, withoutTrailingSlash } from 'ufo'\n\nfunction withoutQuery(path: string): string {\n  return path.split('?')[0]!\n}\n\nexport function createNitroRouteRuleMatcher(): (pathOrUrl: string) => NitroRouteRules {\n  const { nitro, app } = useRuntimeConfig()\n  const _routeRulesMatcher = toRouteMatcher(\n    createRadixRouter({\n      routes: Object.fromEntries(\n        Object.entries(nitro?.routeRules || {})\n          .map(([path, rules]) => [withoutTrailingSlash(path), rules]),\n      ),\n    }),\n  )\n  return (pathOrUrl: string) => {\n    const path = pathOrUrl[0] === '/' ? pathOrUrl : parseURL(pathOrUrl, app.baseURL).pathname\n    return defu({}, ..._routeRulesMatcher.matchAll(\n      withoutBase(withoutTrailingSlash(withoutQuery(path)), app.baseURL),\n    ).reverse()) as NitroRouteRules\n  }\n}\n"
  },
  {
    "path": "src/runtime/server/plugins/compression.ts",
    "content": "import type { H3Event } from 'h3'\nimport { getRequestHeader, setResponseHeader } from 'h3'\nimport { defineNitroPlugin } from 'nitropack/runtime'\n\nfunction getPreferredEncoding(event: H3Event): 'gzip' | 'deflate' | null {\n  const acceptEncoding = getRequestHeader(event, 'accept-encoding') || ''\n  if (acceptEncoding.includes('gzip'))\n    return 'gzip'\n  if (acceptEncoding.includes('deflate'))\n    return 'deflate'\n  return null\n}\n\nexport default defineNitroPlugin((nitro) => {\n  nitro.hooks.hook('beforeResponse', async (event, response) => {\n    if (!event.context._isSitemap || !response.body)\n      return\n\n    const encoding = getPreferredEncoding(event)\n    if (!encoding)\n      return\n\n    const body = typeof response.body === 'string' ? response.body : JSON.stringify(response.body)\n    const stream = new Blob([body]).stream().pipeThrough(new CompressionStream(encoding))\n    response.body = Buffer.from(await new Response(stream).arrayBuffer())\n    setResponseHeader(event, 'Content-Encoding', encoding)\n  })\n})\n"
  },
  {
    "path": "src/runtime/server/plugins/nuxt-content-v2.ts",
    "content": "import type { NitroApp } from 'nitropack/types'\nimport type { SitemapUrl } from '../../types'\nimport { defu } from 'defu'\nimport { defineNitroPlugin } from 'nitropack/runtime'\nimport { useSitemapRuntimeConfig } from '../utils'\n\ninterface NuxtContentDocument {\n  sitemap?: Partial<SitemapUrl> | boolean\n  _draft?: boolean\n  _extension?: string\n  _partial?: boolean\n  _path?: string\n  path?: string\n  robots?: boolean\n  body?: {\n    children?: Array<{ tag?: string, props?: { src?: string } }>\n  }\n  modifiedAt?: string | Date\n  updatedAt?: string | Date\n}\n\nexport default defineNitroPlugin((nitroApp: NitroApp) => {\n  const { discoverImages, isNuxtContentDocumentDriven } = useSitemapRuntimeConfig()\n  // @ts-expect-error untyped hook\n  nitroApp.hooks.hook('content:file:afterParse', async (content: NuxtContentDocument) => {\n    const validExtensions = ['md', 'mdx']\n    if (content.sitemap === false || content._draft || !validExtensions.includes(content._extension || '') || content._partial || content.robots === false)\n      return\n\n    // add any top level images\n    let images: SitemapUrl['images'] = []\n    if (discoverImages) {\n      const children = content.body?.children || []\n      images = children\n        .filter(c => c.tag && c.props?.src && ['image', 'img', 'nuxtimg', 'nuxt-img'].includes(c.tag.toLowerCase()))\n        .map(i => ({ loc: i.props!.src! }))\n    }\n\n    const sitemapConfig: Partial<SitemapUrl> = typeof content.sitemap === 'object' ? content.sitemap : {}\n    const lastmod = content.modifiedAt || content.updatedAt\n    const defaults: Partial<SitemapUrl> = {}\n    if (isNuxtContentDocumentDriven && typeof content._path === 'string')\n      defaults.loc = content._path\n    if (typeof content.path === 'string') // automatically set when document driven\n      defaults.loc = content.path\n    if (images?.length)\n      defaults.images = images\n    if (typeof lastmod === 'string' || lastmod instanceof Date)\n      defaults.lastmod = lastmod\n    const definition = defu(sitemapConfig, defaults) as Partial<SitemapUrl>\n    if (!definition.loc) {\n      // user hasn't provided a loc... lets fallback to a relative path\n      if (typeof content.path === 'string' && content.path.startsWith('/'))\n        definition.loc = content.path\n      // otherwise let's warn them\n      if (Object.keys(sitemapConfig).length > 0 && import.meta.dev)\n        console.warn(`[@nuxtjs/content] The @nuxt/content file \\`${content._path}\\` is missing a sitemap \\`loc\\`.`)\n    }\n    content.sitemap = definition\n    // loc is required\n    if (!definition.loc)\n      delete content.sitemap\n\n    return content\n  })\n})\n"
  },
  {
    "path": "src/runtime/server/plugins/warm-up.ts",
    "content": "import { defineNitroPlugin } from 'nitropack/runtime'\nimport { joinURL, withLeadingSlash } from 'ufo'\nimport { useSitemapRuntimeConfig } from '../utils'\n\nexport default defineNitroPlugin((nitroApp) => {\n  const { sitemaps, sitemapsPathPrefix } = useSitemapRuntimeConfig()\n  const queue: (() => Promise<Response>)[] = []\n  const timeoutIds: NodeJS.Timeout[] = []\n\n  const enqueue = (path: string) => {\n    queue.push(() => nitroApp.localFetch(withLeadingSlash(path), {}))\n  }\n\n  for (const [name, sitemap] of Object.entries(sitemaps)) {\n    if (!sitemap._route)\n      continue\n    if (name === 'index') {\n      enqueue(sitemap._route)\n      continue\n    }\n    // Chunked sitemaps don't expose the base route — the catch-all serves a non-chunked variant\n    // that bypasses chunk slicing. Warm chunk-0 instead so the shared resolved-URLs cache is\n    // populated with the correct filter pass; sibling chunk requests then hit that cache.\n    const def = sitemap as { chunks?: unknown, _isChunking?: boolean, _route: string }\n    if (def.chunks || def._isChunking) {\n      enqueue(joinURL(sitemapsPathPrefix || '/', `${name}-0.xml`))\n    }\n    else {\n      enqueue(sitemap._route)\n    }\n  }\n\n  // run async\n  const initialTimeout = setTimeout(() => {\n    // work the queue step by step await the promise from each task, delay 1s after each task ends\n    const next = async () => {\n      if (queue.length === 0) {\n        // Clear timeout references when done\n        timeoutIds.length = 0\n        return\n      }\n\n      try {\n        await queue.shift()!()\n      }\n      catch (error) {\n        console.error('[sitemap:warm-up] Error warming up sitemap:', error)\n      }\n\n      // Only schedule next if we have more items\n      if (queue.length > 0) {\n        const nextTimeout = setTimeout(next, 1000) // arbitrary delay to avoid throttling\n        timeoutIds.push(nextTimeout)\n      }\n    }\n    next()\n  }, 2500 /* https://github.com/unjs/nitro/pull/1906 */)\n\n  timeoutIds.push(initialTimeout)\n\n  // Clean up on app shutdown\n  nitroApp.hooks.hook('close', () => {\n    // Clear all pending timeouts\n    timeoutIds.forEach(id => clearTimeout(id))\n    timeoutIds.length = 0\n    queue.length = 0\n  })\n})\n"
  },
  {
    "path": "src/runtime/server/robots-polyfill/getPathRobotConfig.ts",
    "content": "import type { H3Event } from 'h3'\n\nexport function getPathRobotConfig(_e: H3Event, _options: any) {\n  return { indexable: true, rule: 'index, follow' }\n}\n"
  },
  {
    "path": "src/runtime/server/routes/__sitemap__/debug-production.ts",
    "content": "import type { SitemapWarning } from '@nuxtjs/sitemap/utils'\nimport { isSitemapIndex, parseSitemapIndex, parseSitemapXml } from '@nuxtjs/sitemap/utils'\nimport { defineEventHandler, getQuery } from 'h3'\n\nexport interface ProductionSitemapEntry {\n  loc: string\n  urlCount: number\n  warnings: SitemapWarning[]\n  error?: string\n  lastmod?: string\n}\n\nexport interface ProductionDebugResponse {\n  url: string\n  isIndex: boolean\n  sitemaps: ProductionSitemapEntry[]\n  warnings: SitemapWarning[]\n  error?: string\n}\n\nasync function fetchXml(url: string): Promise<string> {\n  const response = await fetch(url, {\n    headers: { Accept: 'application/xml, text/xml' },\n    signal: AbortSignal.timeout(15000),\n  })\n  if (!response.ok)\n    throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n  return response.text()\n}\n\nexport default defineEventHandler(async (e): Promise<ProductionDebugResponse | Record<string, any>> => {\n  const { url, mode } = getQuery(e) as { url?: string, mode?: string }\n  if (!url || typeof url !== 'string')\n    return { url: '', isIndex: false, sitemaps: [], warnings: [], error: 'Missing url query parameter' }\n\n  // Try fetching the production debug.json endpoint (requires debug: true in production config)\n  if (mode === 'debug') {\n    const debugUrl = `${url.replace(/\\/$/, '')}/__sitemap__/debug.json`\n    const response = await fetch(debugUrl, {\n      headers: { Accept: 'application/json' },\n      signal: AbortSignal.timeout(10000),\n    }).catch(() => null)\n    if (response?.ok) {\n      const json = await response.json().catch(() => null)\n      if (json?.sitemaps)\n        return json\n    }\n    // Fall through to XML-based approach\n  }\n\n  // Determine the sitemap URL to fetch\n  const sitemapUrl = url.endsWith('/') ? `${url}sitemap.xml` : url\n\n  const xml = await fetchXml(sitemapUrl).catch((err: Error) => {\n    return err\n  })\n\n  if (xml instanceof Error)\n    return { url: sitemapUrl, isIndex: false, sitemaps: [], warnings: [], error: `Failed to fetch sitemap: ${xml.message}` }\n\n  if (isSitemapIndex(xml)) {\n    const { entries, warnings } = await parseSitemapIndex(xml)\n    const sitemaps: ProductionSitemapEntry[] = await Promise.all(\n      entries.map(async (entry) => {\n        const childXml = await fetchXml(entry.loc).catch((err: Error) => err)\n        if (childXml instanceof Error) {\n          return {\n            loc: entry.loc,\n            urlCount: 0,\n            warnings: [],\n            error: childXml.message,\n            lastmod: entry.lastmod,\n          }\n        }\n        const result = await parseSitemapXml(childXml).catch((err: Error) => ({\n          urls: [],\n          warnings: [{ type: 'validation' as const, message: err.message }],\n        }))\n        return {\n          loc: entry.loc,\n          urlCount: result.urls.length,\n          warnings: result.warnings,\n          lastmod: entry.lastmod,\n        }\n      }),\n    )\n    return { url: sitemapUrl, isIndex: true, sitemaps, warnings }\n  }\n\n  // Single sitemap\n  const result = await parseSitemapXml(xml).catch((err: Error) => ({\n    urls: [],\n    warnings: [{ type: 'validation' as const, message: err.message }],\n  }))\n  return {\n    url: sitemapUrl,\n    isIndex: false,\n    sitemaps: [{\n      loc: sitemapUrl,\n      urlCount: result.urls.length,\n      warnings: result.warnings,\n    }],\n    warnings: [],\n  }\n})\n"
  },
  {
    "path": "src/runtime/server/routes/__sitemap__/debug.ts",
    "content": "import type { SitemapDefinition, SitemapSourceResolved } from '../../../types'\nimport { defineEventHandler } from 'h3'\nimport { getNitroOrigin, getSiteConfig } from '#site-config/server/composables'\nimport { validateSitemapUrl } from '../../sitemap/urlset/normalise'\nimport {\n  childSitemapSources,\n  globalSitemapSources,\n  resolveSitemapSources,\n} from '../../sitemap/urlset/sources'\nimport { useSitemapRuntimeConfig } from '../../utils'\n\nfunction attachUrlWarnings(sources: SitemapSourceResolved[]) {\n  for (const source of sources) {\n    if (!source.urls?.length)\n      continue\n    const warnings: SitemapSourceResolved['_urlWarnings'] = []\n    for (const url of source.urls) {\n      const msgs = validateSitemapUrl(url)\n      if (msgs.length) {\n        const loc = typeof url === 'string' ? url : (url.loc || '')\n        for (const message of msgs)\n          warnings.push({ loc, message })\n      }\n    }\n    if (warnings.length)\n      source._urlWarnings = warnings\n  }\n  return sources\n}\n\nexport default defineEventHandler(async (e) => {\n  const _runtimeConfig = useSitemapRuntimeConfig()\n  const siteConfig = getSiteConfig(e)\n  const { sitemaps: _sitemaps } = _runtimeConfig\n  const runtimeConfig = { ..._runtimeConfig }\n  // @ts-expect-error hack\n  delete runtimeConfig.sitemaps\n  const globalSources = await globalSitemapSources()\n  const nitroOrigin = getNitroOrigin(e)\n  const sitemaps: Record<string, SitemapDefinition> = {}\n  for (const s of Object.keys(_sitemaps)) {\n    const sitemap = _sitemaps[s]!\n    // resolve the sources\n    sitemaps[s] = {\n      ...sitemap,\n      sources: attachUrlWarnings(await resolveSitemapSources(await childSitemapSources(sitemap), e)),\n    } as SitemapDefinition\n  }\n  return {\n    nitroOrigin,\n    sitemaps,\n    runtimeConfig,\n    globalSources: attachUrlWarnings(await resolveSitemapSources(globalSources, e)),\n    siteConfig: { ...siteConfig },\n  }\n})\n"
  },
  {
    "path": "src/runtime/server/routes/__sitemap__/nuxt-content-urls-v2.ts",
    "content": "import { defineEventHandler } from 'h3'\n// @ts-expect-error alias module\nimport { serverQueryContent } from '#content/server'\n\ninterface ContentWithSitemap {\n  sitemap?: unknown\n}\n\nexport default defineEventHandler(async (e) => {\n  const contentList = (await serverQueryContent(e).find()) as ContentWithSitemap[]\n  return contentList.map(c => c.sitemap).filter(Boolean)\n})\n"
  },
  {
    "path": "src/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.ts",
    "content": "import { queryCollection } from '@nuxt/content/server'\nimport { defineEventHandler } from 'h3'\nimport manifest from '#content/manifest'\nimport { filters } from '#sitemap/content-filters'\nimport { onUrlFns } from '#sitemap/content-on-url'\n\ninterface ContentEntry {\n  path?: string\n  sitemap?: object | boolean\n}\n\nexport default defineEventHandler(async (e) => {\n  const collections: string[] = []\n  // each collection in the manifest has a key => with fields which has a `sitemap`, we want to get all those\n  for (const collection in manifest) {\n    // @ts-expect-error nuxt content v3\n    if (manifest[collection].fields.sitemap)\n      collections.push(collection)\n  }\n  // now we need to handle multiple queries here, we want to run the requests in parallel\n  const contentList: Promise<{ collection: string, entries: ContentEntry[] }>[] = []\n  for (const collection of collections) {\n    const needsAllFields = filters?.has(collection) || onUrlFns?.has(collection)\n    // @ts-expect-error dynamic collection name\n    const query = queryCollection(e, collection)\n      .where('path', 'IS NOT NULL')\n      .where('sitemap', 'IS NOT NULL')\n\n    // only select specific fields if no filter/onUrl, otherwise get all fields\n    if (!needsAllFields)\n      // @ts-expect-error dynamic field names\n      query.select('path', 'sitemap')\n\n    contentList.push(\n      query.all()\n        .then((results) => {\n          // apply runtime filter if available\n          const filter = filters?.get(collection)\n          return { collection, entries: filter ? results.filter(filter) : results }\n        }),\n    )\n  }\n  // we need to wait for all the queries to finish\n  const results = await Promise.all(contentList)\n  // we need to flatten the results\n  return results\n    .flatMap(({ collection, entries }) => {\n      const onUrl = onUrlFns?.get(collection)\n      return entries\n        .filter(c => c.sitemap !== false && c.path && !c.path.endsWith('.navigation'))\n        .map((c) => {\n          const url: Record<string, unknown> = {\n            loc: c.path,\n            ...(typeof c.sitemap === 'object' ? c.sitemap : {}),\n          }\n          onUrl?.(url, c, collection)\n          return url\n        })\n    })\n    .filter(Boolean)\n})\n"
  },
  {
    "path": "src/runtime/server/routes/__zero-runtime/sitemap/[sitemap].xml.ts",
    "content": "import { createError, defineEventHandler } from 'h3'\n\nexport default defineEventHandler(async (e) => {\n  if (import.meta.dev || import.meta.prerender) {\n    const { sitemapChildXmlEventHandler } = await import('../../../sitemap/event-handlers')\n    return sitemapChildXmlEventHandler(e)\n  }\n  throw createError({ statusCode: 500, message: 'Sitemap not prerendered. zeroRuntime requires prerendering.' })\n})\n"
  },
  {
    "path": "src/runtime/server/routes/__zero-runtime/sitemap.xml.ts",
    "content": "import { createError, defineEventHandler } from 'h3'\n\nexport default defineEventHandler(async (e) => {\n  if (import.meta.dev || import.meta.prerender) {\n    const { sitemapXmlEventHandler } = await import('../../sitemap/event-handlers')\n    return sitemapXmlEventHandler(e)\n  }\n  throw createError({ statusCode: 500, message: 'Sitemap not prerendered. zeroRuntime requires prerendering.' })\n})\n"
  },
  {
    "path": "src/runtime/server/routes/__zero-runtime/sitemap_index.xml.ts",
    "content": "import { createError, defineEventHandler } from 'h3'\n\nexport default defineEventHandler(async (e) => {\n  if (import.meta.dev || import.meta.prerender) {\n    const { sitemapIndexXmlEventHandler } = await import('../../sitemap/event-handlers')\n    return sitemapIndexXmlEventHandler(e)\n  }\n  throw createError({ statusCode: 500, message: 'Sitemap not prerendered. zeroRuntime requires prerendering.' })\n})\n"
  },
  {
    "path": "src/runtime/server/routes/sitemap/[sitemap].xml.ts",
    "content": "import { defineEventHandler } from 'h3'\nimport { sitemapChildXmlEventHandler } from '../../sitemap/event-handlers'\n\nexport default defineEventHandler(sitemapChildXmlEventHandler)\n"
  },
  {
    "path": "src/runtime/server/routes/sitemap.xml.ts",
    "content": "import { defineEventHandler } from 'h3'\nimport { sitemapXmlEventHandler } from '../sitemap/event-handlers'\n\nexport default defineEventHandler(sitemapXmlEventHandler)\n"
  },
  {
    "path": "src/runtime/server/routes/sitemap.xsl.ts",
    "content": "import { defineEventHandler, getHeader, getQuery as h3GetQuery, setHeader } from 'h3'\nimport { getQuery, parseURL, withQuery } from 'ufo'\nimport { getSiteConfig } from '#site-config/server/composables'\nimport { createSitePathResolver } from '#site-config/server/composables/utils'\nimport { useSitemapRuntimeConfig, xmlEscape } from '../utils'\n\nexport default defineEventHandler(async (e) => {\n  const fixPath = createSitePathResolver(e, { absolute: false, withBase: true })\n\n  const { sitemapName: fallbackSitemapName, cacheMaxAgeSeconds, version, xslColumns, xslTips } = useSitemapRuntimeConfig()\n  setHeader(e, 'Content-Type', 'application/xslt+xml')\n  if (cacheMaxAgeSeconds)\n    setHeader(e, 'Cache-Control', `public, max-age=${cacheMaxAgeSeconds}, must-revalidate`)\n  else\n    setHeader(e, 'Cache-Control', `no-cache, no-store`)\n\n  const { name: siteName, url: siteUrl } = getSiteConfig(e)\n\n  const referrer = getHeader(e, 'Referer')! || '/'\n  const referrerPath = parseURL(referrer).pathname\n  const isNotIndexButHasIndex = referrerPath !== '/sitemap.xml' && referrerPath !== '/sitemap_index.xml' && referrerPath.endsWith('.xml')\n  const sitemapName = parseURL(referrer).pathname.split('/').pop()?.split('-sitemap')[0] || fallbackSitemapName\n  const title = `${siteName}${sitemapName !== 'sitemap.xml' ? ` - ${sitemapName === 'sitemap_index.xml' ? 'index' : sitemapName}` : ''}`.replace(/&/g, '&amp;')\n\n  const isIndexPage = referrerPath === '/sitemap.xml' || referrerPath === '/sitemap_index.xml'\n  const canonicalQuery = getQuery(referrer).canonical\n  const isShowingCanonical = typeof canonicalQuery !== 'undefined' && canonicalQuery !== 'false'\n\n  // Build action URLs\n  const debugUrl = xmlEscape(withQuery('/__sitemap__/debug.json', { sitemap: sitemapName }))\n  const devUrl = xmlEscape(referrerPath)\n  const prodUrl = xmlEscape(withQuery(referrerPath, { canonical: '' }))\n\n  // Fetch errors from query params\n  const fetchErrors: string[] = []\n  const xslQuery = h3GetQuery(e)\n  if (xslQuery.error_messages) {\n    const errorMessages = xslQuery.error_messages\n    const errorUrls = xslQuery.error_urls\n    if (errorMessages) {\n      const messages = Array.isArray(errorMessages) ? errorMessages : [errorMessages]\n      const urls = Array.isArray(errorUrls) ? errorUrls : (errorUrls ? [errorUrls] : [])\n      messages.forEach((msg, i) => {\n        const errorParts = [xmlEscape(msg)]\n        if (urls[i])\n          errorParts.push(xmlEscape(urls[i]))\n        fetchErrors.push(`<span class=\"error-item\">${errorParts.join(' — ')}</span>`)\n      })\n    }\n  }\n\n  const hasRuntimeErrors = fetchErrors.length > 0\n  const showDevTools = import.meta.dev && xslTips !== false\n\n  const hints = [\n    `This is an XSL sitemap (CSS for XML). Disable with <code>xsl: false</code>`,\n    `View the raw XML by adding <code>?canonical</code> to the URL`,\n    `Check <code>/__sitemap__/debug.json</code> for full sitemap diagnostics`,\n  ]\n  const hint = hints[Math.floor(Math.random() * hints.length)]\n\n  let columns = [...xslColumns!]\n  if (!columns.length) {\n    columns = [\n      { label: 'URL', width: '50%' },\n      { label: 'Images', width: '25%', select: 'count(image:image)' },\n      { label: 'Last Updated', width: '25%', select: 'concat(substring(sitemap:lastmod,0,11),concat(\\' \\', substring(sitemap:lastmod,12,5)),concat(\\' \\', substring(sitemap:lastmod,20,6)))' },\n    ]\n  }\n\n  return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsl:stylesheet version=\"2.0\"\n                xmlns:html=\"http://www.w3.org/TR/REC-html40\"\n                xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\n                xmlns:sitemap=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n                xmlns:xhtml=\"http://www.w3.org/1999/xhtml\"\n                xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\n                xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n  <xsl:output method=\"html\" version=\"1.0\" encoding=\"UTF-8\" indent=\"yes\"/>\n  <xsl:template match=\"/\">\n    <html xmlns=\"http://www.w3.org/1999/xhtml\">\n      <head>\n        <title>XML Sitemap</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n        <style type=\"text/css\">\n          :root {\n            --accent: #00dc82;\n            --accent-hover: #00b86b;\n            --bg: #0a0a0a;\n            --bg-elevated: #141414;\n            --bg-subtle: #1a1a1a;\n            --border: #262626;\n            --border-subtle: #1f1f1f;\n            --text: #e5e5e5;\n            --text-muted: #737373;\n            --text-faint: #525252;\n            --error: #ef4444;\n            --error-bg: rgba(239,68,68,0.1);\n            --warning: #f59e0b;\n          }\n          * { box-sizing: border-box; }\n          body {\n            font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n            font-size: 13px;\n            color: var(--text);\n            background: var(--bg);\n            margin: 0;\n            padding: 0;\n            line-height: 1.6;\n            -webkit-font-smoothing: antialiased;\n          }\n          a { color: inherit; transition: color 0.15s; }\n          a:hover { color: var(--accent); }\n\n          /* Debug bar (dev only) */\n          .debug-bar {\n            position: fixed;\n            bottom: 0.75rem;\n            left: 50%;\n            transform: translateX(-50%);\n            width: 80%;\n            background: var(--bg-elevated);\n            border: 1px solid var(--border);\n            border-radius: 10px;\n            padding: 0 1rem;\n            height: 2.5rem;\n            display: flex;\n            align-items: center;\n            gap: 0.75rem;\n            z-index: 100;\n            font-size: 11px;\n          }\n          .debug-bar-brand {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            color: var(--text-muted);\n            text-decoration: none;\n          }\n          .debug-bar-brand:hover { color: var(--text); }\n          .debug-bar-brand svg { flex-shrink: 0; }\n          .debug-bar-hint {\n            color: var(--text-faint);\n            margin-right: auto;\n            white-space: nowrap;\n            overflow: hidden;\n            text-overflow: ellipsis;\n          }\n          .debug-bar-hint code {\n            background: var(--bg-subtle);\n            padding: 0.1rem 0.3rem;\n            border-radius: 3px;\n            font-size: 10px;\n          }\n          .mode-badge {\n            font-size: 9px;\n            font-weight: 600;\n            text-transform: uppercase;\n            letter-spacing: 0.04em;\n            padding: 0.2rem 0.4rem;\n            border-radius: 3px;\n          }\n          .mode-dev { background: rgba(245,158,11,0.15); color: var(--warning); }\n          .mode-prod { background: rgba(0,220,130,0.12); color: var(--accent); }\n          .mode-toggle {\n            display: inline-flex;\n            border-radius: 4px;\n            overflow: hidden;\n            background: var(--bg-subtle);\n            padding: 2px;\n            gap: 1px;\n          }\n          .mode-toggle a {\n            padding: 0.2rem 0.4rem;\n            font-size: 9px;\n            font-weight: 500;\n            text-decoration: none;\n            color: var(--text-muted);\n            border-radius: 2px;\n            transition: all 0.15s;\n          }\n          .mode-toggle a:hover { color: var(--text); }\n          .mode-toggle a.active {\n            background: var(--accent);\n            color: #0a0a0a;\n          }\n          .btn {\n            display: inline-flex;\n            align-items: center;\n            gap: 0.25rem;\n            padding: 0.25rem 0.5rem;\n            border-radius: 4px;\n            text-decoration: none;\n            font-size: 10px;\n            font-weight: 500;\n            transition: all 0.15s;\n          }\n          .btn-primary {\n            background: var(--accent);\n            color: #0a0a0a;\n          }\n          .btn-primary:hover { background: var(--accent-hover); color: #0a0a0a; }\n          .btn svg { width: 12px; height: 12px; }\n\n          /* Error banner */\n          .error-banner {\n            background: var(--error-bg);\n            border-bottom: 1px solid rgba(239,68,68,0.2);\n            padding: 0.75rem 1.5rem;\n            color: #fca5a5;\n            font-size: 12px;\n          }\n          .error-banner strong { color: var(--error); }\n          .error-item { display: block; margin-top: 0.375rem; color: #fca5a5; }\n          .error-debug-link {\n            display: inline-flex;\n            align-items: center;\n            gap: 0.25rem;\n            margin-top: 0.625rem;\n            padding: 0.25rem 0.5rem;\n            background: var(--error);\n            color: #fff;\n            border-radius: 4px;\n            font-size: 11px;\n            font-weight: 500;\n            text-decoration: none;\n            transition: background 0.15s;\n          }\n          .error-debug-link:hover { background: #dc2626; color: #fff; }\n\n          /* Main content */\n          .container {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 1.5rem;\n          }\n          .header {\n            margin-bottom: 1.25rem;\n          }\n          .header h1 {\n            font-size: 1rem;\n            font-weight: 600;\n            margin: 0 0 0.25rem 0;\n            color: var(--text);\n          }\n          .header-meta {\n            color: var(--text-muted);\n            font-size: 12px;\n          }\n          .header-meta a {\n            color: var(--text-muted);\n            text-decoration: underline;\n            text-decoration-color: var(--border);\n            text-underline-offset: 2px;\n          }\n          .header-meta a:hover { color: var(--accent); text-decoration-color: var(--accent); }\n\n          /* Table */\n          .table-wrap {\n            border: 1px solid var(--border);\n            border-radius: 8px;\n            overflow: hidden;\n            background: var(--bg-elevated);\n          }\n          table {\n            width: 100%;\n            border-collapse: collapse;\n          }\n          th {\n            text-align: left;\n            padding: 0.625rem 1rem;\n            font-size: 10px;\n            font-weight: 600;\n            text-transform: uppercase;\n            letter-spacing: 0.05em;\n            color: var(--text-muted);\n            background: var(--bg-subtle);\n            border-bottom: 1px solid var(--border);\n          }\n          td {\n            padding: 0.5rem 1rem;\n            border-bottom: 1px solid var(--border-subtle);\n            font-size: 12px;\n            color: var(--text);\n          }\n          tr:last-child td { border-bottom: none; }\n          tr:hover td { background: rgba(255,255,255,0.02); }\n          td a {\n            text-decoration: none;\n            word-break: break-all;\n            color: var(--text);\n          }\n          td a:hover { color: var(--accent); }\n          .inline-warning {\n            font-size: 11px;\n            color: var(--warning);\n            margin-top: 0.25rem;\n            line-height: 1.4;\n          }\n          .inline-warning::before {\n            content: \"⚠ \";\n          }\n          .count {\n            display: inline-block;\n            min-width: 1.25rem;\n            padding: 0.125rem 0.375rem;\n            background: var(--bg-subtle);\n            border-radius: 4px;\n            text-align: center;\n            font-size: 11px;\n            color: var(--text-muted);\n            font-variant-numeric: tabular-nums;\n          }\n          .count:empty::before { content: \"0\"; }\n\n          /* Light mode */\n          @media (prefers-color-scheme: light) {\n            :root {\n              --accent: #00a963;\n              --accent-hover: #008f54;\n              --bg: #ffffff;\n              --bg-elevated: #f5f5f5;\n              --bg-subtle: #ebebeb;\n              --border: #d4d4d4;\n              --border-subtle: #e5e5e5;\n              --text: #171717;\n              --text-muted: #525252;\n              --text-faint: #737373;\n              --error: #dc2626;\n              --error-bg: rgba(220,38,38,0.08);\n              --warning: #b45309;\n            }\n            tr:hover td { background: rgba(0,0,0,0.02); }\n            .btn-primary { color: #fff; }\n            .btn-primary:hover { color: #fff; }\n            .mode-toggle a.active { color: #fff; }\n            .error-banner { color: #991b1b; }\n            .error-item { color: #b91c1c; }\n            .error-debug-link { color: #fff; }\n            .error-debug-link:hover { color: #fff; }\n          }\n\n          .debug-bar-version {\n            color: var(--text-faint);\n            font-size: 10px;\n          }\n\n          /* Responsive */\n          @media (max-width: 640px) {\n            .debug-bar { padding: 0 0.75rem; gap: 0.5rem; width: 95%; }\n            .debug-bar-brand span { display: none; }\n            .debug-bar-hint { display: none; }\n            .debug-bar-version { display: none; }\n            .mode-badge { display: none; }\n            .container { padding: 1rem; }\n            th, td { padding: 0.5rem 0.75rem; }\n          }\n          ${showDevTools ? 'body { padding-bottom: 3.5rem; }' : ''}\n        </style>\n      </head>\n      <body>\n        ${hasRuntimeErrors\n          ? `<div class=\"error-banner\">\n            <strong>Sitemap Generation Errors</strong>\n            ${fetchErrors.join('')}\n            <a href=\"${debugUrl}\" target=\"_blank\" class=\"error-debug-link\">View Debug Info →</a>\n          </div>`\n          : ''}\n        <div class=\"container\">\n          <div class=\"header\">\n            <h1>${xmlEscape(title)}</h1>\n            <div class=\"header-meta\">\n              ${isNotIndexButHasIndex ? `Part of <a href=\"${xmlEscape(fixPath('/sitemap_index.xml'))}\">${xmlEscape(fixPath('/sitemap_index.xml'))}</a> · ` : ''}\n              <xsl:if test=\"count(sitemap:sitemapindex/sitemap:sitemap) &gt; 0\">\n                <xsl:value-of select=\"count(sitemap:sitemapindex/sitemap:sitemap)\"/> sitemaps\n              </xsl:if>\n              <xsl:if test=\"count(sitemap:sitemapindex/sitemap:sitemap) &lt; 1\">\n                <xsl:value-of select=\"count(sitemap:urlset/sitemap:url)\"/> URLs\n              </xsl:if>\n            </div>\n          </div>\n          <xsl:if test=\"count(sitemap:sitemapindex/sitemap:sitemap) &gt; 0\">\n            <div class=\"table-wrap\">\n              <table>\n                <thead>\n                  <tr>\n                    <th style=\"width:70%\">Sitemap</th>\n                    <th style=\"width:30%\">Last Modified</th>\n                  </tr>\n                </thead>\n                <tbody>\n                  <xsl:for-each select=\"sitemap:sitemapindex/sitemap:sitemap\">\n                    <xsl:variable name=\"sitemapURL\">\n                      <xsl:value-of select=\"sitemap:loc\"/>\n                    </xsl:variable>\n                    <tr>\n                      <td>\n                        <a href=\"{$sitemapURL}\">\n                          <xsl:value-of select=\"sitemap:loc\"/>\n                        </a>\n                      </td>\n                      <td>\n                        <xsl:value-of\n                          select=\"concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))\"/>\n                      </td>\n                    </tr>\n                  </xsl:for-each>\n                </tbody>\n              </table>\n            </div>\n          </xsl:if>\n          <xsl:if test=\"count(sitemap:sitemapindex/sitemap:sitemap) &lt; 1\">\n            <div class=\"table-wrap\">\n              <table>\n                <thead>\n                  <tr>\n                    ${columns!.map(c => `<th style=\"width:${c.width}\">${c.label}</th>`).join('\\n')}\n                  </tr>\n                </thead>\n                <tbody>\n                  <xsl:for-each select=\"sitemap:urlset/sitemap:url\">\n                    <tr>\n                      <td>\n                        <xsl:variable name=\"itemURL\">\n                          <xsl:value-of select=\"sitemap:loc\"/>\n                        </xsl:variable>\n                        <a href=\"{$itemURL}\">\n                          <xsl:value-of select=\"sitemap:loc\"/>\n                        </a>\n                        ${showDevTools\n                          ? `<xsl:for-each select=\"comment()[starts-with(normalize-space(.), 'WARN:')]\">\n                          <div class=\"inline-warning\">\n                            <xsl:value-of select=\"substring-after(normalize-space(.), 'WARN:')\"/>\n                          </div>\n                        </xsl:for-each>`\n                          : ''}\n                      </td>\n                      ${columns.filter(c => c.label !== 'URL').map(c => `<td><span class=\"count\"><xsl:value-of select=\"${c.select}\"/></span></td>`).join('\\n')}\n                    </tr>\n                  </xsl:for-each>\n                </tbody>\n              </table>\n            </div>\n          </xsl:if>\n        </div>\n        ${showDevTools\n          ? `<div class=\"debug-bar\">\n            <a href=\"${xmlEscape(fixPath('/sitemap_index.xml'))}\" class=\"debug-bar-brand\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 32 32\"><path fill=\"#00dc82\" d=\"M4 26h4v4H4zm10 0h4v4h-4zm10 0h4v4h-4zm1-10h-8v-2h-2v2H7a2 2 0 0 0-2 2v6h2v-6h8v6h2v-6h8v6h2v-6a2 2 0 0 0-2-2zM9 2v10h14V2zm2 2h2v6h-2zm10 6h-6V4h6z\"/></svg>\n              <span>Sitemap Debug Bar</span>\n            </a>\n            <span class=\"debug-bar-version\">v${version} · ${xmlEscape(siteUrl)}</span>\n            <span class=\"debug-bar-hint\">Hint: ${hint}</span>\n            ${isIndexPage\n              ? `<span class=\"mode-badge ${isShowingCanonical ? 'mode-prod' : 'mode-dev'}\">${isShowingCanonical ? 'Prod' : 'Dev'}</span>\n              <div class=\"mode-toggle\">\n                <a href=\"${isShowingCanonical ? devUrl : '#'}\" class=\"${!isShowingCanonical ? 'active' : ''}\">Dev</a>\n                <a href=\"${!isShowingCanonical ? prodUrl : '#'}\" class=\"${isShowingCanonical ? 'active' : ''}\">Prod</a>\n              </div>`\n              : ''}\n            <a href=\"${debugUrl}\" target=\"_blank\" class=\"btn btn-primary\">\n              <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/></svg>\n              Debug\n            </a>\n          </div>`\n          : ''}\n      </body>\n    </html>\n  </xsl:template>\n</xsl:stylesheet>\n`\n})\n"
  },
  {
    "path": "src/runtime/server/routes/sitemap_index.xml.ts",
    "content": "import { defineEventHandler } from 'h3'\nimport { sitemapIndexXmlEventHandler } from '../sitemap/event-handlers'\n\nexport default defineEventHandler(sitemapIndexXmlEventHandler)\n"
  },
  {
    "path": "src/runtime/server/sitemap/builder/sitemap-index.ts",
    "content": "import type { H3Event } from 'h3'\nimport type { NitroApp } from 'nitropack/types'\nimport type {\n  ModuleRuntimeConfig,\n  NitroUrlResolvers,\n  SitemapIndexEntry,\n} from '../../../types'\nimport { getHeader } from 'h3'\nimport { defineCachedFunction } from 'nitropack/runtime'\nimport { joinURL, withQuery } from 'ufo'\n// @ts-expect-error virtual module\nimport staticConfig from '#sitemap-virtual/static-config.mjs'\nimport { normaliseDate } from '../urlset/normalise'\nimport { getResolvedSitemapUrls } from './sitemap'\nimport { escapeValueForXml } from './xml'\n\nconst SERVER_CACHE_MAX_AGE = (staticConfig.cacheMaxAgeSeconds as number | false) || 60 * 10\n\n// Create cached wrapper for sitemap index building\nconst buildSitemapIndexCached = defineCachedFunction(\n  async (event: H3Event, resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp) => {\n    return buildSitemapIndexInternal(resolvers, runtimeConfig, nitro)\n  },\n  {\n    name: 'sitemap:index',\n    group: 'sitemap',\n    maxAge: SERVER_CACHE_MAX_AGE,\n    base: 'sitemap', // Use the sitemap storage\n    getKey: (event: H3Event) => {\n      // Include headers that could affect the output in the cache key\n      const host = getHeader(event, 'host') || getHeader(event, 'x-forwarded-host') || ''\n      const proto = getHeader(event, 'x-forwarded-proto') || 'https'\n      return `sitemap-index-${proto}-${host}`\n    },\n    swr: true, // Enable stale-while-revalidate\n  },\n)\n\nasync function buildSitemapIndexInternal(resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp): Promise<{ entries: SitemapIndexEntry[], failedSources: Array<{ url: string, error: string }> }> {\n  const {\n    sitemaps,\n    autoLastmod,\n    defaultSitemapsChunkSize,\n    sitemapsPathPrefix,\n  } = runtimeConfig\n\n  if (!sitemaps)\n    throw new Error('Attempting to build a sitemap index without required `sitemaps` configuration.')\n\n  const nonChunkedNames: string[] = []\n  const allFailedSources: Array<{ url: string, error: string }> = []\n\n  // Process all sitemaps to determine chunks\n  for (const sitemapName in sitemaps) {\n    if (sitemapName === 'index' || sitemapName === 'chunks')\n      continue\n\n    const sitemapConfig = sitemaps[sitemapName]!\n\n    // Check if this sitemap should be chunked\n    if (sitemapConfig.chunks || sitemapConfig._isChunking) {\n      // Mark as chunking for later processing\n      sitemapConfig._isChunking = true\n      sitemapConfig._chunkSize = sitemapConfig.chunkSize || (typeof sitemapConfig.chunks === 'number' ? sitemapConfig.chunks : (defaultSitemapsChunkSize || 1000))\n    }\n    else {\n      nonChunkedNames.push(sitemapName)\n    }\n  }\n\n  // sitemap.org defines index <lastmod> as the file's modification time, not the max of URL\n  // lastmods inside it. Our default sort is by `loc`, so per-chunk URL lastmods were already\n  // misleading. Emit `new Date()` when autoLastmod is on, otherwise no <lastmod>. This avoids\n  // a slice/filter/sort pass per chunk and lets us count without holding URLs in memory.\n  const indexLastmod = autoLastmod ? normaliseDate(new Date()) : undefined\n  const entries: SitemapIndexEntry[] = []\n\n  // Auto-chunking: count URLs to know how many chunk entries to emit. Shares cache with the\n  // chunk handler (matchName 'sitemap', isChunked true) so the source fetch is one-shot.\n  if (typeof sitemaps.chunks !== 'undefined') {\n    const sitemap = sitemaps.chunks\n    const resolved = await getResolvedSitemapUrls(sitemap, 'sitemap', true, resolvers, runtimeConfig, nitro)\n    allFailedSources.push(...resolved.failedSources)\n    const chunkCount = Math.ceil(resolved.urls.length / (defaultSitemapsChunkSize as number))\n    for (let i = 0; i < chunkCount; i++) {\n      const entry: SitemapIndexEntry = {\n        _sitemapName: String(i),\n        sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || '', `/${i}.xml`)),\n      }\n      if (indexLastmod)\n        entry.lastmod = indexLastmod\n      entries.push(entry)\n    }\n  }\n\n  // Non-chunked named sitemaps: just emit one entry each, no fetch.\n  for (const name of nonChunkedNames) {\n    const entry: SitemapIndexEntry = {\n      _sitemapName: name,\n      sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || '', `/${name}.xml`)),\n    }\n    if (indexLastmod)\n      entry.lastmod = indexLastmod\n    entries.push(entry)\n  }\n\n  // Chunked named sitemaps. Skip the source fetch when `chunkCount` is declared upfront.\n  for (const sitemapName in sitemaps) {\n    const sitemapConfig = sitemaps[sitemapName]!\n    if (sitemapName !== 'index' && sitemapConfig._isChunking) {\n      const chunkSize = sitemapConfig._chunkSize || defaultSitemapsChunkSize || 1000\n\n      let chunkCount: number\n      if (typeof sitemapConfig.chunkCount === 'number' && sitemapConfig.chunkCount > 0) {\n        chunkCount = sitemapConfig.chunkCount\n      }\n      else {\n        const resolved = await getResolvedSitemapUrls(sitemapConfig, sitemapName, true, resolvers, runtimeConfig, nitro)\n        allFailedSources.push(...resolved.failedSources)\n        chunkCount = Math.ceil(resolved.urls.length / chunkSize)\n      }\n\n      sitemapConfig._chunkCount = chunkCount\n\n      for (let i = 0; i < chunkCount; i++) {\n        const chunkName = `${sitemapName}-${i}`\n        const entry: SitemapIndexEntry = {\n          _sitemapName: chunkName,\n          sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || '', `/${chunkName}.xml`)),\n        }\n        if (indexLastmod)\n          entry.lastmod = indexLastmod\n        entries.push(entry)\n      }\n    }\n  }\n\n  // allow extending the index sitemap\n  if (sitemaps.index) {\n    entries.push(...sitemaps.index.sitemaps.map((entry) => {\n      return typeof entry === 'string' ? { sitemap: entry } : entry\n    }))\n  }\n\n  return { entries, failedSources: allFailedSources }\n}\n\nexport function urlsToIndexXml(sitemaps: SitemapIndexEntry[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: { messages: string[], urls: string[] }) {\n  const sitemapXml = sitemaps.map(e => [\n    '    <sitemap>',\n    `        <loc>${escapeValueForXml(e.sitemap)}</loc>`,\n    // lastmod is optional\n    e.lastmod ? `        <lastmod>${escapeValueForXml(e.lastmod)}</lastmod>` : false,\n    '    </sitemap>',\n  ].filter(Boolean).join('\\n')).join('\\n')\n\n  const xmlParts = [\n    '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n  ]\n\n  // Add XSL if enabled\n  if (xsl) {\n    let relativeBaseUrl = resolvers.relativeBaseUrlResolver?.(xsl) ?? xsl\n\n    // Add error information to XSL URL if available\n    if (errorInfo && errorInfo.messages.length > 0) {\n      relativeBaseUrl = withQuery(relativeBaseUrl, {\n        errors: 'true',\n        error_messages: errorInfo.messages,\n        error_urls: errorInfo.urls,\n      })\n    }\n\n    xmlParts.push(`<?xml-stylesheet type=\"text/xsl\" href=\"${escapeValueForXml(relativeBaseUrl)}\"?>`)\n  }\n\n  // Add sitemap index content\n  xmlParts.push(\n    '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n    sitemapXml,\n    '</sitemapindex>',\n  )\n\n  // Add credits if enabled\n  if (credits) {\n    xmlParts.push(`<!-- XML Sitemap Index generated by @nuxtjs/sitemap v${version} at ${new Date().toISOString()} -->`)\n  }\n\n  // Join with appropriate separator\n  return minify\n    ? xmlParts.join('').replace(/(?<!<[^>]*)\\s(?![^<]*>)/g, '')\n    : xmlParts.join('\\n')\n}\n\nexport async function buildSitemapIndex(resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp) {\n  // Check if should use cached version.\n  // Skip caching during prerender: sources are written to disk by `prerender:done`, so\n  // an early crawl would otherwise poison the cache with an empty result.\n  if (!import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === 'number' && runtimeConfig.cacheMaxAgeSeconds > 0 && resolvers.event) {\n    return buildSitemapIndexCached(resolvers.event, resolvers, runtimeConfig, nitro)\n  }\n  return buildSitemapIndexInternal(resolvers, runtimeConfig, nitro)\n}\n"
  },
  {
    "path": "src/runtime/server/sitemap/builder/sitemap.ts",
    "content": "import type { H3Event } from 'h3'\nimport type { NitroApp } from 'nitropack/types'\nimport type {\n  AlternativeEntry,\n  AutoI18nConfig,\n  ModuleRuntimeConfig,\n  NitroUrlResolvers,\n  ResolvedSitemapUrl,\n  SitemapDefinition,\n  SitemapInputCtx,\n  SitemapSourcesHookCtx,\n  SitemapUrl,\n  SitemapUrlInput,\n} from '../../../types'\nimport { getHeader } from 'h3'\nimport { defineCachedFunction, useRuntimeConfig } from 'nitropack/runtime'\nimport { resolveSitePath } from 'nuxt-site-config/urls'\nimport { joinURL, withHttps } from 'ufo'\n// @ts-expect-error virtual module\nimport staticConfig from '#sitemap-virtual/static-config.mjs'\nimport { applyDynamicParams, createPathFilter, findPageMapping, logger, splitForLocales } from '../../../utils-pure'\nimport { preNormalizeEntry } from '../urlset/normalise'\nimport { sortInPlace } from '../urlset/sort'\nimport { childSitemapSources, globalSitemapSources, resolveSitemapSources } from '../urlset/sources'\nimport { parseChunkInfo, sliceUrlsForChunk } from '../utils/chunk'\n\nconst SERVER_CACHE_MAX_AGE = (staticConfig.cacheMaxAgeSeconds as number | false) || 60 * 10\n\nexport interface NormalizedI18n extends ResolvedSitemapUrl {\n  _pathWithoutPrefix: string\n  _locale: AutoI18nConfig['locales'][number]\n  _index?: number\n}\n\nexport function resolveSitemapEntries(sitemap: SitemapDefinition, urls: SitemapUrlInput[], runtimeConfig: Pick<ModuleRuntimeConfig, 'autoI18n' | 'isI18nMapped'>, resolvers?: NitroUrlResolvers, baseURL?: string): ResolvedSitemapUrl[] {\n  const {\n    autoI18n,\n    isI18nMapped,\n  } = runtimeConfig\n  const filterPath = createPathFilter({\n    include: sitemap.include,\n    exclude: sitemap.exclude,\n  }, baseURL || '/')\n  // 1. normalise\n  const _urls = urls.map((_e) => {\n    const e = preNormalizeEntry(_e, resolvers)\n    if (!e.loc || !filterPath(e.loc))\n      return false\n    return e\n  }).filter(Boolean) as ResolvedSitemapUrl[]\n\n  let validI18nUrlsForTransform: NormalizedI18n[] = []\n  const withoutPrefixPaths: Record<string, NormalizedI18n[]> = {}\n  if (autoI18n && autoI18n.strategy !== 'no_prefix') {\n    const localeCodes = autoI18n.locales.map(l => l.code)\n    // Create locale lookup Map for O(1) access\n    const localeByCode = new Map(autoI18n.locales.map(l => [l.code, l]))\n    // Pre-check strategy once\n    const isPrefixStrategy = autoI18n.strategy === 'prefix'\n    const isPrefixExceptOrAndDefault = autoI18n.strategy === 'prefix_and_default' || autoI18n.strategy === 'prefix_except_default'\n    // Pre-create x-default + locales array for alternatives\n    const xDefaultAndLocales = [{ code: 'x-default', _hreflang: 'x-default' }, ...autoI18n.locales] as Array<{ code: string, _hreflang: string }>\n    // Cache frequently accessed values\n    const defaultLocale = autoI18n.defaultLocale\n    const hasPages = !!autoI18n.pages\n    const hasDifferentDomains = !!autoI18n.differentDomains\n\n    validI18nUrlsForTransform = _urls.map((_e, i) => {\n      if (_e._abs)\n        return false\n      const split = splitForLocales(_e._relativeLoc, localeCodes)\n      let localeCode = split[0]\n      const pathWithoutPrefix = split[1]\n      if (!localeCode)\n        localeCode = defaultLocale\n      const e = _e as NormalizedI18n\n      e._pathWithoutPrefix = pathWithoutPrefix\n      // Use Map instead of find for O(1) lookup\n      const locale = localeByCode.get(localeCode)\n      if (!locale)\n        return false\n      e._locale = locale\n      e._index = i\n      e._key = `${e._sitemap || ''}${e._path?.pathname || '/'}${e._path?.search || ''}`\n      withoutPrefixPaths[pathWithoutPrefix] = withoutPrefixPaths[pathWithoutPrefix] || []\n      // need to make sure the locale doesn't already exist\n      if (!withoutPrefixPaths[pathWithoutPrefix].some(e => e._locale.code === locale.code))\n        withoutPrefixPaths[pathWithoutPrefix].push(e)\n      return e\n    }).filter(Boolean) as NormalizedI18n[]\n\n    for (const e of validI18nUrlsForTransform) {\n      // let's try and find other urls that we can use for alternatives\n      if (!e._i18nTransform && !e.alternatives?.length) {\n        const alternatives = (withoutPrefixPaths[e._pathWithoutPrefix] || [])\n          .map((u) => {\n            const entries: AlternativeEntry[] = []\n            if (u._locale.code === defaultLocale) {\n              entries.push({\n                href: u.loc,\n                hreflang: 'x-default',\n              })\n            }\n            entries.push({\n              href: u.loc,\n              hreflang: u._locale._hreflang || defaultLocale,\n            })\n            return entries\n          })\n          .flat()\n          .filter(Boolean) as AlternativeEntry[]\n        if (alternatives.length)\n          e.alternatives = alternatives\n      }\n      else if (e._i18nTransform) {\n        delete e._i18nTransform\n        // keep single entry, just add alternatvies\n        if (hasDifferentDomains) {\n          // Use Map instead of find with array creation\n          const defLocale = localeByCode.get(defaultLocale)\n          e.alternatives = [\n            {\n              ...defLocale,\n              code: 'x-default',\n            },\n            ...autoI18n.locales\n              .filter(l => !!l.domain),\n          ]\n            .map((locale) => {\n              return {\n                hreflang: locale._hreflang!,\n                href: joinURL(withHttps(locale.domain!), e._pathWithoutPrefix),\n              }\n            })\n        }\n        else {\n          // Find page mapping with support for dynamic routes\n          const pageMatch = hasPages ? findPageMapping(e._pathWithoutPrefix, autoI18n.pages!) : null\n          const pathSearch = e._path?.search || ''\n          const pathWithoutPrefix = e._pathWithoutPrefix\n\n          // need to add urls for all other locales\n          for (const l of autoI18n.locales) {\n            let loc = pathWithoutPrefix\n\n            // Check if there's a custom mapping in i18n pages config\n            if (pageMatch && pageMatch.mappings[l.code] !== undefined) {\n              const customPath = pageMatch.mappings[l.code]\n              // If customPath is false, skip this locale\n              if (customPath === false)\n                continue\n              // If customPath is a string, use it (applying dynamic params if present)\n              if (typeof customPath === 'string') {\n                loc = customPath[0] === '/' ? customPath : `/${customPath}`\n                loc = applyDynamicParams(loc, pageMatch.paramSegments)\n                // Add locale prefix for non-default locales\n                if (isPrefixStrategy || (isPrefixExceptOrAndDefault && l.code !== defaultLocale))\n                  loc = joinURL(`/${l.code}`, loc)\n              }\n            }\n            else if (!hasDifferentDomains && !(isPrefixExceptOrAndDefault && l.code === defaultLocale)) {\n              // No custom mapping found, use default behavior\n              loc = joinURL(`/${l.code}`, pathWithoutPrefix)\n            }\n\n            const _sitemap = isI18nMapped ? l._sitemap : undefined\n            // Build alternatives array with loop instead of map().filter()\n            const alternatives: AlternativeEntry[] = []\n            for (const locale of xDefaultAndLocales) {\n              const code = locale.code === 'x-default' ? defaultLocale : locale.code\n              const isDefault = locale.code === 'x-default' || locale.code === defaultLocale\n              let href = pathWithoutPrefix\n\n              // Check for custom path mapping\n              if (pageMatch && pageMatch.mappings[code] !== undefined) {\n                const customPath = pageMatch.mappings[code]\n                if (customPath === false)\n                  continue\n                if (typeof customPath === 'string') {\n                  href = customPath[0] === '/' ? customPath : `/${customPath}`\n                  href = applyDynamicParams(href, pageMatch.paramSegments)\n                  // Add locale prefix for non-default locales\n                  if (isPrefixStrategy || (isPrefixExceptOrAndDefault && !isDefault))\n                    href = joinURL('/', code, href)\n                }\n              }\n              else if (isPrefixStrategy) {\n                href = joinURL('/', code, pathWithoutPrefix)\n              }\n              else if (isPrefixExceptOrAndDefault && !isDefault) {\n                href = joinURL('/', code, pathWithoutPrefix)\n              }\n\n              if (!filterPath(href))\n                continue\n              alternatives.push({\n                hreflang: locale._hreflang,\n                href,\n              })\n            }\n\n            const { _index: _, ...rest } = e\n            const newEntry = preNormalizeEntry({\n              _sitemap,\n              ...rest,\n              _key: `${_sitemap || ''}${loc || '/'}${pathSearch}`,\n              _locale: l,\n              loc,\n              alternatives,\n            } as SitemapUrl, resolvers) as NormalizedI18n\n            if (e._locale.code === newEntry._locale.code) {\n              // replace\n              _urls[e._index!] = newEntry\n              // avoid getting re-replaced\n              e._index = undefined\n            }\n            else {\n              _urls.push(newEntry)\n            }\n          }\n        }\n      }\n      if (isI18nMapped) {\n        e._sitemap = e._sitemap || e._locale._sitemap\n        e._key = `${e._sitemap || ''}${e.loc || '/'}${e._path?.search || ''}`\n      }\n      if (e._index)\n        _urls[e._index] = e\n    }\n  }\n  return _urls\n}\n\nexport interface ResolvedSitemapUrlsResult {\n  urls: ResolvedSitemapUrl[]\n  failedSources: Array<{ url: string, error: string }>\n}\n\n// Chunk-agnostic computation: fetch sources, run hooks, normalise, filter, sort.\n// Returns the full sorted array; chunked sitemaps slice from this on the way out.\n// All chunks of the same base sitemap share one cache entry.\nexport async function buildResolvedSitemapUrls(\n  effectiveSitemap: SitemapDefinition,\n  matchName: string,\n  isChunked: boolean,\n  resolvers: NitroUrlResolvers,\n  runtimeConfig: ModuleRuntimeConfig,\n  nitro?: NitroApp,\n): Promise<ResolvedSitemapUrlsResult> {\n  const { sitemaps, autoI18n, isI18nMapped, isMultiSitemap, sortEntries } = runtimeConfig\n\n  let sourcesInput = effectiveSitemap.includeAppSources\n    ? [...await globalSitemapSources(), ...await childSitemapSources(effectiveSitemap)]\n    : await childSitemapSources(effectiveSitemap)\n\n  if (nitro && resolvers.event) {\n    const ctx: SitemapSourcesHookCtx = {\n      event: resolvers.event,\n      sitemapName: matchName,\n      sources: sourcesInput,\n    }\n    await nitro.hooks.callHook('sitemap:sources', ctx)\n    sourcesInput = ctx.sources\n  }\n\n  const sources = await resolveSitemapSources(sourcesInput, resolvers.event)\n\n  const failedSources = sources\n    .filter(source => source.error && source._isFailure)\n    .map(source => ({\n      url: typeof source.fetch === 'string' ? source.fetch : (source.fetch?.[0] || 'unknown'),\n      error: source.error || 'Unknown error',\n    }))\n\n  const resolvedCtx: SitemapInputCtx = {\n    urls: sources.flatMap(s => s.urls),\n    sitemapName: matchName,\n    event: resolvers.event,\n  }\n  await nitro?.hooks.callHook('sitemap:input', resolvedCtx)\n  const enhancedUrls = resolveSitemapEntries(effectiveSitemap, resolvedCtx.urls, { autoI18n, isI18nMapped }, resolvers, useRuntimeConfig().app.baseURL)\n\n  if (isMultiSitemap) {\n    const sitemapNames = Object.keys(sitemaps).filter(k => k !== 'index')\n    // @ts-expect-error loose typing\n    const warnedSitemaps = nitro?._sitemapWarnedSitemaps || new Set<string>()\n    for (const e of enhancedUrls) {\n      const hasMatchingSitemap = typeof e._sitemap === 'string'\n        && (sitemapNames.includes(e._sitemap) || (isI18nMapped && sitemapNames.some(name => name.startsWith(`${e._sitemap}-`))))\n      if (typeof e._sitemap === 'string' && !hasMatchingSitemap) {\n        if (!warnedSitemaps.has(e._sitemap)) {\n          warnedSitemaps.add(e._sitemap)\n          logger.error(`Sitemap \\`${e._sitemap}\\` not found in sitemap config. Available sitemaps: ${sitemapNames.join(', ')}. Entry \\`${e.loc}\\` will be omitted.`)\n        }\n      }\n    }\n    if (nitro) {\n      // @ts-expect-error loose typing\n      nitro._sitemapWarnedSitemaps = warnedSitemaps\n    }\n  }\n\n  const filteredUrls = enhancedUrls.filter((e) => {\n    if (e._sitemap === false)\n      return false\n    if (isMultiSitemap && e._sitemap && matchName) {\n      if (isChunked)\n        return e._sitemap === matchName\n      return e._sitemap === matchName || (isI18nMapped && matchName.startsWith(`${e._sitemap}-`))\n    }\n    return true\n  })\n\n  const urls = sortEntries ? sortInPlace(filteredUrls) : filteredUrls\n  return { urls, failedSources }\n}\n\nexport const buildResolvedSitemapUrlsCached = defineCachedFunction(\n  async (\n    _event: H3Event,\n    effectiveSitemap: SitemapDefinition,\n    matchName: string,\n    isChunked: boolean,\n    resolvers: NitroUrlResolvers,\n    runtimeConfig: ModuleRuntimeConfig,\n    nitro?: NitroApp,\n  ) => buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro),\n  {\n    name: 'sitemap:resolved-urls',\n    group: 'sitemap',\n    base: 'sitemap',\n    maxAge: SERVER_CACHE_MAX_AGE,\n    getKey: (event, _effectiveSitemap, matchName, isChunked) => {\n      const host = getHeader(event, 'host') || getHeader(event, 'x-forwarded-host') || ''\n      const proto = getHeader(event, 'x-forwarded-proto') || 'https'\n      return `resolved-${isChunked ? 'chunked-' : ''}${matchName}-${proto}-${host}`\n    },\n    swr: true,\n  },\n)\n\n// Routes between Nitro's storage-backed cache (production) and direct execution. Chunks of the\n// same base sitemap share one cache entry so the source fetch + normalize + sort runs once per\n// `cacheMaxAgeSeconds` window. Edge-runtime safe: relies on Nitro's storage layer, no module\n// state. Dev and prerender skip the cache (prerender to avoid poisoning from early empty-source\n// reads; dev to keep iteration fast).\nexport async function getResolvedSitemapUrls(\n  effectiveSitemap: SitemapDefinition,\n  matchName: string,\n  isChunked: boolean,\n  resolvers: NitroUrlResolvers,\n  runtimeConfig: ModuleRuntimeConfig,\n  nitro?: NitroApp,\n): Promise<ResolvedSitemapUrlsResult> {\n  const event = resolvers.event\n  const shouldCache = !import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === 'number' && runtimeConfig.cacheMaxAgeSeconds > 0\n  if (shouldCache && event) {\n    return buildResolvedSitemapUrlsCached(event, effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro)\n  }\n  return buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro)\n}\n\nexport async function buildSitemapUrls(sitemap: SitemapDefinition, resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp): Promise<ResolvedSitemapUrlsResult> {\n  const { sitemaps, autoI18n, defaultSitemapsChunkSize } = runtimeConfig\n\n  const chunkSize = defaultSitemapsChunkSize || undefined\n  const chunkInfo = parseChunkInfo(sitemap.sitemapName, sitemaps, chunkSize)\n\n  if (autoI18n?.differentDomains) {\n    const domain = autoI18n.locales.find(e => e.language === sitemap.sitemapName || e.code === sitemap.sitemapName)?.domain\n    if (domain) {\n      const _tester = resolvers.canonicalUrlResolver\n      resolvers.canonicalUrlResolver = (path: string) => resolveSitePath(path, {\n        absolute: true,\n        withBase: false,\n        siteUrl: withHttps(domain),\n        trailingSlash: _tester('/test/').endsWith('/'),\n        base: '/',\n      })\n    }\n  }\n\n  // For chunked sitemaps the base sitemap config holds the sources; all chunks share one cache entry.\n  let effectiveSitemap = sitemap\n  const baseSitemapName = chunkInfo.baseSitemapName\n  if (chunkInfo.isChunked && baseSitemapName !== sitemap.sitemapName && sitemaps[baseSitemapName]) {\n    effectiveSitemap = sitemaps[baseSitemapName]\n  }\n\n  const matchName = chunkInfo.isChunked ? baseSitemapName : sitemap.sitemapName\n  const resolved = await getResolvedSitemapUrls(effectiveSitemap, matchName, chunkInfo.isChunked, resolvers, runtimeConfig, nitro)\n\n  // Slice last so all chunks of the same base reuse the cached sorted array.\n  const urls = sliceUrlsForChunk(resolved.urls, sitemap.sitemapName, sitemaps, chunkSize)\n  return { urls, failedSources: resolved.failedSources }\n}\n\nexport { urlsToXml } from './xml'\n"
  },
  {
    "path": "src/runtime/server/sitemap/builder/xml.ts",
    "content": "import type { ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl } from '../../../types'\nimport { withQuery } from 'ufo'\nimport { xmlEscape } from '../../utils'\n\nexport function escapeValueForXml(value: boolean | string | number): string {\n  if (value === true || value === false)\n    return value ? 'yes' : 'no'\n  return xmlEscape(String(value))\n}\n\nfunction yesNo(v: boolean | string) {\n  return v === 'yes' || v === true ? 'yes' : 'no'\n}\n\nconst URLSET_OPENING_TAG = '<urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">'\n\nfunction buildUrlXml(url: ResolvedSitemapUrl, NL: string, I1: string, I2: string, I3: string, I4: string): string {\n  let xml = `${I1}<url>${NL}`\n\n  if (url.loc)\n    xml += `${I2}<loc>${xmlEscape(url.loc)}</loc>${NL}`\n  if (url.lastmod)\n    xml += `${I2}<lastmod>${xmlEscape(url.lastmod)}</lastmod>${NL}`\n  if (url.changefreq)\n    xml += `${I2}<changefreq>${xmlEscape(url.changefreq)}</changefreq>${NL}`\n  if (url.priority !== undefined) {\n    const p = typeof url.priority === 'number' ? url.priority : Number.parseFloat(url.priority)\n    xml += `${I2}<priority>${p.toFixed(1)}</priority>${NL}`\n  }\n\n  if (url.alternatives) {\n    for (const alt of url.alternatives) {\n      let attrs = ''\n      for (const [k, v] of Object.entries(alt)) attrs += ` ${k}=\"${xmlEscape(String(v))}\"`\n      xml += `${I2}<xhtml:link rel=\"alternate\"${attrs} />${NL}`\n    }\n  }\n\n  if (url.images) {\n    for (const img of url.images) {\n      xml += `${I2}<image:image>${NL}${I3}<image:loc>${xmlEscape(img.loc as string)}</image:loc>${NL}`\n      if (img.title)\n        xml += `${I3}<image:title>${xmlEscape(img.title)}</image:title>${NL}`\n      if (img.caption)\n        xml += `${I3}<image:caption>${xmlEscape(img.caption)}</image:caption>${NL}`\n      if (img.geo_location)\n        xml += `${I3}<image:geo_location>${xmlEscape(img.geo_location)}</image:geo_location>${NL}`\n      if (img.license)\n        xml += `${I3}<image:license>${xmlEscape(img.license as string)}</image:license>${NL}`\n      xml += `${I2}</image:image>${NL}`\n    }\n  }\n\n  if (url.videos) {\n    for (const video of url.videos) {\n      xml += `${I2}<video:video>${NL}${I3}<video:title>${xmlEscape(video.title)}</video:title>${NL}`\n      if (video.thumbnail_loc)\n        xml += `${I3}<video:thumbnail_loc>${xmlEscape(video.thumbnail_loc as string)}</video:thumbnail_loc>${NL}`\n      xml += `${I3}<video:description>${xmlEscape(video.description)}</video:description>${NL}`\n      if (video.content_loc)\n        xml += `${I3}<video:content_loc>${xmlEscape(video.content_loc as string)}</video:content_loc>${NL}`\n      if (video.player_loc)\n        xml += `${I3}<video:player_loc>${xmlEscape(video.player_loc as string)}</video:player_loc>${NL}`\n      if (video.duration !== undefined)\n        xml += `${I3}<video:duration>${escapeValueForXml(video.duration)}</video:duration>${NL}`\n      if (video.expiration_date)\n        xml += `${I3}<video:expiration_date>${xmlEscape(video.expiration_date)}</video:expiration_date>${NL}`\n      if (video.rating !== undefined)\n        xml += `${I3}<video:rating>${escapeValueForXml(video.rating)}</video:rating>${NL}`\n      if (video.view_count !== undefined)\n        xml += `${I3}<video:view_count>${escapeValueForXml(video.view_count)}</video:view_count>${NL}`\n      if (video.publication_date)\n        xml += `${I3}<video:publication_date>${xmlEscape(video.publication_date)}</video:publication_date>${NL}`\n      if (video.family_friendly !== undefined)\n        xml += `${I3}<video:family_friendly>${yesNo(video.family_friendly)}</video:family_friendly>${NL}`\n      if (video.restriction)\n        xml += `${I3}<video:restriction relationship=\"${xmlEscape(video.restriction.relationship || 'allow')}\">${xmlEscape(video.restriction.restriction)}</video:restriction>${NL}`\n      if (video.platform)\n        xml += `${I3}<video:platform relationship=\"${xmlEscape(video.platform.relationship || 'allow')}\">${xmlEscape(video.platform.platform)}</video:platform>${NL}`\n      if (video.requires_subscription !== undefined)\n        xml += `${I3}<video:requires_subscription>${yesNo(video.requires_subscription)}</video:requires_subscription>${NL}`\n      if (video.price) {\n        for (const price of video.price) {\n          const c = price.currency ? ` currency=\"${xmlEscape(price.currency)}\"` : ''\n          const t = price.type ? ` type=\"${xmlEscape(price.type)}\"` : ''\n          xml += `${I3}<video:price${c}${t}>${xmlEscape(String(price.price ?? ''))}</video:price>${NL}`\n        }\n      }\n      if (video.uploader) {\n        const info = video.uploader.info ? ` info=\"${xmlEscape(video.uploader.info as string)}\"` : ''\n        xml += `${I3}<video:uploader${info}>${xmlEscape(video.uploader.uploader)}</video:uploader>${NL}`\n      }\n      if (video.live !== undefined)\n        xml += `${I3}<video:live>${yesNo(video.live)}</video:live>${NL}`\n      if (video.tag) {\n        const tags = Array.isArray(video.tag) ? video.tag : [video.tag]\n        for (const t of tags) xml += `${I3}<video:tag>${xmlEscape(t)}</video:tag>${NL}`\n      }\n      if (video.category)\n        xml += `${I3}<video:category>${xmlEscape(video.category)}</video:category>${NL}`\n      if (video.gallery_loc)\n        xml += `${I3}<video:gallery_loc>${xmlEscape(video.gallery_loc as string)}</video:gallery_loc>${NL}`\n      xml += `${I2}</video:video>${NL}`\n    }\n  }\n\n  if (url.news) {\n    xml += `${I2}<news:news>${NL}${I3}<news:publication>${NL}`\n    xml += `${I4}<news:name>${xmlEscape(url.news.publication.name)}</news:name>${NL}`\n    xml += `${I4}<news:language>${xmlEscape(url.news.publication.language)}</news:language>${NL}`\n    xml += `${I3}</news:publication>${NL}`\n    if (url.news.title)\n      xml += `${I3}<news:title>${xmlEscape(url.news.title)}</news:title>${NL}`\n    if (url.news.publication_date)\n      xml += `${I3}<news:publication_date>${xmlEscape(url.news.publication_date)}</news:publication_date>${NL}`\n    xml += `${I2}</news:news>${NL}`\n  }\n\n  if (import.meta.dev && url._warnings?.length) {\n    for (const w of url._warnings)\n      xml += `${I2}<!-- WARN: ${w} -->${NL}`\n  }\n\n  xml += `${I1}</url>`\n  return xml\n}\n\nexport function urlsToXml(\n  urls: ResolvedSitemapUrl[],\n  resolvers: NitroUrlResolvers,\n  { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>,\n  errorInfo?: { messages: string[], urls: string[] },\n): string {\n  let xslHref = xsl ? resolvers.relativeBaseUrlResolver(xsl) : false\n\n  if (xslHref && errorInfo?.messages.length) {\n    xslHref = withQuery(xslHref, {\n      errors: 'true',\n      error_messages: errorInfo.messages,\n      error_urls: errorInfo.urls,\n    })\n  }\n\n  const NL = minify ? '' : '\\n'\n  const I1 = minify ? '' : '    '\n  const I2 = minify ? '' : '        '\n  const I3 = minify ? '' : '            '\n  const I4 = minify ? '' : '                '\n\n  let xml = xslHref\n    ? `<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"${escapeValueForXml(xslHref)}\"?>${NL}`\n    : `<?xml version=\"1.0\" encoding=\"UTF-8\"?>${NL}`\n\n  xml += URLSET_OPENING_TAG + NL\n\n  for (const url of urls) {\n    xml += buildUrlXml(url, NL, I1, I2, I3, I4) + NL\n  }\n\n  xml += '</urlset>'\n\n  if (credits) {\n    xml += `${NL}<!-- XML Sitemap generated by @nuxtjs/sitemap v${version} at ${new Date().toISOString()} -->`\n  }\n\n  return xml\n}\n"
  },
  {
    "path": "src/runtime/server/sitemap/event-handlers.ts",
    "content": "import type { H3Event } from 'h3'\nimport { appendHeader, createError, getRouterParam, sendRedirect, setHeader } from 'h3'\nimport { useNitroApp, useRuntimeConfig } from 'nitropack/runtime'\nimport { joinURL, withBase, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from 'ufo'\nimport { useSitemapRuntimeConfig } from '../utils'\nimport { buildSitemapIndex, urlsToIndexXml } from './builder/sitemap-index'\nimport { createSitemap, useNitroUrlResolvers } from './nitro'\nimport { getSitemapConfig, parseChunkInfo } from './utils/chunk'\n\nexport async function sitemapXmlEventHandler(e: H3Event) {\n  const runtimeConfig = useSitemapRuntimeConfig()\n  const { sitemaps } = runtimeConfig\n  if ('index' in sitemaps)\n    return sendRedirect(e, withBase('/sitemap_index.xml', useRuntimeConfig().app.baseURL), import.meta.dev ? 302 : 301)\n\n  return createSitemap(e, Object.values(sitemaps)[0]!, runtimeConfig)\n}\n\nexport async function sitemapIndexXmlEventHandler(e: H3Event) {\n  const runtimeConfig = useSitemapRuntimeConfig()\n  const nitro = useNitroApp()\n  const resolvers = useNitroUrlResolvers(e)\n  const { entries: sitemaps, failedSources } = await buildSitemapIndex(resolvers, runtimeConfig, nitro)\n\n  if (import.meta.prerender) {\n    appendHeader(\n      e,\n      'x-nitro-prerender',\n      sitemaps.filter(entry => !!entry._sitemapName)\n        .map(entry => encodeURIComponent(joinURL(runtimeConfig.sitemapsPathPrefix || '', `/${entry._sitemapName}.xml`))).join(', '),\n    )\n  }\n\n  const indexResolvedCtx = { sitemaps, event: e }\n  await nitro.hooks.callHook('sitemap:index-resolved', indexResolvedCtx)\n\n  const errorInfo = failedSources.length > 0\n    ? { messages: failedSources.map(f => f.error), urls: failedSources.map(f => f.url) }\n    : undefined\n\n  const output = urlsToIndexXml(indexResolvedCtx.sitemaps, resolvers, runtimeConfig, errorInfo)\n  const ctx = { sitemap: output, sitemapName: 'sitemap', event: e }\n  await nitro.hooks.callHook('sitemap:output', ctx)\n\n  setHeader(e, 'Content-Type', 'text/xml; charset=UTF-8')\n  if (runtimeConfig.cacheMaxAgeSeconds) {\n    setHeader(e, 'Cache-Control', `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, s-maxage=${runtimeConfig.cacheMaxAgeSeconds}, stale-while-revalidate=3600`)\n    const now = new Date()\n    setHeader(e, 'X-Sitemap-Generated', now.toISOString())\n    setHeader(e, 'X-Sitemap-Cache-Duration', `${runtimeConfig.cacheMaxAgeSeconds}s`)\n    const expiryTime = new Date(now.getTime() + (runtimeConfig.cacheMaxAgeSeconds * 1000))\n    setHeader(e, 'X-Sitemap-Cache-Expires', expiryTime.toISOString())\n    const remainingSeconds = Math.floor((expiryTime.getTime() - now.getTime()) / 1000)\n    setHeader(e, 'X-Sitemap-Cache-Remaining', `${remainingSeconds}s`)\n  }\n  else {\n    setHeader(e, 'Cache-Control', `no-cache, no-store`)\n  }\n\n  return ctx.sitemap\n}\n\nexport async function sitemapChildXmlEventHandler(e: H3Event) {\n  // Only process .xml requests - pass through for other paths\n  if (!e.path.endsWith('.xml'))\n    return\n\n  const runtimeConfig = useSitemapRuntimeConfig(e)\n  const { sitemaps } = runtimeConfig\n\n  let sitemapName = getRouterParam(e, 'sitemap')\n  if (!sitemapName) {\n    const path = e.path\n    const match = path.match(/(?:\\/__sitemap__\\/)?(.+)\\.xml$/)\n    if (match)\n      sitemapName = match[1]\n  }\n\n  if (!sitemapName)\n    throw createError({ statusCode: 400, message: 'Invalid sitemap request' })\n\n  sitemapName = sitemapName.replace(/\\.xml$/, '')\n  sitemapName = withLeadingSlash(sitemapName)\n  if (sitemapName.startsWith('/__sitemap__/'))\n    sitemapName = sitemapName.replace('/__sitemap__/', '/')\n\n  if (runtimeConfig.sitemapsPathPrefix) {\n    const prefix = withLeadingSlash(runtimeConfig.sitemapsPathPrefix)\n    if (sitemapName.startsWith(prefix))\n      sitemapName = sitemapName.replace(prefix, '/')\n  }\n  sitemapName = withoutLeadingSlash(withoutTrailingSlash(sitemapName))\n\n  const chunkInfo = parseChunkInfo(sitemapName, sitemaps, runtimeConfig.defaultSitemapsChunkSize)\n  const isAutoChunked = typeof sitemaps.chunks !== 'undefined' && !Number.isNaN(Number(sitemapName))\n  const sitemapExists = sitemapName in sitemaps || chunkInfo.baseSitemapName in sitemaps || isAutoChunked\n\n  if (!sitemapExists)\n    throw createError({ statusCode: 404, message: `Sitemap \"${sitemapName}\" not found.` })\n\n  if (chunkInfo.isChunked && chunkInfo.chunkIndex !== undefined) {\n    const baseSitemap = sitemaps[chunkInfo.baseSitemapName]\n    if (baseSitemap && !baseSitemap.chunks && !baseSitemap._isChunking)\n      throw createError({ statusCode: 404, message: `Sitemap \"${chunkInfo.baseSitemapName}\" does not support chunking.` })\n\n    if (baseSitemap?._chunkCount !== undefined && chunkInfo.chunkIndex >= baseSitemap._chunkCount)\n      throw createError({ statusCode: 404, message: `Chunk ${chunkInfo.chunkIndex} does not exist for sitemap \"${chunkInfo.baseSitemapName}\".` })\n  }\n\n  const sitemapConfig = getSitemapConfig(sitemapName, sitemaps, runtimeConfig.defaultSitemapsChunkSize || undefined)\n  return createSitemap(e, sitemapConfig, runtimeConfig)\n}\n"
  },
  {
    "path": "src/runtime/server/sitemap/nitro.ts",
    "content": "import type { H3Event } from 'h3'\nimport type { NitroApp } from 'nitropack/types'\nimport type {\n  ModuleRuntimeConfig,\n  NitroUrlResolvers,\n  ResolvedSitemapUrl,\n  SitemapDefinition,\n  SitemapRenderCtx,\n} from '../../types'\nimport { defu } from 'defu'\nimport { createError, getHeader, getQuery, setHeader } from 'h3'\nimport { defineCachedFunction, useNitroApp } from 'nitropack/runtime'\nimport { fixSlashes } from 'nuxt-site-config/urls'\n// @ts-expect-error virtual\nimport { getPathRobotConfig } from '#internal/nuxt-robots/getPathRobotConfig' // can't solve this\nimport { getSiteConfig } from '#site-config/server/composables/getSiteConfig'\nimport { createSitePathResolver } from '#site-config/server/composables/utils'\n// @ts-expect-error virtual module\nimport staticConfig from '#sitemap-virtual/static-config.mjs'\nimport { logger, mergeOnKey, splitForLocales } from '../../utils-pure'\nimport { createNitroRouteRuleMatcher } from '../kit'\nimport { buildSitemapUrls, urlsToXml } from './builder/sitemap'\nimport { normaliseEntry, preNormalizeEntry } from './urlset/normalise'\nimport { sortInPlace } from './urlset/sort'\n\n// Read at module init: defineCachedFunction takes a static maxAge. Falls back to 10 minutes\n// when caching is disabled in static config (still bypassed at request time via shouldCache).\nconst SERVER_CACHE_MAX_AGE = (staticConfig.cacheMaxAgeSeconds as number | false) || 60 * 10\n\ninterface SitemapNitroApp extends NitroApp {\n  _sitemapWarned?: boolean\n}\n\nexport function useNitroUrlResolvers(e: H3Event): NitroUrlResolvers {\n  const canonicalQuery = getQuery(e).canonical\n  const isShowingCanonical = typeof canonicalQuery !== 'undefined' && canonicalQuery !== 'false'\n  const siteConfig = getSiteConfig(e)\n  return {\n    event: e,\n    fixSlashes: (path: string) => fixSlashes(siteConfig.trailingSlash, path),\n    // we need these as they depend on the nitro event\n    canonicalUrlResolver: createSitePathResolver(e, {\n      canonical: isShowingCanonical || !import.meta.dev,\n      absolute: true,\n      withBase: true,\n    }),\n    relativeBaseUrlResolver: createSitePathResolver(e, { absolute: false, withBase: true }),\n  }\n}\n\n// Shared sitemap building logic\nasync function buildSitemapXml(event: H3Event, definition: SitemapDefinition, resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig) {\n  const { sitemapName } = definition\n  const nitro = useNitroApp() as SitemapNitroApp\n  if (import.meta.prerender) {\n    const config = getSiteConfig(event)\n    if (!config.url && !nitro._sitemapWarned) {\n      nitro._sitemapWarned = true\n      logger.error('Sitemap Site URL missing!')\n      logger.info('To fix this please add `{ site: { url: \\'site.com\\' } }` to your Nuxt config or a `NUXT_PUBLIC_SITE_URL=site.com` to your .env. Learn more at https://nuxtseo.com/site-config/getting-started/how-it-works')\n      throw createError({\n        statusMessage: 'You must provide a site URL to prerender a sitemap.',\n        statusCode: 500,\n      })\n    }\n  }\n  const { urls: sitemapUrls, failedSources } = await buildSitemapUrls(definition, resolvers, runtimeConfig, nitro)\n\n  if (import.meta.prerender && failedSources.length) {\n    throw createError({\n      statusCode: 500,\n      message: `Sitemap generation failed due to ${failedSources.length} failed sources: ${failedSources.map(s => `\"${s.url}\" (${s.error})`).join(', ')}`,\n    })\n  }\n\n  const routeRuleMatcher = createNitroRouteRuleMatcher()\n  const { autoI18n } = runtimeConfig\n\n  // Process in place to avoid creating intermediate arrays\n  let validCount = 0\n  for (let i = 0; i < sitemapUrls.length; i++) {\n    const u = sitemapUrls[i]!\n    const path = u._path?.pathname || u.loc\n\n    // Early continue for robots blocked paths\n    if (!getPathRobotConfig(event, { path, skipSiteIndexable: true }).indexable)\n      continue\n\n    let routeRules = routeRuleMatcher(path)\n\n    // Apply top-level path without prefix\n    if (autoI18n?.locales && autoI18n?.strategy !== 'no_prefix') {\n      const match = splitForLocales(path, autoI18n.locales.map(l => l.code))\n      const pathWithoutPrefix = match[1]\n      if (pathWithoutPrefix && pathWithoutPrefix !== path)\n        routeRules = defu(routeRules, routeRuleMatcher(pathWithoutPrefix))\n    }\n\n    // Skip invalid entries\n    if (routeRules.sitemap === false)\n      continue\n    if (typeof routeRules.robots !== 'undefined' && !routeRules.robots)\n      continue\n\n    const hasRobotsDisabled = Object.entries(routeRules.headers || {})\n      .some(([name, value]) => name.toLowerCase() === 'x-robots-tag' && value.toLowerCase().includes('noindex'))\n\n    if (routeRules.redirect || hasRobotsDisabled)\n      continue\n\n    // Move valid entries to the front of the array\n    sitemapUrls[validCount++] = (routeRules.sitemap ? defu(u, routeRules.sitemap) : u) as ResolvedSitemapUrl\n  }\n\n  // Truncate array to valid entries only\n  sitemapUrls.length = validCount\n  if (import.meta.dev && validCount === 0 && sitemapUrls.length > 0) {\n    logger.warn(`Sitemap had ${sitemapUrls.length} that were all filtered out. This may be due to a robots rules blocking these URLs from indexing. Check your /** route rules or robots.txt configuration.`)\n  }\n\n  // 6. nitro hooks\n  const locSize = sitemapUrls.length\n  const resolvedCtx: SitemapRenderCtx = {\n    urls: sitemapUrls,\n    sitemapName,\n    event,\n  }\n  await nitro.hooks.callHook('sitemap:resolved', resolvedCtx)\n  // we need to normalize any new urls otherwise they won't appear in the final sitemap\n  // Note this is risky and users should be using the sitemap:input hook for additions\n  if (resolvedCtx.urls.length !== locSize) {\n    resolvedCtx.urls = resolvedCtx.urls.map(e => preNormalizeEntry(e, resolvers))\n  }\n\n  const maybeSort = (urls: ResolvedSitemapUrl[]) => runtimeConfig.sortEntries ? sortInPlace(urls) : urls\n  // final urls\n  const defaults = definition.defaults || {}\n  const normalizedPreDedupe = resolvedCtx.urls.map(e => normaliseEntry(e, defaults, resolvers))\n  const urls = maybeSort(mergeOnKey(normalizedPreDedupe, '_key').map(e => normaliseEntry(e, defaults, resolvers)))\n\n  // Check if this is a chunk request that would be empty\n  if (definition._isChunking && definition.sitemapName.includes('-')) {\n    const parts = definition.sitemapName.split('-')\n    const lastPart = parts.pop()\n    if (!Number.isNaN(Number(lastPart))) {\n      const chunkIndex = Number(lastPart)\n      const baseSitemapName = parts.join('-')\n      // If this is a chunk and we have no URLs, it means the chunk doesn't exist\n      if (urls.length === 0 && chunkIndex > 0) {\n        throw createError({\n          statusCode: 404,\n          message: `Sitemap chunk ${chunkIndex} for \"${baseSitemapName}\" does not exist.`,\n        })\n      }\n    }\n  }\n\n  // Prepare error information for XSL if there are failed sources\n  const errorInfo = failedSources.length > 0\n    ? {\n        messages: failedSources.map(f => f.error),\n        urls: failedSources.map(f => f.url),\n      }\n    : undefined\n  const sitemap = urlsToXml(urls, resolvers, runtimeConfig, errorInfo)\n\n  const ctx = { sitemap, sitemapName, event }\n  await nitro.hooks.callHook('sitemap:output', ctx)\n  return ctx.sitemap\n}\n\n// Create cached function for building sitemap XML\nconst buildSitemapXmlCached = defineCachedFunction(\n  buildSitemapXml,\n  {\n    name: 'sitemap:xml',\n    group: 'sitemap',\n    maxAge: SERVER_CACHE_MAX_AGE,\n    base: 'sitemap', // Use the sitemap storage\n    getKey: (event: H3Event, definition: SitemapDefinition) => {\n      // Include headers that could affect the output in the cache key\n      const host = getHeader(event, 'host') || getHeader(event, 'x-forwarded-host') || ''\n      const proto = getHeader(event, 'x-forwarded-proto') || 'https'\n      const sitemapName = definition.sitemapName || 'default'\n      return `${sitemapName}-${proto}-${host}`\n    },\n    swr: true, // Enable stale-while-revalidate\n  },\n)\n\nexport async function createSitemap(event: H3Event, definition: SitemapDefinition, runtimeConfig: ModuleRuntimeConfig) {\n  const resolvers = useNitroUrlResolvers(event)\n\n  // Choose between cached or direct generation.\n  // Skip caching during prerender: the crawl may run before `prerender:done` has written\n  // `global-sources.json`, so an early empty result would poison the cache and be returned\n  // on the follow-up render, shipping an empty sitemap.\n  const shouldCache = !import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === 'number' && runtimeConfig.cacheMaxAgeSeconds > 0\n  const xml = shouldCache\n    ? await buildSitemapXmlCached(event, definition, resolvers, runtimeConfig)\n    : await buildSitemapXml(event, definition, resolvers, runtimeConfig)\n\n  // Set headers\n  setHeader(event, 'Content-Type', 'text/xml; charset=UTF-8')\n  if (runtimeConfig.cacheMaxAgeSeconds) {\n    setHeader(event, 'Cache-Control', `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, s-maxage=${runtimeConfig.cacheMaxAgeSeconds}, stale-while-revalidate=3600`)\n\n    // Add debug headers when caching is enabled\n    const now = new Date()\n    setHeader(event, 'X-Sitemap-Generated', now.toISOString())\n    setHeader(event, 'X-Sitemap-Cache-Duration', `${runtimeConfig.cacheMaxAgeSeconds}s`)\n\n    // Calculate expiry time\n    const expiryTime = new Date(now.getTime() + (runtimeConfig.cacheMaxAgeSeconds * 1000))\n    setHeader(event, 'X-Sitemap-Cache-Expires', expiryTime.toISOString())\n\n    // Calculate remaining time\n    const remainingSeconds = Math.floor((expiryTime.getTime() - now.getTime()) / 1000)\n    setHeader(event, 'X-Sitemap-Cache-Remaining', `${remainingSeconds}s`)\n  }\n  else {\n    setHeader(event, 'Cache-Control', `no-cache, no-store`)\n  }\n  event.context._isSitemap = true\n  return xml\n}\n"
  },
  {
    "path": "src/runtime/server/sitemap/urlset/normalise.ts",
    "content": "import type {\n  NitroUrlResolvers,\n  ResolvedSitemapUrl,\n  SitemapUrl,\n  SitemapUrlInput,\n} from '../../../types'\nimport { defu } from 'defu'\nimport {\n  encodePath,\n  hasProtocol,\n  parsePath,\n  parseQuery,\n  parseURL,\n  stringifyParsedURL,\n  stringifyQuery,\n  withoutTrailingSlash,\n} from 'ufo'\nimport { mergeOnKey } from '../../../utils-pure'\n\nconst VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never']\n\nexport function validateSitemapUrl(url: SitemapUrlInput): string[] {\n  if (typeof url === 'string')\n    return []\n  const warnings: string[] = []\n  if (url.lastmod) {\n    const d = typeof url.lastmod === 'string' ? url.lastmod : undefined\n    if (d && !isValidW3CDate(d))\n      warnings.push(`lastmod \"${d}\" is not a valid W3C date`)\n  }\n  if (url.changefreq && !VALID_CHANGEFREQ.includes(url.changefreq))\n    warnings.push(`changefreq \"${url.changefreq}\" is not valid (expected: always|hourly|daily|weekly|monthly|yearly|never)`)\n  if (url.priority !== undefined) {\n    const p = typeof url.priority === 'number' ? url.priority : Number.parseFloat(String(url.priority))\n    if (Number.isNaN(p) || p < 0 || p > 1)\n      warnings.push(`priority \"${url.priority}\" is not valid (expected: number between 0.0 and 1.0)`)\n  }\n  return warnings\n}\n\nfunction resolve(s: string | URL, resolvers?: NitroUrlResolvers): string\nfunction resolve(s: string | URL | undefined, resolvers?: NitroUrlResolvers): string | undefined\nfunction resolve(s: string | URL | undefined, resolvers?: NitroUrlResolvers): string | undefined {\n  if (typeof s === 'undefined')\n    return undefined\n  // convert url to string\n  const str = typeof s === 'string' ? s : s.toString()\n  if (!resolvers)\n    return str\n  // avoid transforming remote urls and urls already resolved\n  if (hasProtocol(str, { acceptRelative: true, strict: false }))\n    return resolvers.fixSlashes(str)\n\n  return resolvers.canonicalUrlResolver(str)\n}\n\nfunction removeTrailingSlash(s: string) {\n  // need to account for query strings and hashes\n  // this assumes the URL is normalised\n  return s.replace(/\\/(\\?|#|$)/, '$1')\n}\n\nexport function preNormalizeEntry(_e: SitemapUrl | string, resolvers?: NitroUrlResolvers): ResolvedSitemapUrl {\n  // Normalize url → loc before casting to ResolvedSitemapUrl\n  const input = typeof _e === 'string' ? { loc: _e } : { ..._e }\n  if (input.url && !input.loc) {\n    input.loc = input.url\n  }\n  delete input.url\n  if (typeof input.loc !== 'string') {\n    input.loc = ''\n  }\n  // Check if URL is marked as already encoded\n  const skipEncoding = input._encoded === true\n  const e = input as ResolvedSitemapUrl\n  // we want a uniform loc so we can dedupe using it, remove slashes and only get the path\n  e.loc = removeTrailingSlash(e.loc)\n  e._abs = hasProtocol(e.loc, { acceptRelative: false, strict: false })\n  try {\n    e._path = e._abs ? parseURL(e.loc) : parsePath(e.loc)\n  }\n  catch {\n    e._path = null\n  }\n  if (e._path) {\n    const search = e._path.search\n    // Skip parse/stringify if no query string\n    const qs = search && search.length > 1\n      ? stringifyQuery(parseQuery(search))\n      : ''\n    const pathname = skipEncoding ? e._path.pathname : encodePath(e._path.pathname)\n    e._relativeLoc = `${pathname}${qs.length ? `?${qs}` : ''}`\n    if (e._path.host) {\n      e.loc = stringifyParsedURL(e._path)\n    }\n    else {\n      e.loc = e._relativeLoc\n    }\n  }\n  else if (!skipEncoding && !isEncoded(e.loc)) {\n    e.loc = encodeURI(e.loc)\n  }\n  if (e.loc === '')\n    e.loc = `/`\n  e.loc = resolve(e.loc, resolvers)\n  e._key = `${e._sitemap || ''}${withoutTrailingSlash(e.loc)}`\n  return e as ResolvedSitemapUrl\n}\n\nexport function isEncoded(url: string) {\n  // checks, if an url is already decoded\n  try {\n    return url !== decodeURIComponent(url)\n  }\n  catch {\n    return false\n  }\n}\n\nexport function normaliseEntry(_e: ResolvedSitemapUrl, defaults: Omit<SitemapUrl, 'loc'>, resolvers?: NitroUrlResolvers): ResolvedSitemapUrl {\n  const e = defu(_e, defaults) as ResolvedSitemapUrl\n  if (import.meta.dev) {\n    const warnings = validateSitemapUrl(e)\n    if (warnings.length)\n      e._warnings = (e._warnings || []).concat(warnings)\n  }\n  if (e.lastmod) {\n    const date = normaliseDate(e.lastmod)\n    if (date)\n      e.lastmod = date\n    else\n      delete e.lastmod\n  }\n  // make sure it's valid\n  if (!e.lastmod)\n    delete e.lastmod\n\n  // need to make sure siteURL doesn't have the base on the end\n  e.loc = resolve(e.loc, resolvers)\n\n  // correct alternative hrefs\n  if (e.alternatives) {\n    const alternatives = e.alternatives.map(a => ({ ...a }))\n    for (const alt of alternatives) {\n      if (typeof alt.href === 'string') {\n        alt.href = resolve(alt.href, resolvers)\n      }\n      else if (typeof alt.href === 'object' && alt.href) {\n        alt.href = resolve(alt.href.href, resolvers)\n      }\n    }\n    e.alternatives = mergeOnKey(alternatives, 'hreflang')\n  }\n\n  if (e.images) {\n    const images = e.images.map(i => ({ ...i }))\n    for (const img of images) {\n      img.loc = resolve(img.loc, resolvers)\n    }\n    e.images = mergeOnKey(images, 'loc')\n  }\n\n  if (e.videos) {\n    const videos = e.videos.map(v => ({ ...v }))\n    for (const video of videos) {\n      if (video.content_loc) {\n        video.content_loc = resolve(video.content_loc, resolvers)\n      }\n    }\n    e.videos = mergeOnKey(videos, 'content_loc')\n  }\n  return e\n}\n\nconst IS_VALID_W3C_DATE = [\n  /(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))/,\n  /^\\d{4}-[01]\\d-[0-3]\\d$/,\n  /^\\d{4}-[01]\\d$/,\n  /^\\d{4}$/,\n]\nexport function isValidW3CDate(d: string) {\n  return IS_VALID_W3C_DATE.some(r => r.test(d))\n}\n\nexport function normaliseDate(date: string | Date): string\nexport function normaliseDate(d: Date | string) {\n  // lastmod must adhere to W3C Datetime encoding rules\n  if (typeof d === 'string') {\n    // correct a time component without a timezone\n    const tIdx = d.indexOf('T')\n    if (tIdx !== -1) {\n      const t = d.slice(tIdx + 1)\n      if (!t.includes('+') && !t.includes('-') && !t.includes('Z')) {\n        // add UTC timezone\n        d += 'Z'\n      }\n    }\n    // skip invalid w3c date\n    if (!isValidW3CDate(d))\n      return false\n    // otherwise we need to parse it\n    d = new Date(d)\n    d.setMilliseconds(0)\n    // check for invalid date\n    if (Number.isNaN(d.getTime()))\n      return false\n  }\n  const z = (n: number) => (`0${n}`).slice(-2)\n  // need to normalise for google sitemap spec\n  const date = `${d.getUTCFullYear()\n  }-${\n    z(d.getUTCMonth() + 1)\n  }-${\n    z(d.getUTCDate())\n  }`\n  // check if we have a time set\n  if (d.getUTCHours() > 0 || d.getUTCMinutes() > 0 || d.getUTCSeconds() > 0) {\n    return (\n      `${date}T${\n        z(d.getUTCHours())\n      }:${\n        z(d.getUTCMinutes())\n      }:${\n        z(d.getUTCSeconds())\n      }Z`\n    )\n  }\n  return date\n}\n"
  },
  {
    "path": "src/runtime/server/sitemap/urlset/sort.ts",
    "content": "import type {\n  ResolvedSitemapUrl,\n  SitemapUrlInput,\n} from '../../../types'\n\nexport function sortInPlace<T extends SitemapUrlInput[] | ResolvedSitemapUrl[]>(urls: T): T {\n  // In-place sort to avoid creating new arrays\n  urls.sort((a, b) => {\n    const aLoc = typeof a === 'string' ? a : a.loc\n    const bLoc = typeof b === 'string' ? b : b.loc\n\n    // First sort by path segments\n    const aSegments = aLoc.split('/').length\n    const bSegments = bLoc.split('/').length\n    if (aSegments !== bSegments) {\n      return aSegments - bSegments\n    }\n\n    // Then sort by locale compare with numeric\n    return aLoc.localeCompare(bLoc, undefined, { numeric: true })\n  })\n\n  return urls\n}\n"
  },
  {
    "path": "src/runtime/server/sitemap/urlset/sources.ts",
    "content": "import type { H3Event } from 'h3'\nimport type { FetchError } from 'ofetch'\nimport type {\n  ModuleRuntimeConfig,\n  SitemapSourceBase,\n  SitemapSourceInput,\n  SitemapSourceResolved,\n  SitemapUrlInput,\n} from '../../../types'\nimport { parseSitemapXml } from '@nuxtjs/sitemap/utils'\nimport { defu } from 'defu'\nimport { getRequestHost } from 'h3'\nimport { parseURL } from 'ufo'\nimport { logger } from '../../../utils-pure'\n\nexport function normalizeSourceInput(source: SitemapSourceInput): SitemapSourceBase | SitemapSourceResolved {\n  // string -> { fetch: string, context: { name: 'hook' } }\n  if (typeof source === 'string') {\n    return { context: { name: 'hook' }, fetch: source }\n  }\n  // [string, FetchOptions] -> { fetch: [string, FetchOptions], context: { name: 'hook' } }\n  if (Array.isArray(source)) {\n    return { context: { name: 'hook' }, fetch: source }\n  }\n  return source\n}\n\nasync function tryFetchWithFallback(url: string, options: any, event?: H3Event): Promise<any> {\n  const isExternalUrl = !url.startsWith('/')\n  // For external URLs, try different fetch strategies\n  if (isExternalUrl) {\n    const strategies = [\n      // Strategy 1: Use globalThis.$fetch (original approach)\n      () => globalThis.$fetch(url, options),\n      // Strategy 2: If event is available, try using event context even for external URLs\n      event ? () => event.$fetch(url, options) : null,\n      // Strategy 3: Use native fetch as last resort\n      () => $fetch(url, options),\n    ].filter(Boolean)\n\n    let lastError: Error | null = null\n    for (const strategy of strategies) {\n      try {\n        return await strategy!()\n      }\n      catch (error) {\n        lastError = error as Error\n        continue\n      }\n    }\n    throw lastError\n  }\n\n  // For internal URLs, use the original logic\n  const fetchContainer = (url.startsWith('/') && event) ? event : globalThis\n  return await fetchContainer.$fetch(url, options)\n}\n\nexport async function fetchDataSource(input: SitemapSourceBase | SitemapSourceResolved, event?: H3Event): Promise<SitemapSourceResolved> {\n  const context = typeof input.context === 'string' ? { name: input.context } : input.context || { name: 'fetch' }\n  const url = typeof input.fetch === 'string' ? input.fetch : input.fetch![0]\n  const options = typeof input.fetch === 'string' ? {} : input.fetch![1]\n  const start = Date.now()\n\n  // Get external source configuration\n  const isExternalUrl = !url.startsWith('/')\n\n  // Use external source timeout if it's an external URL, otherwise use original timeout\n  const timeout = isExternalUrl ? 10000 : (options.timeout || 5000)\n\n  const timeoutController = new AbortController()\n  const abortRequestTimeout = setTimeout(() => timeoutController.abort(), timeout)\n\n  try {\n    let isMaybeErrorResponse = false\n    const isXmlRequest = parseURL(url).pathname.endsWith('.xml')\n\n    // Merge external source headers with request headers\n    const mergedHeaders = defu(\n      options?.headers,\n      {\n        Accept: isXmlRequest ? 'text/xml' : 'application/json',\n      },\n      (event && !isExternalUrl) ? { host: getRequestHost(event, { xForwardedHost: true }) } : {},\n    )\n\n    const fetchOptions = {\n      ...options,\n      responseType: isXmlRequest ? 'text' : 'json',\n      signal: timeoutController.signal,\n      headers: mergedHeaders,\n      // Use ofetch's built-in retry for external sources\n      ...(isExternalUrl && {\n        retry: 2,\n        retryDelay: 200,\n      }),\n      // @ts-expect-error untyped\n      onResponse({ response }) {\n        if (typeof response._data === 'string' && response._data.startsWith('<!DOCTYPE html>'))\n          isMaybeErrorResponse = true\n      },\n    }\n\n    const res = await tryFetchWithFallback(url, fetchOptions, event)\n\n    const timeTakenMs = Date.now() - start\n    if (isMaybeErrorResponse) {\n      return {\n        ...input,\n        context,\n        urls: [],\n        timeTakenMs,\n        error: 'Received HTML response instead of JSON',\n      }\n    }\n    let urls = []\n    if (typeof res === 'object') {\n      urls = res.urls || res\n    }\n    else if (typeof res === 'string' && parseURL(url).pathname.endsWith('.xml')) {\n      const result = await parseSitemapXml(res)\n      urls = result.urls\n    }\n    return {\n      ...input,\n      context,\n      timeTakenMs,\n      urls: urls as SitemapUrlInput[],\n    }\n  }\n  catch (_err) {\n    const error = _err as FetchError\n\n    // Enhanced error logging for external sources\n    if (isExternalUrl) {\n      const errorInfo = {\n        url,\n        timeout,\n        error: error.message,\n        statusCode: error.response?.status,\n        statusText: error.response?.statusText,\n        method: options?.method || 'GET',\n      }\n\n      logger.error('Failed to fetch external source.', errorInfo)\n    }\n    else {\n      logger.error('Failed to fetch source.', { url, error: error.message })\n    }\n\n    return {\n      ...input,\n      context,\n      urls: [],\n      error: error.message,\n      _isFailure: true, // Mark as failure to prevent caching\n    }\n  }\n  finally {\n    if (abortRequestTimeout) {\n      clearTimeout(abortRequestTimeout)\n    }\n  }\n}\n\nexport async function globalSitemapSources() {\n  if (import.meta.prerender) {\n    const { readSourcesFromFilesystem } = await import('#sitemap-virtual/read-sources.mjs')\n    const sources = await readSourcesFromFilesystem('global-sources.json')\n    if (sources) {\n      // Spread to create a copy since the cached module returns a mutable reference\n      return [...sources]\n    }\n  }\n  const m = await import('#sitemap-virtual/global-sources.mjs')\n  // Spread to create a copy since the cached module returns a mutable reference\n  return [...m.sources]\n}\n\nexport async function childSitemapSources(definition: ModuleRuntimeConfig['sitemaps'][string]) {\n  if (!definition?._hasSourceChunk)\n    return []\n\n  if (import.meta.prerender) {\n    const { readSourcesFromFilesystem } = await import('#sitemap-virtual/read-sources.mjs')\n    const allSources = await readSourcesFromFilesystem('child-sources.json')\n    if (allSources) {\n      // Spread to create a copy since the cached module returns a mutable reference\n      return [...(allSources[definition.sitemapName] || [])]\n    }\n  }\n\n  const m = await import('#sitemap-virtual/child-sources.mjs')\n  // Spread to create a copy since the cached module returns a mutable reference\n  return [...(m.sources[definition.sitemapName] || [])]\n}\n\nexport async function resolveSitemapSources(sources: SitemapSourceInput[], event?: H3Event) {\n  return (await Promise.all(\n    sources.map((source) => {\n      const normalized = normalizeSourceInput(source)\n      if ('urls' in normalized) {\n        return <SitemapSourceResolved> {\n          timeTakenMs: 0,\n          ...normalized,\n          urls: normalized.urls,\n        }\n      }\n      if (normalized.fetch)\n        return fetchDataSource(normalized, event)\n\n      return <SitemapSourceResolved> {\n        ...normalized,\n        error: 'Invalid source',\n      }\n    }),\n  )).flat()\n}\n"
  },
  {
    "path": "src/runtime/server/sitemap/utils/chunk.ts",
    "content": "import type { ModuleRuntimeConfig, SitemapDefinition } from '../../../types'\n\nexport interface ChunkInfo {\n  isChunked: boolean\n  baseSitemapName: string\n  chunkIndex?: number\n  chunkSize: number\n}\n\nexport function parseChunkInfo(\n  sitemapName: string,\n  sitemaps: ModuleRuntimeConfig['sitemaps'],\n  defaultChunkSize?: number | false,\n): ChunkInfo {\n  defaultChunkSize = defaultChunkSize || 1000\n  // Check if this is an auto-chunked sitemap (numeric name)\n  if (typeof sitemaps.chunks !== 'undefined' && !Number.isNaN(Number(sitemapName))) {\n    return {\n      isChunked: true,\n      baseSitemapName: 'sitemap',\n      chunkIndex: Number(sitemapName),\n      chunkSize: defaultChunkSize,\n    }\n  }\n\n  // Check if this is a chunked named sitemap (format: name-number)\n  if (sitemapName.includes('-')) {\n    const parts = sitemapName.split('-')\n    const lastPart = parts.pop()\n\n    if (!Number.isNaN(Number(lastPart))) {\n      const baseSitemapName = parts.join('-')\n      const baseSitemap = sitemaps[baseSitemapName]\n\n      if (baseSitemap && (baseSitemap.chunks || baseSitemap._isChunking)) {\n        const chunkSize = typeof baseSitemap.chunks === 'number'\n          ? baseSitemap.chunks\n          : (baseSitemap.chunkSize || defaultChunkSize)\n\n        return {\n          isChunked: true,\n          baseSitemapName,\n          chunkIndex: Number(lastPart),\n          chunkSize,\n        }\n      }\n    }\n  }\n\n  // Not a chunked sitemap\n  return {\n    isChunked: false,\n    baseSitemapName: sitemapName,\n    chunkIndex: undefined,\n    chunkSize: defaultChunkSize,\n  }\n}\n\nexport function getSitemapConfig(\n  sitemapName: string,\n  sitemaps: ModuleRuntimeConfig['sitemaps'],\n  defaultChunkSize: number = 1000,\n): SitemapDefinition {\n  const chunkInfo = parseChunkInfo(sitemapName, sitemaps, defaultChunkSize)\n\n  if (chunkInfo.isChunked) {\n    // For auto-chunked sitemaps\n    if (chunkInfo.baseSitemapName === 'sitemap' && typeof sitemaps.chunks !== 'undefined') {\n      return {\n        ...sitemaps.chunks,\n        sitemapName,\n        _isChunking: true,\n        _chunkSize: chunkInfo.chunkSize,\n      }\n    }\n\n    // For named chunked sitemaps\n    const baseSitemap = sitemaps[chunkInfo.baseSitemapName]\n    if (baseSitemap) {\n      return {\n        ...baseSitemap,\n        sitemapName, // Use the full name with chunk index\n        _isChunking: true,\n        _chunkSize: chunkInfo.chunkSize,\n      }\n    }\n  }\n\n  // Regular sitemap\n  return sitemaps[sitemapName]!\n}\n\nexport function sliceUrlsForChunk<T>(\n  urls: T[],\n  sitemapName: string,\n  sitemaps: ModuleRuntimeConfig['sitemaps'],\n  defaultChunkSize: number = 1000,\n): T[] {\n  const chunkInfo = parseChunkInfo(sitemapName, sitemaps, defaultChunkSize)\n\n  if (chunkInfo.isChunked && chunkInfo.chunkIndex !== undefined) {\n    const startIndex = chunkInfo.chunkIndex * chunkInfo.chunkSize\n    const endIndex = (chunkInfo.chunkIndex + 1) * chunkInfo.chunkSize\n    return urls.slice(startIndex, endIndex)\n  }\n\n  return urls\n}\n"
  },
  {
    "path": "src/runtime/server/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../.nuxt/tsconfig.server.json\"\n}\n"
  },
  {
    "path": "src/runtime/server/utils.ts",
    "content": "import type { H3Event } from 'h3'\nimport type { ModuleRuntimeConfig } from '../types'\nimport { useRuntimeConfig } from 'nitropack/runtime'\n// @ts-expect-error virtual module\nimport staticConfig from '#sitemap-virtual/static-config.mjs'\nimport { normalizeRuntimeFilters } from '../utils-pure'\n\nexport * from '../utils-pure'\n\n// XML escape function for content inserted into XML/XSL\nexport function xmlEscape(str: string | number | boolean | Date): string {\n  return String(str)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&apos;')\n}\n\nexport function useSitemapRuntimeConfig(e?: H3Event): ModuleRuntimeConfig {\n  // Static fields live in a virtual module; only env-overridable fields go through runtimeConfig.\n  // we still need to clone so callers can mutate without affecting the shared module-scope copy\n  const clone = JSON.parse(JSON.stringify(staticConfig)) as ModuleRuntimeConfig\n  for (const k in clone.sitemaps) {\n    const sitemap = clone.sitemaps[k]!\n    sitemap.include = normalizeRuntimeFilters(sitemap.include)\n    sitemap.exclude = normalizeRuntimeFilters(sitemap.exclude)\n    clone.sitemaps[k] = sitemap\n  }\n  Object.assign(clone, useRuntimeConfig(e).sitemap)\n  return Object.freeze(clone)\n}\n"
  },
  {
    "path": "src/runtime/types.ts",
    "content": "import type { NuxtI18nOptions } from '@nuxtjs/i18n'\nimport type { H3Event } from 'h3'\nimport type { FetchOptions } from 'ofetch'\nimport type { ParsedURL } from 'ufo'\n\n// we need to have the module options within the runtime entry\n// as we don't want to depend on the module entry as it can cause\n// weird nitro issues\nexport interface ModuleOptions extends SitemapDefinition {\n  /**\n   * Whether the sitemap.xml should be generated.\n   *\n   * @default true\n   */\n  enabled: boolean\n  /**\n   * Enables debug logs and a debug endpoint.\n   *\n   * @default false\n   */\n  debug: boolean\n  /**\n   * Minify the sitemap xml\n   *\n   * @default false\n   */\n  minify: boolean\n  /**\n   * Should lastmod be automatically added to the sitemap.\n   *\n   * Warning: This may not be following best practices for sitemaps.\n   *\n   * @see https://nuxtseo.com/sitemap/guides/best-practices.\n   * @default false\n   */\n  autoLastmod: boolean\n  /**\n   * Sources to exclude from the sitemap.\n   */\n  excludeAppSources: true | (AppSourceContext[])\n  /**\n   * Multiple sitemap support for large sites.\n   *\n   * @default false\n   */\n  sitemaps?: boolean | MultiSitemapsInput\n  /**\n   * The path prefix for the sitemaps.\n   *\n   * @default /__sitemap__/\n   */\n  sitemapsPathPrefix: string | false\n  /**\n   * Sitemaps to append to the sitemap index.\n   *\n   * This will only do anything when using multiple sitemaps.\n   */\n  appendSitemaps?: (string | SitemapIndexEntry)[]\n  /**\n   * Path to the xsl that styles sitemap.xml.\n   *\n   * Set to `false` to disable styling.\n   *\n   * @default /__sitemap__/style.xsl\n   */\n  xsl: string | false\n  /**\n   * Toggle the tips displayed in the xsl.\n   *\n   * @default true\n   */\n  xslTips: boolean\n  /**\n   * Customised the columns displayed in the xsl.\n   *\n   * @default [{ label: 'URL', width: '50%' }, { label: 'Images', width: '25%', select: 'count(image:image)' }, { label: 'Last Updated', width: '25%', select: 'concat(substring(sitemap:lastmod,0,11),concat(\\' \\', substring(sitemap:lastmod,12,5)),concat(\\' \\', substring(sitemap:lastmod,20,6)))' }]\n   */\n  xslColumns?: { label: string, width: `${string}%`, select?: string }[]\n  /**\n   * When prerendering, should images be automatically be discovered and added to the sitemap.\n   *\n   * @default true\n   */\n  discoverImages: boolean\n  /**\n   * When prerendering, should videos be automatically be discovered and added to the sitemap.\n   *\n   * @default true\n   */\n  discoverVideos: boolean\n  /**\n   * When chunking the sitemaps into multiple files, how many entries should each file contain.\n   *\n   * Set to `false` to disabling chunking completely.\n   *\n   * @default 1000\n   */\n  defaultSitemapsChunkSize: number | false\n  /**\n   * Modify the cache behavior.\n   *\n   * Passing a boolean will enable or disable the runtime cache with the default options.\n   *\n   * Providing a record will allow you to configure the runtime cache fully.\n   *\n   * @default true\n   * @see https://nitro.unjs.io/guide/storage#mountpoints\n   * @example { driver: 'redis', host: 'localhost', port: 6379, password: 'password' }\n   */\n  runtimeCacheStorage: boolean | (Record<string, any> & {\n    driver: string\n  })\n  /**\n   * Automatically add alternative links to the sitemap based on a prefix list.\n   * Is used by @nuxtjs/i18n to automatically add alternative links to the sitemap.\n   */\n  autoI18n?: boolean | AutoI18nConfig\n  /**\n   * Enable when your nuxt/content files match your pages. This will automatically add sitemap content to the sitemap.\n   *\n   * This is similar behavior to using `nuxt/content` with `documentDriven: true`.\n   */\n  strictNuxtContentPaths: boolean\n  /**\n   * Should the sitemap.xml display credits for the module.\n   *\n   * @default true\n   */\n  credits: boolean\n  /**\n   * How long, in seconds, should the sitemap be cached for.\n   *\n   * @default 600\n   */\n  cacheMaxAgeSeconds: number | false\n  /**\n   * Should the entries be sorted by loc.\n   *\n   * @default true\n   */\n  sortEntries: boolean\n  /**\n   * Warm up the sitemap route(s) cache when Nitro starts.\n   *\n   * May be implemented by default in a future minor version.\n   *\n   * @experimental Will be enabled by default in v5 (if stable)\n   */\n  experimentalWarmUp?: boolean\n  /**\n   * Send the Sitemap as a compressed stream supporting gzip, brolti, etc.\n   *\n   * @experimental Will be enabled by default in v5 (if stable)\n   */\n  experimentalCompression?: boolean\n  /**\n   * When enabled, sitemap generation only runs during prerendering.\n   * The sitemap building code is tree-shaken from the runtime bundle.\n   *\n   * Requires sitemaps to be prerendered (e.g., `nuxt generate` or `nitro.prerender.routes` includes sitemap).\n   *\n   * @default false\n   */\n  zeroRuntime?: boolean\n}\n\nexport interface IndexSitemapRemotes {\n  index?: (string | SitemapIndexEntry)[]\n}\n\nexport interface MultiSitemapEntry {\n  [key: string]: Partial<SitemapDefinition>\n}\n\nexport type MultiSitemapsInput = Partial<MultiSitemapEntry> & Partial<IndexSitemapRemotes>\n\nexport type MaybeFunction<T> = T | (() => T)\nexport type MaybePromise<T> = T | Promise<T>\n\nexport type SitemapUrlInput = SitemapUrl | string\n\nexport interface SitemapSourceBase {\n  context: {\n    name: string\n    description?: string\n    tips?: string[]\n  }\n  fetch?: string | [string, FetchOptions]\n  urls?: SitemapUrlInput[]\n  sourceType?: 'app' | 'user'\n}\nexport interface SitemapSourceResolved extends Omit<SitemapSourceBase, 'urls'> {\n  urls: SitemapUrlInput[]\n  error?: any\n  timeTakenMs?: number\n  _isFailure?: boolean\n  _urlWarnings?: { loc: string, message: string }[]\n}\n\nexport type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rules' | '@nuxtjs/i18n:pages' | 'nuxt-i18n-micro:pages' | '@nuxt/content@v2:urls' | '@nuxt/content@v3:urls'\n\nexport type SitemapSourceInput = string | [string, FetchOptions] | SitemapSourceBase | SitemapSourceResolved\n\n// copied from @nuxtjs/i18n, types do not appear to be working\ninterface LocaleObject extends Record<string, any> {\n  code: string\n  name?: string\n  dir?: 'ltr' | 'rtl' | 'auto'\n  domain?: string\n  domains?: string[]\n  defaultForDomains?: string[]\n  file?: string | {\n    path: string\n    cache?: boolean\n  }\n  files?: (string | {\n    path: string\n    cache?: boolean\n  })[]\n  isCatchallLocale?: boolean\n  /**\n   * @deprecated in v9, use `language` instead\n   */\n  iso?: string\n  language?: string\n}\n\nexport interface AutoI18nConfig {\n  differentDomains?: boolean\n  locales: (LocaleObject & { _sitemap: string, _hreflang: string })[]\n  defaultLocale: string\n  strategy: 'prefix' | 'prefix_except_default' | 'prefix_and_default' | 'no_prefix'\n  pages?: Record<string, Record<string, string | false>>\n}\n\nexport interface ModuleRuntimeConfig extends Pick<ModuleOptions, 'sitemapsPathPrefix' | 'cacheMaxAgeSeconds' | 'sitemapName' | 'excludeAppSources' | 'sortEntries' | 'defaultSitemapsChunkSize' | 'xslColumns' | 'xslTips' | 'debug' | 'discoverImages' | 'discoverVideos' | 'autoLastmod' | 'xsl' | 'credits' | 'minify'> {\n  version: string\n  isNuxtContentDocumentDriven: boolean\n  sitemaps: { index?: Pick<SitemapDefinition, 'sitemapName' | '_route'> & { sitemaps: SitemapIndexEntry[] } } & Record<string, Omit<SitemapDefinition, 'urls'> & { _hasSourceChunk?: boolean }>\n  autoI18n?: AutoI18nConfig\n  hasDisabledAutoI18n?: boolean\n  isMultiSitemap: boolean\n  isI18nMapped: boolean\n}\n\nexport interface SitemapIndexEntry {\n  sitemap: string\n  lastmod?: string\n  /**\n   * @internal\n   */\n  _sitemapName?: string\n}\n\nexport type FilterInput = (string | RegExp | {\n  regex: string\n})\nexport type ResolvedSitemapUrl = Omit<SitemapUrl, 'url'> & Required<Pick<SitemapUrl, 'loc'>> & {\n  /**\n   * @internal\n   */\n  _key: string\n  /**\n   * @internal\n   */\n  _path: ParsedURL | null\n  /**\n   * @internal\n   */\n  _relativeLoc: string\n  /**\n   * @internal\n   */\n  _abs: boolean\n  /**\n   * @internal\n   */\n  _warnings?: string[]\n}\n\nexport interface SitemapDefinition {\n  /**\n   * A collection include patterns for filtering which URLs end up in the sitemap.\n   */\n  include?: FilterInput[]\n  /**\n   * A collection exclude patterns for filtering which URLs end up in the sitemap.\n   */\n  exclude?: FilterInput[]\n  /**\n   * Should the sitemap be generated using global sources.\n   *\n   * This is enabled by default when using a single sitemap. Otherwise, it will be opt-in.\n   */\n  includeAppSources?: boolean\n  /**\n   * The root sitemap name.\n   * Only works when multiple sitemaps option `sitemaps` isn't used.\n   *\n   * @default `sitemap.xml`\n   */\n  sitemapName: string\n  /**\n   * A resolvable collection of URLs to include in the sitemap.\n   *\n   * Will be resolved when the sitemap is generated.\n   */\n  urls?: MaybeFunction<MaybePromise<SitemapUrlInput[]>>\n  /**\n   * Default options for all URLs in the sitemap.\n   */\n  defaults?: Omit<SitemapUrl, 'loc'>\n  /**\n   * Additional sources of URLs to include in the sitemap.\n   */\n  sources?: SitemapSourceInput[]\n  /**\n   * Whether to enable chunking for this sitemap.\n   *\n   * - `true`: Enable with default chunk size from `defaultSitemapsChunkSize`\n   * - `number`: Enable with specific chunk size (must be > 0)\n   * - `false` or `undefined`: Disable chunking\n   *\n   * Note: Chunking only applies to sitemaps with sources. URLs provided directly\n   * are not chunked.\n   *\n   * @default false\n   * @example true\n   * @example 5000\n   */\n  chunks?: boolean | number\n  /**\n   * The maximum number of URLs per chunk when chunking is enabled.\n   * Takes precedence over the `chunks` property when both are specified.\n   * Also overrides the global `defaultSitemapsChunkSize`.\n   *\n   * Must be a positive integer.\n   *\n   * @default 1000\n   * @example 500\n   * @example 10000\n   */\n  chunkSize?: number\n  /**\n   * Pre-declare the number of chunks this sitemap will produce. When set, the sitemap index\n   * renders this many chunk entries without fetching the source data — useful at very large\n   * scale where the cold-start fetch is the bottleneck. Per-chunk renders still fetch on\n   * demand and slice. If the actual data produces fewer URLs than declared, tail chunks render\n   * empty; if more, the extras are unreachable. Update this when your data set grows.\n   *\n   * @example 100\n   */\n  chunkCount?: number\n  /**\n   * @internal\n   */\n  _route?: string\n  /**\n   * @internal\n   */\n  _isChunking?: boolean\n  /**\n   * @internal\n   */\n  _chunkSize?: number\n  /**\n   * @internal\n   */\n  _chunkCount?: number\n}\n\ninterface NitroBaseHook {\n  event: H3Event\n}\n\nexport interface SitemapIndexRenderCtx extends NitroBaseHook {\n  sitemaps: SitemapIndexEntry[]\n}\n\nexport interface SitemapRenderCtx extends NitroBaseHook {\n  sitemapName: string\n  urls: ResolvedSitemapUrl[]\n}\n\nexport interface SitemapInputCtx extends NitroBaseHook {\n  sitemapName: string\n  urls: SitemapUrlInput[]\n}\n\nexport interface SitemapOutputHookCtx extends NitroBaseHook {\n  sitemapName: string\n  sitemap: string\n}\n\nexport interface SitemapSourcesHookCtx extends NitroBaseHook {\n  sitemapName: string\n  sources: SitemapSourceInput[]\n}\n\nexport type Changefreq\n  = | 'always'\n    | 'hourly'\n    | 'daily'\n    | 'weekly'\n    | 'monthly'\n    | 'yearly'\n    | 'never'\n\nexport interface SitemapUrl {\n  loc: string\n  /**\n   * Alias for `loc`. Will be normalized to `loc`.\n   */\n  url?: string\n  lastmod?: string | Date\n  changefreq?: Changefreq\n  priority?: 0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1\n  alternatives?: Array<AlternativeEntry>\n  news?: GoogleNewsEntry\n  images?: Array<ImageEntry>\n  videos?: Array<VideoEntry>\n  _i18nTransform?: boolean\n  _sitemap?: string | false\n  /**\n   * Mark the URL as already encoded.\n   *\n   * When true, the loc will not be automatically encoded, preventing double-encoding\n   * when you've already applied encodeURIComponent() to path segments.\n   *\n   * @example\n   * ```ts\n   * {\n   *   loc: `/${encodeURIComponent('$pecial-char')}`,\n   *   _encoded: true\n   * }\n   * ```\n   */\n  _encoded?: boolean\n}\n\nexport type SitemapItemDefaults = Omit<SitemapUrl, 'loc'>\n\nexport type SitemapStrict = Required<SitemapUrl>\n\nexport interface AlternativeEntry {\n  hreflang: string\n  href: string | URL\n}\n\nexport interface GoogleNewsEntry {\n  /**\n   * The title of the news article.\n   * @example \"Companies A, B in Merger Talks\"\n   */\n  title: string\n  /**\n   * The article publication date in W3C format. Specify the original date and time when the article was first\n   * published on your site. Don't specify the time when you added the article to your sitemap.\n   * @example \"2008-12-23\"\n   */\n  publication_date: Date | string\n  publication: {\n    /**\n     * The <news:name> tag is the name of the news publication.\n     * It must exactly match the name as it appears on your articles on news.google.com, omitting anything in parentheses.\n     * @example \"The Example Times\"\n     */\n    name: string\n    /**\n     * The <news:language> tag is the language of your publication. Use an ISO 639 language code (two or three letters).\n     * @example en\n     */\n    language: string\n  }\n}\n\nexport interface ImageEntry {\n  loc: string | URL\n  caption?: string\n  geo_location?: string\n  title?: string\n  license?: string | URL\n}\n\nexport interface VideoEntry {\n  title: string\n  thumbnail_loc: string | URL\n  description: string\n  content_loc?: string | URL\n  player_loc?: string | URL\n  duration?: number\n  expiration_date?: Date | string\n  rating?: number\n  view_count?: number\n  publication_date?: Date | string\n  family_friendly?: 'yes' | 'no' | boolean\n  restriction?: Restriction\n  platform?: Platform\n  price?: ({\n    price?: number | string\n    currency?: string\n    type?: 'rent' | 'purchase' | 'package' | 'subscription'\n  })[]\n  requires_subscription?: 'yes' | 'no' | boolean\n  uploader?: {\n    uploader: string\n    info?: string | URL\n  }\n  live?: 'yes' | 'no' | boolean\n  tag?: string | string[]\n  category?: string\n  gallery_loc?: string | URL\n}\n\nexport interface Restriction {\n  relationship: 'allow' | 'deny'\n  restriction: string\n}\nexport interface Platform {\n  relationship: 'allow' | 'deny'\n  platform: string\n}\n\nexport interface NitroUrlResolvers {\n  event: H3Event\n  canonicalUrlResolver: (path: string) => string\n  relativeBaseUrlResolver: (path: string) => string\n  fixSlashes: (path: string) => string\n}\n\nexport type I18nIntegrationOptions = NuxtI18nOptions\n"
  },
  {
    "path": "src/runtime/utils-pure.ts",
    "content": "import type { FilterInput } from './types'\nimport { createConsola } from 'consola'\nimport { createDefu } from 'defu'\nimport { createFilter } from 'nuxtseo-shared/utils'\nimport { parseURL, withLeadingSlash, withoutBase } from 'ufo'\n\nexport { createFilter, type CreateFilterOptions } from 'nuxtseo-shared/utils'\n\nexport const logger = createConsola({\n  defaults: {\n    tag: '@nuxt/sitemap',\n  },\n})\n\nconst merger = createDefu((obj, key, value) => {\n  // merge arrays using a set\n  if (Array.isArray(obj[key]) && Array.isArray(value))\n    // @ts-expect-error untyped\n    obj[key] = Array.from(new Set([...obj[key], ...value]))\n  return obj[key]\n})\n\nexport function mergeOnKey<T, K extends keyof T>(arr: T[], key: K): T[] {\n  const seen = new Map<string, number>()\n\n  // Pre-allocate result array to avoid resizing\n  let resultLength = 0\n  const result: T[] = Array.from({ length: arr.length })\n\n  for (const item of arr) {\n    const k = item[key] as string\n    if (seen.has(k)) {\n      const existingIndex = seen.get(k)!\n      // @ts-expect-error untyped\n      result[existingIndex] = merger(item, result[existingIndex])\n    }\n    else {\n      seen.set(k, resultLength)\n      result[resultLength++] = item\n    }\n  }\n\n  // Truncate in-place instead of creating a copy via slice\n  result.length = resultLength\n  return result\n}\n\nexport function splitForLocales(path: string, locales: string[]): [string | null, string] {\n  // we only want to use the first path segment otherwise we can end up turning \"/ending\" into \"/en/ding\"\n  const prefix = withLeadingSlash(path).split('/')[1]\n  // make sure prefix is a valid locale\n  if (prefix && locales.includes(prefix))\n    return [prefix, path.replace(`/${prefix}`, '')]\n  return [null, path]\n}\n\nconst StringifiedRegExpPattern = /\\/(.*?)\\/([gimsuy]*)$/\n\n/**\n * Transform a literal notation string regex to RegExp\n */\nexport function normalizeRuntimeFilters(input?: FilterInput[]): (RegExp | string)[] {\n  return (input || []).map((rule) => {\n    if (rule instanceof RegExp || typeof rule === 'string')\n      return rule\n    // regex is already validated\n    const match = rule.regex.match(StringifiedRegExpPattern)\n    if (match)\n      return new RegExp(match[1]!, match[2])\n    return false\n  }).filter(Boolean) as (RegExp | string)[]\n}\n\nexport function createPathFilter(options: { include?: (FilterInput | string | RegExp)[], exclude?: (FilterInput | string | RegExp)[] } = {}, baseURL?: string) {\n  const urlFilter = createFilter({\n    include: normalizeRuntimeFilters(options.include),\n    exclude: normalizeRuntimeFilters(options.exclude),\n  })\n  const hasBase = baseURL && baseURL !== '/'\n  return (loc: string) => {\n    let path = loc\n    try {\n      // e.loc is absolute here\n      path = parseURL(loc).pathname\n    }\n    catch {\n      // invalid URL\n      return false\n    }\n    if (hasBase)\n      path = withoutBase(path, baseURL)\n    return urlFilter(path)\n  }\n}\n\nexport interface PageMatch {\n  mappings: Record<string, string | false>\n  paramSegments: string[]\n}\n\nexport function findPageMapping(pathWithoutPrefix: string, pages: Record<string, Record<string, string | false>>): PageMatch | null {\n  const stripped = pathWithoutPrefix[0] === '/' ? pathWithoutPrefix.slice(1) : pathWithoutPrefix\n  const pageKey = stripped.endsWith('/index') ? stripped.slice(0, -6) || 'index' : stripped || 'index'\n\n  // exact match\n  if (pages[pageKey])\n    return { mappings: pages[pageKey], paramSegments: [] }\n\n  // prefix matching for dynamic routes (e.g., 'posts/2' matches 'posts' key)\n  // sort by length desc to match most specific first\n  const sortedKeys = Object.keys(pages).sort((a, b) => b.length - a.length)\n  for (const key of sortedKeys) {\n    if (pageKey.startsWith(`${key}/`)) {\n      const paramPath = pageKey.slice(key.length + 1)\n      return { mappings: pages[key]!, paramSegments: paramPath.split('/') }\n    }\n  }\n\n  return null\n}\n\nexport function applyDynamicParams(customPath: string, paramSegments: string[]): string {\n  if (!paramSegments.length)\n    return customPath\n  let i = 0\n  return customPath.replace(/\\[[^\\]]+\\]/g, () => paramSegments[i++] || '')\n}\n"
  },
  {
    "path": "src/templates.ts",
    "content": "import { addTemplate, addTypeTemplate, hasNuxtModule } from '@nuxt/kit'\n\nexport function registerTypeTemplates() {\n  const hasRobotsModule = hasNuxtModule('@nuxtjs/robots') || hasNuxtModule('nuxt-simple-robots')\n  // Type augmentations for existing modules\n  addTypeTemplate({\n    filename: 'types/nuxt-sitemap-augments.d.ts',\n    getContents: () => {\n      const robotsType = hasRobotsModule ? '' : '    robots?: boolean\\n'\n      return `// Generated by @nuxtjs/sitemap\n/// <reference path=\"./nuxt-sitemap-virtual.d.ts\" />\nimport type { SitemapUrl, SitemapItemDefaults, SitemapIndexRenderCtx, SitemapInputCtx, SitemapRenderCtx, SitemapOutputHookCtx, SitemapSourcesHookCtx } from '@nuxtjs/sitemap'\n\ndeclare module 'nitropack' {\n  interface PrerenderRoute {\n    _sitemap?: SitemapUrl\n  }\n  interface NitroRouteRules {\n${robotsType}    sitemap?: SitemapItemDefaults | false\n  }\n  interface NitroRouteConfig {\n${robotsType}    sitemap?: SitemapItemDefaults | false\n  }\n  interface NitroRuntimeHooks {\n    'sitemap:index-resolved': (ctx: SitemapIndexRenderCtx) => void | Promise<void>\n    'sitemap:input': (ctx: SitemapInputCtx) => void | Promise<void>\n    'sitemap:resolved': (ctx: SitemapRenderCtx) => void | Promise<void>\n    'sitemap:output': (ctx: SitemapOutputHookCtx) => void | Promise<void>\n    'sitemap:sources': (ctx: SitemapSourcesHookCtx) => void | Promise<void>\n  }\n}\n\ndeclare module 'nitropack/types' {\n  interface PrerenderRoute {\n    _sitemap?: SitemapUrl\n  }\n  interface NitroRouteRules {\n${robotsType}    sitemap?: SitemapItemDefaults | false\n  }\n  interface NitroRouteConfig {\n${robotsType}    sitemap?: SitemapItemDefaults | false\n  }\n  interface NitroRuntimeHooks {\n    'sitemap:index-resolved': (ctx: SitemapIndexRenderCtx) => void | Promise<void>\n    'sitemap:input': (ctx: SitemapInputCtx) => void | Promise<void>\n    'sitemap:resolved': (ctx: SitemapRenderCtx) => void | Promise<void>\n    'sitemap:output': (ctx: SitemapOutputHookCtx) => void | Promise<void>\n    'sitemap:sources': (ctx: SitemapSourcesHookCtx) => void | Promise<void>\n  }\n}\n\ndeclare module 'vue-router' {\n  interface RouteMeta {\n    sitemap?: SitemapItemDefaults | false\n  }\n}\n\ndeclare module '#app' {\n  interface PageMeta {\n    sitemap?: SitemapItemDefaults | false\n  }\n}\n\ndeclare module 'nuxt/app' {\n  interface PageMeta {\n    sitemap?: SitemapItemDefaults | false\n  }\n}\n\nexport {}\n`\n    },\n  })\n\n  // Type definitions for virtual modules\n  addTemplate({\n    filename: 'types/nuxt-sitemap-virtual.d.ts',\n    getContents: () => `declare module '#sitemap-virtual/read-sources.mjs' {\n  export function readSourcesFromFilesystem(filename: string): Promise<any | null>\n}\n\ndeclare module '#sitemap-virtual/global-sources.mjs' {\n  import type { SitemapSourceBase, SitemapSourceResolved } from '#sitemap/types'\n\n  export const sources: (SitemapSourceBase | SitemapSourceResolved)[]\n}\n\ndeclare module '#sitemap-virtual/child-sources.mjs' {\n  import type { SitemapSourceBase, SitemapSourceResolved } from '#sitemap/types'\n\n  export const sources: Record<string, (SitemapSourceBase | SitemapSourceResolved)[]>\n}\n\ndeclare module '#sitemap/content-filters' {\n  export const filters: Map<string, (entry: any) => boolean>\n}\n\ndeclare module '#sitemap/content-on-url' {\n  export const onUrlFns: Map<string, (url: Record<string, unknown>, entry: any, collection: string) => void>\n}\n`,\n  })\n}\n"
  },
  {
    "path": "src/utils/index.ts",
    "content": "export type * from '../runtime/types'\nexport { parseHtmlExtractSitemapMeta } from './parseHtmlExtractSitemapMeta'\nexport { isSitemapIndex, parseSitemapIndex } from './parseSitemapIndex'\nexport type { SitemapIndexEntry, SitemapIndexParseResult } from './parseSitemapIndex'\nexport { parseSitemapXml } from './parseSitemapXml'\nexport type { SitemapParseResult, SitemapWarning } from './parseSitemapXml'\n"
  },
  {
    "path": "src/utils/parseHtmlExtractSitemapMeta.ts",
    "content": "import type { ElementNode } from 'ultrahtml'\nimport type { ResolvedSitemapUrl, SitemapUrl, VideoEntry } from '../runtime/types'\nimport { parseURL } from 'ufo'\nimport { ELEMENT_NODE, parse, walkSync } from 'ultrahtml'\n\n// Validation helpers\nfunction isValidUrl(url: string): boolean {\n  if (!url || typeof url !== 'string')\n    return false\n  const trimmed = url.trim()\n  if (!trimmed)\n    return false\n\n  // Reject data URLs, blob URLs, and other non-http(s) protocols for sitemap content\n  if (trimmed.startsWith('data:') || trimmed.startsWith('blob:') || trimmed.startsWith('file:')) {\n    return false\n  }\n\n  try {\n    const parsed = parseURL(trimmed)\n    // Allow both absolute URLs (with protocol/host) and relative paths (with pathname)\n    return !!(parsed.protocol && parsed.host) || !!parsed.pathname\n  }\n  catch {\n    return false\n  }\n}\n\nfunction isValidString(value: unknown): value is string {\n  return typeof value === 'string' && value.trim().length > 0\n}\n\nfunction sanitizeString(value: unknown): string {\n  if (!isValidString(value))\n    return ''\n  // eslint-disable-next-line no-control-regex\n  return String(value).trim().replace(/[\\x00-\\x1F\\x7F-\\x9F]/g, '') // Remove control characters\n}\n\nfunction isValidDate(dateString: string): boolean {\n  if (!dateString)\n    return false\n  const date = new Date(dateString)\n  return !Number.isNaN(date.getTime()) && date.getFullYear() > 1900 && date.getFullYear() < 3000\n}\n\nexport function parseHtmlExtractSitemapMeta(html: string, options?: { images?: boolean, videos?: boolean, lastmod?: boolean, alternatives?: boolean, resolveUrl?: (s: string) => string }): Partial<SitemapUrl> | null {\n  options = options || { images: true, videos: true, lastmod: true, alternatives: true }\n  const payload: Partial<SitemapUrl> = {}\n  const resolveUrl = options?.resolveUrl || ((s: string) => s)\n\n  let doc: any\n  try {\n    doc = parse(html)\n  }\n  catch (error) {\n    console.warn('Failed to parse HTML:', error)\n    return payload\n  }\n\n  // Collect all needed data in a single traversal\n  let mainElement: ElementNode | null = null\n  const images = new Set<string>()\n  const videos: Array<{ videoObj: VideoEntry, element: ElementNode }> = []\n  const videoSources = new Map<ElementNode, string[]>()\n  let articleModifiedTime: string | undefined\n  const alternatives: ResolvedSitemapUrl['alternatives'] = []\n  let isBlocked = false\n\n  // First pass: find main element and collect document-level elements\n  walkSync(doc, (node) => {\n    if (node.type === ELEMENT_NODE) {\n      const element = node as ElementNode\n      const attrs = element.attributes || {}\n\n      // Find main element\n      if (element.name === 'main' && !mainElement) {\n        mainElement = element\n      }\n\n      // Check for blocking meta tags\n      if (element.name === 'meta') {\n        const name = sanitizeString(attrs.name).toLowerCase()\n        const content = sanitizeString(attrs.content).toLowerCase()\n        if (name === 'robots' && (content.includes('noindex') || content.includes('none'))) {\n          isBlocked = true\n        }\n      }\n\n      // Collect lastmod meta tags (document-level)\n      if (options?.lastmod && element.name === 'meta') {\n        const property = sanitizeString(attrs.property)\n        const content = sanitizeString(attrs.content)\n        if ((property === 'article:modified_time' || property === 'article:published_time') && content && isValidDate(content)) {\n          // prioritize modified_time\n          if (property === 'article:modified_time' || !articleModifiedTime) {\n            articleModifiedTime = content\n          }\n        }\n      }\n\n      // Collect alternative links (document-level)\n      if (options?.alternatives && element.name === 'link') {\n        const rel = sanitizeString(attrs.rel)\n        const href = sanitizeString(attrs.href)\n        const hreflang = sanitizeString(attrs.hreflang)\n        if (rel === 'alternate' && href && hreflang && isValidUrl(href)) {\n          // Validate hreflang format (language codes)\n          const hreflangPattern = /^[a-z]{2}(?:-[A-Z]{2})?$|^x-default$/\n          if (hreflangPattern.test(hreflang)) {\n            try {\n              const parsed = parseURL(href)\n              if (parsed.pathname) {\n                alternatives.push({\n                  hreflang,\n                  href: parsed.pathname,\n                })\n              }\n            }\n            catch {\n              // Skip invalid URLs\n            }\n          }\n        }\n      }\n    }\n  })\n\n  // Second pass: traverse search scope for content elements\n  const searchScope = mainElement || doc\n  walkSync(searchScope, (node) => {\n    if (node.type === ELEMENT_NODE) {\n      const element = node as ElementNode\n      const attrs = element.attributes || {}\n\n      // Collect images\n      if (options?.images && element.name === 'img') {\n        const src = sanitizeString(attrs.src)\n        if (src && isValidUrl(src)) {\n          const resolvedUrl = resolveUrl(src)\n          if (isValidUrl(resolvedUrl)) {\n            images.add(resolvedUrl)\n          }\n        }\n      }\n\n      // Collect videos\n      if (options?.videos && element.name === 'video') {\n        const content_loc = sanitizeString(attrs.src)\n        const thumbnail_loc = sanitizeString(attrs.poster)\n        const title = sanitizeString(attrs['data-title'])\n        const description = sanitizeString(attrs['data-description'])\n\n        // Skip videos with invalid required fields\n        if (!title || !description) {\n          return\n        }\n\n        const videoObj: VideoEntry = {\n          content_loc,\n          thumbnail_loc,\n          title,\n          description,\n        }\n\n        // Handle optional video attributes with validation\n        const player_loc = sanitizeString(attrs['data-player-loc'])\n        if (player_loc && isValidUrl(player_loc)) {\n          videoObj.player_loc = player_loc\n        }\n\n        const duration = sanitizeString(attrs['data-duration'])\n        if (duration) {\n          const parsedDuration = Number.parseInt(duration, 10)\n          if (!Number.isNaN(parsedDuration) && parsedDuration > 0 && parsedDuration <= 28800) { // Max 8 hours\n            videoObj.duration = parsedDuration\n          }\n        }\n\n        const expiration_date = sanitizeString(attrs['data-expiration-date'])\n        if (expiration_date && isValidDate(expiration_date)) {\n          videoObj.expiration_date = expiration_date\n        }\n\n        const rating = sanitizeString(attrs['data-rating'])\n        if (rating) {\n          const parsedRating = Number.parseFloat(rating)\n          if (!Number.isNaN(parsedRating) && parsedRating >= 0 && parsedRating <= 5) {\n            videoObj.rating = parsedRating\n          }\n        }\n\n        const view_count = sanitizeString(attrs['data-view-count'])\n        if (view_count) {\n          const parsedViewCount = Number.parseInt(view_count, 10)\n          if (!Number.isNaN(parsedViewCount) && parsedViewCount >= 0) {\n            videoObj.view_count = parsedViewCount\n          }\n        }\n\n        const publication_date = sanitizeString(attrs['data-publication-date'])\n        if (publication_date && isValidDate(publication_date)) {\n          videoObj.publication_date = publication_date\n        }\n\n        const family_friendly = sanitizeString(attrs['data-family-friendly'])\n        if (family_friendly && ['yes', 'no'].includes(family_friendly.toLowerCase())) {\n          videoObj.family_friendly = family_friendly.toLowerCase() as VideoEntry['family_friendly']\n        }\n\n        const requires_subscription = sanitizeString(attrs['data-requires-subscription'])\n        if (requires_subscription && ['yes', 'no'].includes(requires_subscription.toLowerCase())) {\n          videoObj.requires_subscription = requires_subscription.toLowerCase() as VideoEntry['requires_subscription']\n        }\n\n        const live = sanitizeString(attrs['data-live'])\n        if (live && ['yes', 'no'].includes(live.toLowerCase())) {\n          videoObj.live = live.toLowerCase() as VideoEntry['live']\n        }\n\n        const tag = sanitizeString(attrs['data-tag'])\n        if (tag && tag.length <= 256) { // Reasonable tag length limit\n          videoObj.tag = tag\n        }\n\n        // Store video element for later source processing\n        videos.push({ videoObj, element })\n      }\n\n      // Collect video sources\n      if (options?.videos && element.name === 'source' && element.parent && element.parent.name === 'video') {\n        const videoElement = element.parent as ElementNode\n        const src = sanitizeString(attrs.src)\n        if (src && isValidUrl(src)) {\n          if (!videoSources.has(videoElement)) {\n            videoSources.set(videoElement, [])\n          }\n          videoSources.get(videoElement)!.push(src)\n        }\n      }\n    }\n  })\n\n  // Process collected data\n  if (options?.images && images.size > 0) {\n    payload.images = Array.from(images, i => ({ loc: i }))\n  }\n\n  if (options?.videos) {\n    const processedVideos: VideoEntry[] = []\n\n    for (const { videoObj, element } of videos) {\n      const sources = videoSources.get(element) || []\n\n      if (sources.length > 0) {\n        // Video has source elements - create one video entry per source\n        for (const source of sources) {\n          const resolvedVideoObj = { ...videoObj }\n          if (resolvedVideoObj.thumbnail_loc) {\n            resolvedVideoObj.thumbnail_loc = resolveUrl(String(resolvedVideoObj.thumbnail_loc))\n          }\n          processedVideos.push({\n            ...resolvedVideoObj,\n            content_loc: resolveUrl(source),\n          })\n        }\n      }\n      else {\n        // Video has no source elements - use the video element directly\n        processedVideos.push(videoObj)\n      }\n    }\n\n    const validVideos = processedVideos.filter((v) => {\n      return (\n        isValidString(v.title)\n        && isValidString(v.description)\n        && isValidString(v.content_loc) && isValidUrl(v.content_loc)\n        && isValidString(v.thumbnail_loc) && isValidUrl(v.thumbnail_loc)\n        && v.title.length <= 2048 // Google's title limit\n        && v.description.length <= 2048 // Google's description limit\n      )\n    })\n    if (validVideos.length > 0) {\n      payload.videos = validVideos\n    }\n  }\n\n  if (options?.lastmod && articleModifiedTime) {\n    payload.lastmod = articleModifiedTime\n  }\n\n  if (options?.alternatives && alternatives.length > 0 && (alternatives.length > 1 || alternatives[0]?.hreflang !== 'x-default')) {\n    payload.alternatives = alternatives\n  }\n\n  // Return null if blocked from indexing\n  if (isBlocked) {\n    return null\n  }\n\n  return payload\n}\n"
  },
  {
    "path": "src/utils/parseSitemapIndex.ts",
    "content": "import type { SitemapWarning } from './parseSitemapXml'\n\nexport interface SitemapIndexEntry {\n  loc: string\n  lastmod?: string\n}\n\nexport interface SitemapIndexParseResult {\n  entries: SitemapIndexEntry[]\n  warnings: SitemapWarning[]\n}\n\ninterface ParsedSitemap {\n  loc?: string\n  lastmod?: string\n}\n\ninterface ParsedSitemapIndex {\n  sitemap?: ParsedSitemap | ParsedSitemap[]\n}\n\ninterface ParsedRoot {\n  sitemapindex?: ParsedSitemapIndex\n}\n\nfunction isValidUrl(value: string): boolean {\n  return URL.canParse(value)\n}\n\nexport async function parseSitemapIndex(xml: string): Promise<SitemapIndexParseResult> {\n  if (!xml)\n    throw new Error('Empty XML input provided')\n\n  const { XMLParser } = await import('fast-xml-parser')\n  const parser = new XMLParser({\n    isArray: (tagName: string) => tagName === 'sitemap',\n    removeNSPrefix: true,\n    trimValues: true,\n  })\n  const parsed = parser.parse(xml) as ParsedRoot\n\n  if (parsed?.sitemapindex === undefined)\n    throw new Error('XML does not contain a valid sitemapindex element')\n\n  if (!parsed.sitemapindex || !parsed.sitemapindex.sitemap)\n    return { entries: [], warnings: [] }\n\n  const sitemaps = Array.isArray(parsed.sitemapindex.sitemap)\n    ? parsed.sitemapindex.sitemap\n    : [parsed.sitemapindex.sitemap]\n\n  const warnings: SitemapWarning[] = []\n  const entries: SitemapIndexEntry[] = []\n\n  for (const s of sitemaps) {\n    if (typeof s.loc !== 'string' || !s.loc.trim().length) {\n      warnings.push({\n        type: 'validation',\n        message: 'Sitemap entry missing required loc element',\n      })\n      continue\n    }\n    const loc = s.loc.trim()\n    if (!isValidUrl(loc)) {\n      warnings.push({\n        type: 'validation',\n        message: 'Sitemap entry has invalid URL',\n        context: { url: loc },\n      })\n      continue\n    }\n    entries.push({\n      loc,\n      ...(s.lastmod && { lastmod: s.lastmod.trim() }),\n    })\n  }\n\n  return { entries, warnings }\n}\n\nexport function isSitemapIndex(xml: string): boolean {\n  return xml.includes('<sitemapindex') || xml.includes('sitemapindex>')\n}\n"
  },
  {
    "path": "src/utils/parseSitemapXml.ts",
    "content": "import type { AlternativeEntry, GoogleNewsEntry, ImageEntry, SitemapStrict, SitemapUrl, SitemapUrlInput, VideoEntry } from '../runtime/types'\n\ninterface ParsedUrl {\n  loc?: string\n  lastmod?: string\n  changefreq?: string\n  priority?: string | number\n  image?: ParsedImage | ParsedImage[]\n  video?: ParsedVideo | ParsedVideo[]\n  link?: ParsedLink | ParsedLink[]\n  news?: ParsedNews\n}\n\ninterface ParsedImage {\n  loc?: string\n}\n\ninterface ParsedVideo {\n  title?: string\n  thumbnail_loc?: string\n  description?: string\n  content_loc?: string\n  player_loc?: string\n  duration?: string | number\n  expiration_date?: string\n  rating?: string | number\n  view_count?: string | number\n  publication_date?: string\n  family_friendly?: string\n  requires_subscription?: string\n  live?: string\n  restriction?: {\n    'relationship'?: string\n    '#text'?: string\n  }\n  platform?: {\n    'relationship'?: string\n    '#text'?: string\n  }\n  price?: ParsedPrice | ParsedPrice[]\n  uploader?: {\n    'info'?: string\n    '#text'?: string\n  }\n  tag?: string | string[]\n}\n\ninterface ParsedPrice {\n  '#text'?: string\n  'currency'?: string\n  'type'?: string\n}\n\ninterface ParsedLink {\n  rel?: string\n  hreflang?: string\n  href?: string\n}\n\ninterface ParsedNews {\n  title?: string\n  publication_date?: string\n  publication?: {\n    name?: string\n    language?: string\n  }\n}\n\ninterface ParsedUrlset {\n  url?: ParsedUrl | ParsedUrl[]\n}\n\ninterface ParsedRoot {\n  urlset?: ParsedUrlset\n}\n\nexport interface SitemapWarning {\n  type: 'validation'\n  message: string\n  context?: {\n    url?: string\n    field?: string\n    value?: unknown\n  }\n}\n\nexport interface SitemapParseResult {\n  urls: SitemapUrlInput[]\n  warnings: SitemapWarning[]\n}\n\nfunction isValidString(value: unknown): value is string {\n  return typeof value === 'string' && value.trim().length > 0\n}\n\nfunction parseNumber(value: unknown): number | undefined {\n  if (typeof value === 'number')\n    return value\n  if (typeof value === 'string' && value.trim()) {\n    const num = Number.parseFloat(value.trim())\n    return Number.isNaN(num) ? undefined : num\n  }\n  return undefined\n}\n\nfunction parseInteger(value: unknown): number | undefined {\n  if (typeof value === 'number')\n    return Math.floor(value)\n  if (typeof value === 'string' && value.trim()) {\n    const num = Number.parseInt(value.trim(), 10)\n    return Number.isNaN(num) ? undefined : num\n  }\n  return undefined\n}\n\nfunction extractUrlFromParsedElement(\n  urlElement: ParsedUrl,\n  warnings: SitemapWarning[],\n): SitemapUrlInput | null {\n  if (!isValidString(urlElement.loc)) {\n    warnings.push({\n      type: 'validation',\n      message: 'URL entry missing required loc element',\n      context: { url: String(urlElement.loc || 'undefined') },\n    })\n    return null\n  }\n\n  const urlObj: Partial<SitemapUrl> & { loc: string } = { loc: urlElement.loc }\n\n  // Handle optional fields with validation\n  if (isValidString(urlElement.lastmod)) {\n    urlObj.lastmod = urlElement.lastmod\n  }\n\n  if (isValidString(urlElement.changefreq)) {\n    const validFreqs = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never']\n    if (validFreqs.includes(urlElement.changefreq)) {\n      urlObj.changefreq = urlElement.changefreq as SitemapStrict['changefreq']\n    }\n    else {\n      warnings.push({\n        type: 'validation',\n        message: 'Invalid changefreq value',\n        context: { url: urlElement.loc, field: 'changefreq', value: urlElement.changefreq },\n      })\n    }\n  }\n\n  const priority = parseNumber(urlElement.priority)\n  if (priority !== undefined && !Number.isNaN(priority)) {\n    if (priority < 0 || priority > 1) {\n      warnings.push({\n        type: 'validation',\n        message: 'Priority value should be between 0.0 and 1.0, clamping to valid range',\n        context: { url: urlElement.loc, field: 'priority', value: priority },\n      })\n    }\n    // Clamp priority to valid sitemap range\n    urlObj.priority = Math.max(0, Math.min(1, priority)) as SitemapStrict['priority']\n  }\n  else if (urlElement.priority !== undefined) {\n    warnings.push({\n      type: 'validation',\n      message: 'Invalid priority value',\n      context: { url: urlElement.loc, field: 'priority', value: urlElement.priority },\n    })\n  }\n\n  // Handle images\n  if (urlElement.image) {\n    const images = Array.isArray(urlElement.image) ? urlElement.image : [urlElement.image]\n    const validImages: ImageEntry[] = images\n      .map((img: ParsedImage): ImageEntry | null => {\n        if (isValidString(img.loc)) {\n          return { loc: img.loc }\n        }\n        else {\n          warnings.push({\n            type: 'validation',\n            message: 'Image missing required loc element',\n            context: { url: urlElement.loc, field: 'image.loc' },\n          })\n          return null\n        }\n      })\n      .filter((img): img is ImageEntry => img !== null)\n\n    if (validImages.length > 0) {\n      urlObj.images = validImages\n    }\n  }\n\n  // Handle videos\n  if (urlElement.video) {\n    const videos = Array.isArray(urlElement.video) ? urlElement.video : [urlElement.video]\n    const validVideos: VideoEntry[] = videos\n      .map((video: ParsedVideo): VideoEntry | null => {\n        // Check required fields\n        const missingFields: string[] = []\n        if (!isValidString(video.title))\n          missingFields.push('title')\n        if (!isValidString(video.thumbnail_loc))\n          missingFields.push('thumbnail_loc')\n        if (!isValidString(video.description))\n          missingFields.push('description')\n        if (!isValidString(video.content_loc))\n          missingFields.push('content_loc')\n\n        if (missingFields.length > 0) {\n          warnings.push({\n            type: 'validation',\n            message: `Video missing required fields: ${missingFields.join(', ')}`,\n            context: { url: urlElement.loc, field: 'video' },\n          })\n          return null\n        }\n\n        const videoObj: VideoEntry = {\n          title: video.title!,\n          thumbnail_loc: video.thumbnail_loc!,\n          description: video.description!,\n          content_loc: video.content_loc!,\n        }\n\n        // Handle optional video fields\n        if (isValidString(video.player_loc)) {\n          videoObj.player_loc = video.player_loc\n        }\n\n        const duration = parseInteger(video.duration)\n        if (duration !== undefined) {\n          videoObj.duration = duration\n        }\n        else if (video.duration !== undefined) {\n          warnings.push({\n            type: 'validation',\n            message: 'Invalid video duration value',\n            context: { url: urlElement.loc, field: 'video.duration', value: video.duration },\n          })\n        }\n\n        if (isValidString(video.expiration_date)) {\n          videoObj.expiration_date = video.expiration_date\n        }\n\n        const rating = parseNumber(video.rating)\n        if (rating !== undefined) {\n          if (rating < 0 || rating > 5) {\n            warnings.push({\n              type: 'validation',\n              message: 'Video rating should be between 0.0 and 5.0',\n              context: { url: urlElement.loc, field: 'video.rating', value: rating },\n            })\n          }\n          videoObj.rating = rating\n        }\n        else if (video.rating !== undefined) {\n          warnings.push({\n            type: 'validation',\n            message: 'Invalid video rating value',\n            context: { url: urlElement.loc, field: 'video.rating', value: video.rating },\n          })\n        }\n\n        const viewCount = parseInteger(video.view_count)\n        if (viewCount !== undefined) {\n          videoObj.view_count = viewCount\n        }\n        else if (video.view_count !== undefined) {\n          warnings.push({\n            type: 'validation',\n            message: 'Invalid video view_count value',\n            context: { url: urlElement.loc, field: 'video.view_count', value: video.view_count },\n          })\n        }\n\n        if (isValidString(video.publication_date)) {\n          videoObj.publication_date = video.publication_date\n        }\n\n        if (isValidString(video.family_friendly)) {\n          const validValues = ['yes', 'no']\n          if (validValues.includes(video.family_friendly)) {\n            videoObj.family_friendly = video.family_friendly as VideoEntry['family_friendly']\n          }\n          else {\n            warnings.push({\n              type: 'validation',\n              message: 'Invalid video family_friendly value, should be \"yes\" or \"no\"',\n              context: { url: urlElement.loc, field: 'video.family_friendly', value: video.family_friendly },\n            })\n          }\n        }\n\n        if (isValidString(video.requires_subscription)) {\n          const validValues = ['yes', 'no']\n          if (validValues.includes(video.requires_subscription)) {\n            videoObj.requires_subscription = video.requires_subscription as VideoEntry['requires_subscription']\n          }\n          else {\n            warnings.push({\n              type: 'validation',\n              message: 'Invalid video requires_subscription value, should be \"yes\" or \"no\"',\n              context: { url: urlElement.loc, field: 'video.requires_subscription', value: video.requires_subscription },\n            })\n          }\n        }\n\n        if (isValidString(video.live)) {\n          const validValues = ['yes', 'no']\n          if (validValues.includes(video.live)) {\n            videoObj.live = video.live as VideoEntry['live']\n          }\n          else {\n            warnings.push({\n              type: 'validation',\n              message: 'Invalid video live value, should be \"yes\" or \"no\"',\n              context: { url: urlElement.loc, field: 'video.live', value: video.live },\n            })\n          }\n        }\n\n        // Handle restriction (element-based, not attribute-based)\n        if (video.restriction && typeof video.restriction === 'object') {\n          const restriction = video.restriction\n          if (isValidString(restriction.relationship) && isValidString(restriction['#text'])) {\n            const validRelationships = ['allow', 'deny']\n            if (validRelationships.includes(restriction.relationship)) {\n              videoObj.restriction = {\n                relationship: restriction.relationship as 'allow' | 'deny',\n                restriction: restriction['#text'],\n              }\n            }\n            else {\n              warnings.push({\n                type: 'validation',\n                message: 'Invalid video restriction relationship, should be \"allow\" or \"deny\"',\n                context: { url: urlElement.loc, field: 'video.restriction.relationship', value: restriction.relationship },\n              })\n            }\n          }\n        }\n\n        // Handle platform (element-based, not attribute-based)\n        if (video.platform && typeof video.platform === 'object') {\n          const platform = video.platform\n          if (isValidString(platform.relationship) && isValidString(platform['#text'])) {\n            const validRelationships = ['allow', 'deny']\n            if (validRelationships.includes(platform.relationship)) {\n              videoObj.platform = {\n                relationship: platform.relationship as 'allow' | 'deny',\n                platform: platform['#text'],\n              }\n            }\n            else {\n              warnings.push({\n                type: 'validation',\n                message: 'Invalid video platform relationship, should be \"allow\" or \"deny\"',\n                context: { url: urlElement.loc, field: 'video.platform.relationship', value: platform.relationship },\n              })\n            }\n          }\n        }\n\n        // Handle price - keep as strings to maintain precision\n        if (video.price) {\n          const prices = Array.isArray(video.price) ? video.price : [video.price]\n          const validPrices = prices\n            .map((price: ParsedPrice) => {\n              const priceValue = price['#text']\n              if (priceValue == null || (typeof priceValue !== 'string' && typeof priceValue !== 'number')) {\n                warnings.push({\n                  type: 'validation',\n                  message: 'Video price missing value',\n                  context: { url: urlElement.loc, field: 'video.price' },\n                })\n                return null\n              }\n\n              const validTypes = ['rent', 'purchase', 'package', 'subscription']\n              if (price.type && !validTypes.includes(price.type)) {\n                warnings.push({\n                  type: 'validation',\n                  message: `Invalid video price type \"${price.type}\", should be one of: ${validTypes.join(', ')}`,\n                  context: { url: urlElement.loc, field: 'video.price.type', value: price.type },\n                })\n              }\n\n              return {\n                price: String(priceValue),\n                currency: price.currency,\n                type: price.type as NonNullable<VideoEntry['price']>[0]['type'],\n              }\n            })\n            .filter((p): p is NonNullable<typeof p> => p !== null)\n\n          if (validPrices.length > 0) {\n            videoObj.price = validPrices\n          }\n        }\n\n        // Handle uploader (element-based)\n        if (video.uploader && typeof video.uploader === 'object') {\n          const uploader = video.uploader\n          if (isValidString(uploader.info) && isValidString(uploader['#text'])) {\n            videoObj.uploader = {\n              uploader: uploader['#text'],\n              info: uploader.info,\n            }\n          }\n          else {\n            warnings.push({\n              type: 'validation',\n              message: 'Video uploader missing required info or name',\n              context: { url: urlElement.loc, field: 'video.uploader' },\n            })\n          }\n        }\n\n        // Handle tags\n        if (video.tag) {\n          const tags = Array.isArray(video.tag) ? video.tag : [video.tag]\n          const validTags = tags.filter(isValidString)\n\n          if (validTags.length > 0) {\n            videoObj.tag = validTags\n          }\n        }\n\n        return videoObj\n      })\n      .filter((video): video is VideoEntry => video !== null)\n\n    if (validVideos.length > 0) {\n      urlObj.videos = validVideos\n    }\n  }\n\n  // Handle alternatives (element-based xhtml:link)\n  if (urlElement.link) {\n    const links = Array.isArray(urlElement.link) ? urlElement.link : [urlElement.link]\n    const alternatives: AlternativeEntry[] = links\n      .map((link: ParsedLink): AlternativeEntry | null => {\n        if (link.rel === 'alternate' && isValidString(link.hreflang) && isValidString(link.href)) {\n          return {\n            hreflang: link.hreflang,\n            href: link.href,\n          }\n        }\n        else {\n          warnings.push({\n            type: 'validation',\n            message: 'Alternative link missing required rel=\"alternate\", hreflang, or href',\n            context: { url: urlElement.loc, field: 'link' },\n          })\n          return null\n        }\n      })\n      .filter((alt): alt is AlternativeEntry => alt !== null)\n\n    if (alternatives.length > 0) {\n      urlObj.alternatives = alternatives\n    }\n  }\n\n  // Handle news\n  if (urlElement.news && typeof urlElement.news === 'object') {\n    const news = urlElement.news as ParsedNews\n    if (\n      isValidString(news.title)\n      && isValidString(news.publication_date)\n      && news.publication\n      && isValidString(news.publication.name)\n      && isValidString(news.publication.language)\n    ) {\n      urlObj.news = {\n        title: news.title!,\n        publication_date: news.publication_date!,\n        publication: {\n          name: news.publication.name!,\n          language: news.publication.language!,\n        },\n      } as GoogleNewsEntry\n    }\n    else {\n      warnings.push({\n        type: 'validation',\n        message: 'News entry missing required fields (title, publication_date, publication.name, publication.language)',\n        context: { url: urlElement.loc, field: 'news' },\n      })\n    }\n  }\n\n  // Filter out undefined values and empty arrays - Object.fromEntries loses type info so cast is necessary\n  return Object.fromEntries(\n    Object.entries(urlObj).filter(([_, value]) =>\n      value != null && (!Array.isArray(value) || value.length > 0),\n    ),\n  ) as unknown as SitemapUrl\n}\n\nexport async function parseSitemapXml(xml: string): Promise<SitemapParseResult> {\n  const warnings: SitemapWarning[] = []\n\n  if (!xml) {\n    throw new Error('Empty XML input provided')\n  }\n  const { XMLParser } = await import('fast-xml-parser')\n  const parser = new XMLParser({\n    isArray: (tagName: string): boolean =>\n      ['url', 'image', 'video', 'link', 'tag', 'price'].includes(tagName),\n    removeNSPrefix: true,\n    parseAttributeValue: false,\n    ignoreAttributes: false,\n    attributeNamePrefix: '',\n    trimValues: true,\n  })\n\n  try {\n    const parsed = parser.parse(xml) as ParsedRoot\n\n    if (!parsed?.urlset) {\n      throw new Error('XML does not contain a valid urlset element')\n    }\n\n    if (!parsed.urlset.url) {\n      throw new Error('Sitemap contains no URL entries')\n    }\n\n    const urls = Array.isArray(parsed.urlset.url) ? parsed.urlset.url : [parsed.urlset.url]\n\n    const validUrls = urls\n      .map((url: ParsedUrl) => extractUrlFromParsedElement(url, warnings))\n      .filter((url): url is SitemapUrlInput => url !== null)\n\n    if (validUrls.length === 0 && urls.length > 0) {\n      warnings.push({\n        type: 'validation',\n        message: 'No valid URLs found in sitemap after validation',\n      })\n    }\n\n    return { urls: validUrls, warnings }\n  }\n  catch (error) {\n    if (error instanceof Error && (\n      error.message === 'Empty XML input provided'\n      || error.message === 'XML does not contain a valid urlset element'\n      || error.message === 'Sitemap contains no URL entries'\n    )) {\n      throw error\n    }\n    throw new Error(`Failed to parse XML: ${error instanceof Error ? error.message : String(error)}`)\n  }\n}\n"
  },
  {
    "path": "src/utils-internal/filter.ts",
    "content": "import type { FilterInput } from '../runtime/types'\n\n/**\n * Check if a filter is valid, otherwise exclude it\n * @param filter string | RegExp | RegexObjectType\n *\n */\nfunction isValidFilter(filter: FilterInput): boolean {\n  if (typeof filter === 'string')\n    return true\n  if (filter instanceof RegExp)\n    return true\n  if (typeof filter === 'object' && typeof filter.regex === 'string')\n    return true\n  // check if the object has a toString() function\n  return false\n}\n\n/**\n * Transform the RegeExp into RegexObjectType\n */\nexport function normalizeFilters(filters: FilterInput[] | undefined) {\n  return (filters || []).map((filter) => {\n    if (!isValidFilter(filter)) {\n      console.warn(`[@nuxtjs/sitemap] You have provided an invalid filter: ${filter}, ignoring.`)\n      return false\n    }\n    // regex needs to be converted into an object that can be serialized\n    return filter instanceof RegExp ? { regex: filter.toString() } : filter\n  }).filter(Boolean) as FilterInput[]\n}\n"
  },
  {
    "path": "src/utils-internal/i18n.ts",
    "content": "import type { AutoI18nConfig } from 'nuxtseo-shared/i18n'\nimport type { FilterInput } from '../runtime/types'\nimport { splitPathForI18nLocales as _splitPathForI18nLocales } from 'nuxtseo-shared/i18n'\n\nexport { generatePathForI18nPages, normalizeLocales } from 'nuxtseo-shared/i18n'\nexport type { AutoI18nConfig, Strategies, StrategyProps } from 'nuxtseo-shared/i18n'\n\nexport function splitPathForI18nLocales(path: FilterInput, autoI18n: AutoI18nConfig): FilterInput | FilterInput[] {\n  if (typeof path !== 'string')\n    return path\n  return _splitPathForI18nLocales(path, autoI18n)\n}\n"
  },
  {
    "path": "src/utils-internal/kit.ts",
    "content": "export { createNitroPromise, createPagesPromise, detectTarget, getNuxtModuleOptions, isNuxtGenerate, resolveNitroPreset, resolveNuxtContentVersion } from 'nuxtseo-shared/kit'\n"
  },
  {
    "path": "src/utils-internal/nuxtSitemap.ts",
    "content": "import type { Nuxt } from '@nuxt/schema'\nimport type { ConsolaInstance } from 'consola'\nimport type { NuxtPage } from 'nuxt/schema'\nimport type { AutoI18nConfig, SitemapDefinition, SitemapUrl, SitemapUrlInput } from '../runtime/types'\nimport type { CreateFilterOptions } from '../runtime/utils-pure'\nimport { statSync } from 'node:fs'\nimport { useNuxt } from '@nuxt/kit'\nimport { defu } from 'defu'\nimport { extname } from 'pathe'\nimport { withBase, withHttps } from 'ufo'\nimport { createPathFilter } from '../runtime/utils-pure'\n\nexport async function resolveUrls(urls: Required<SitemapDefinition>['urls'], ctx: { logger: ConsolaInstance, path: string }): Promise<SitemapUrlInput[]> {\n  try {\n    if (typeof urls === 'function')\n      urls = urls()\n    // resolve promise\n    urls = await urls\n  }\n  catch (e) {\n    ctx.logger.error(`Failed to resolve ${typeof urls} urls.`)\n    ctx.logger.error(e)\n    return []\n  }\n  // we need to validate that the urls can be serialised properly for example to avoid circular references\n  try {\n    urls = JSON.parse(JSON.stringify(urls))\n  }\n  catch (e) {\n    ctx.logger.error(`Failed to serialize ${typeof urls} \\`${ctx.path}\\`, please make sure that the urls resolve as a valid array without circular dependencies.`)\n    ctx.logger.error(e)\n    return []\n  }\n  return urls as SitemapUrlInput[]\n}\n\nexport interface NuxtPagesToSitemapEntriesOptions {\n  normalisedLocales: AutoI18nConfig['locales']\n  routesNameSeparator?: string\n  autoLastmod: boolean\n  defaultLocale: string\n  strategy: 'no_prefix' | 'prefix_except_default' | 'prefix' | 'prefix_and_default'\n  isI18nMapped: boolean\n  isI18nMicro: boolean\n  filter: CreateFilterOptions\n  autoI18n: boolean\n}\n\ninterface PageEntry extends SitemapUrl {\n  page?: NuxtPage\n  locale?: string\n  depth?: number\n}\n\nfunction deepForEachPage(\n  pages: NuxtPage[],\n  callback: (page: NuxtPage, fullpath: string, depth: number) => void,\n  opts: NuxtPagesToSitemapEntriesOptions,\n  fullpath: string | undefined | null = null,\n  depth: number = 0,\n) {\n  pages.forEach((page) => {\n    let currentPath\n    if (page.path.startsWith('/')) {\n      currentPath = page.path\n    }\n    else {\n      currentPath = page.path === '' ? fullpath : `${fullpath!.replace(/\\/$/, '')}/${page.path}`\n    }\n\n    let didCallback = false\n    if (opts.isI18nMicro) {\n      const localePattern = /\\/:locale\\(([^)]+)\\)/\n      const match = localePattern.exec(currentPath || '')\n      if (match && match[1]) {\n        const locales = match[1].split('|')\n        locales.forEach((locale) => {\n          const subPage = { ...page }\n          const localizedPath = (currentPath || '').replace(localePattern, `/${locale}`)\n          subPage.name += opts.routesNameSeparator + locale\n          subPage.path = localizedPath\n          callback(subPage, localizedPath || '', depth)\n          didCallback = true\n        })\n      }\n    }\n    if (!didCallback) {\n      callback(page, currentPath || '', depth)\n    }\n    if (page.children) {\n      deepForEachPage(page.children, callback, opts, currentPath, depth + 1)\n    }\n  })\n}\n\nexport function convertNuxtPagesToSitemapEntries(pages: NuxtPage[], config: NuxtPagesToSitemapEntriesOptions) {\n  const pathFilter = createPathFilter(config.filter)\n  const routesNameSeparator = config.routesNameSeparator || '___'\n  let flattenedPages: PageEntry[] = []\n  deepForEachPage(\n    pages,\n    (page, loc, depth) => {\n      flattenedPages.push({ page, loc, depth })\n    },\n    {\n      ...config,\n      routesNameSeparator: config.routesNameSeparator || '___',\n    },\n  )\n  flattenedPages = flattenedPages\n    // Removing dynamic routes\n    .filter(page => !page.loc.includes(':'))\n    // Removing duplicates\n    .filter((page, idx, arr) => {\n      return !arr.some((p) => {\n        return p.loc === page.loc && p.depth! > page.depth!\n      })\n    })\n    .map((p) => {\n      delete p.depth\n      return p\n    })\n\n  if (config.strategy === 'prefix_and_default') {\n    // filter out any pages started with the default locale\n    flattenedPages = flattenedPages.filter((p) => {\n      if (p.page?.name) {\n        const [, locale] = p.page.name.split(routesNameSeparator)\n        return locale !== config.defaultLocale || p.page.name.endsWith('__default')\n      }\n      return true\n    })\n  }\n\n  const pagesWithMeta = flattenedPages.map((p) => {\n    if (config.autoLastmod && p.page!.file) {\n      const stats = statSync(p.page!.file, { throwIfNoEntry: false })\n      if (stats?.mtime)\n        p.lastmod = stats.mtime\n    }\n    if (p.page?.meta?.sitemap) {\n      // merge in page meta\n      p = defu(p.page.meta.sitemap, p)\n    }\n    return p\n  })\n  const localeGroups: Record<string, PageEntry[]> = {}\n  pagesWithMeta.reduce((acc: Record<string, any>, e) => {\n    if (e.page!.name?.includes(routesNameSeparator)) {\n      const [name, locale] = e.page!.name.split(routesNameSeparator)\n      if (!name)\n        return acc\n      if (!acc[name])\n        acc[name] = []\n      const { _sitemap } = config.normalisedLocales.find(l => l.code === locale) || { _sitemap: locale }\n      acc[name].push({ ...e, _sitemap: config.isI18nMapped ? _sitemap : undefined, locale })\n    }\n    else {\n      acc.default = acc.default || []\n      acc.default.push(e)\n    }\n\n    return acc\n  }, localeGroups)\n\n  // now need to convert to alternatives\n  return Object.entries(localeGroups).map(([locale, entries]) => {\n    if (locale === 'default') {\n      // we add pages without a prefix, they may have disabled i18n\n      return entries.map((e) => {\n        const [name] = (e.page?.name || '').split(routesNameSeparator)\n        if (!name)\n          return false\n        // we need to check if the same page with a prefix exists within the default locale\n        // for example this will fix the `/` if the configuration is set to `prefix`\n        if (localeGroups[name]?.some(a => a.locale === config.defaultLocale))\n          return false\n        const defaultLocale = config.normalisedLocales.find(l => l.code === config.defaultLocale)\n        if (defaultLocale && config.isI18nMapped)\n          e._sitemap = defaultLocale._sitemap\n        delete e.page\n        delete e.locale\n        return { ...e }\n      }).filter(Boolean)\n    }\n    return entries.map((entry) => {\n      const alternatives = config.autoI18n\n        ? entries.map((entry) => {\n            const locale = config.normalisedLocales.find(l => l.code === entry.locale)\n            // check if the locale has a iso code\n            if (!pathFilter(entry.loc))\n              return false\n            const href = locale?.domain ? withHttps(withBase(entry.loc, locale?.domain)) : entry.loc\n            return {\n              hreflang: locale?._hreflang,\n              href,\n            }\n          }).filter(Boolean)\n        : []\n      if (config.autoI18n) {\n        const xDefault = entries.find(a => a.locale === config.defaultLocale)\n        if (xDefault && alternatives.length && pathFilter(xDefault.loc)) {\n          const locale = config.normalisedLocales.find(l => l.code === xDefault.locale)\n          const href = locale?.domain ? withHttps(withBase(xDefault.loc, locale?.domain)) : xDefault.loc\n          alternatives.push({\n            hreflang: 'x-default',\n            href,\n          })\n        }\n      }\n      const e = { ...entry }\n      if (config.isI18nMapped) {\n        const { _sitemap } = config.normalisedLocales.find(l => l.code === entry.locale) || { _sitemap: locale }\n        e._sitemap = _sitemap\n      }\n      delete e.page\n      delete e.locale\n      return {\n        ...e,\n        ...(alternatives.length ? { alternatives } : {}),\n      }\n    })\n  }).filter(Boolean).flat() as SitemapUrlInput[]\n}\n\nexport function generateExtraRoutesFromNuxtConfig(nuxt: Nuxt = useNuxt()) {\n  const filterForValidPage = (p: unknown): p is string => typeof p === 'string' && !!p && !extname(p) && !p.startsWith('/api/') && !p.startsWith('/_')\n  const routeRules = Object.entries(nuxt.options.routeRules || {})\n    .filter(([k, v]) => {\n      // make sure key doesn't use a wildcard and its not for a file\n      if (k.includes('*') || k.includes('.') || k.includes(':'))\n        return false\n      if ('robots' in v && typeof v.robots === 'boolean' && !v.robots)\n        return false\n      // make sure that we're not redirecting\n      return !v.redirect\n    })\n    .map(([k]) => k)\n    .filter(filterForValidPage)\n  // don't support files\n  const prerenderUrls = (nuxt.options.nitro.prerender?.routes || [])\n    .filter(filterForValidPage) as string[]\n  return { routeRules, prerenderUrls }\n}\n"
  },
  {
    "path": "test/bench/i18n.bench.ts",
    "content": "import type { SitemapSourceResolved } from '#sitemap'\nimport { bench, describe } from 'vitest'\nimport { resolveSitemapEntries } from '../../src/runtime/server/sitemap/builder/sitemap'\n\nconst sources: SitemapSourceResolved[] = [\n  {\n    urls: Array.from({ length: 3000 }, (_, i) => ({\n      loc: `/foo-${i}`,\n    })),\n    context: {\n      name: 'foo',\n    },\n    sourceType: 'user',\n  },\n]\n\ndescribe('i18n', () => {\n  bench('normaliseI18nSources', () => {\n    resolveSitemapEntries({\n      sitemapName: 'sitemap.xml',\n    }, sources.flatMap(s => s.urls), {\n      autoI18n: {\n        locales: [\n          { code: 'en', iso: 'en' },\n          { code: 'fr', iso: 'fr' },\n          // add 22 more locales\n          ...Array.from({ length: 22 }, (_, i) => ({\n            code: `code-${i}`,\n            iso: `iso-${i}`,\n          })),\n        ],\n        strategy: 'prefix',\n        defaultLocale: 'en',\n      },\n      isI18nMapped: true,\n    })\n  }, {\n    iterations: 1000,\n  })\n})\n"
  },
  {
    "path": "test/bench/normalize.bench.ts",
    "content": "import type { SitemapSourceResolved } from '#sitemap'\nimport { bench, describe } from 'vitest'\nimport { preNormalizeEntry } from '../../src/runtime/server/sitemap/urlset/normalise'\n\nconst sources: SitemapSourceResolved[] = [\n  {\n    urls: Array.from({ length: 3000 }, (_, i) => ({\n      loc: `/foo-${i}`,\n    })),\n    context: {\n      name: 'foo',\n    },\n    sourceType: 'user',\n  },\n]\n\ndescribe('normalize', () => {\n  bench('preNormalizeEntry', () => {\n    const urls = sources.flatMap(s => s.urls)\n    urls.map(u => preNormalizeEntry(u))\n  }, {\n    iterations: 1000,\n  })\n})\n"
  },
  {
    "path": "test/bench/sitemap.bench.ts",
    "content": "import type { AlternativeEntry, AutoI18nConfig, ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl, SitemapDefinition, SitemapUrl, SitemapUrlInput } from '../../src/runtime/types'\nimport { joinURL, withHttps } from 'ufo'\nimport { bench, describe } from 'vitest'\nimport { preNormalizeEntry } from '../../src/runtime/server/sitemap/urlset/normalise'\nimport { createPathFilter, splitForLocales } from '../../src/runtime/utils-pure'\n\ninterface NormalizedI18n extends ResolvedSitemapUrl {\n  _pathWithoutPrefix: string\n  _locale: AutoI18nConfig['locales'][number]\n  _index?: number\n}\n\nfunction getPageKey(pathWithoutPrefix: string): string {\n  const stripped = pathWithoutPrefix[0] === '/' ? pathWithoutPrefix.slice(1) : pathWithoutPrefix\n  return stripped.endsWith('/index') ? stripped.slice(0, -6) || 'index' : stripped || 'index'\n}\n\n// Implementation matching src/runtime/server/sitemap/builder/sitemap.ts\nfunction resolveSitemapEntries(sitemap: SitemapDefinition, urls: SitemapUrlInput[], runtimeConfig: Pick<ModuleRuntimeConfig, 'autoI18n' | 'isI18nMapped'>, resolvers?: NitroUrlResolvers): ResolvedSitemapUrl[] {\n  const {\n    autoI18n,\n    isI18nMapped,\n  } = runtimeConfig\n  const filterPath = createPathFilter({\n    include: sitemap.include,\n    exclude: sitemap.exclude,\n  })\n  const _urls = urls.map((_e) => {\n    const e = preNormalizeEntry(_e, resolvers)\n    if (!e.loc || !filterPath(e.loc))\n      return false\n    return e\n  }).filter(Boolean) as ResolvedSitemapUrl[]\n\n  let validI18nUrlsForTransform: NormalizedI18n[] = []\n  const withoutPrefixPaths: Record<string, NormalizedI18n[]> = {}\n  if (autoI18n && autoI18n.strategy !== 'no_prefix') {\n    const localeCodes = autoI18n.locales.map(l => l.code)\n    const localeByCode = new Map(autoI18n.locales.map(l => [l.code, l]))\n    const isPrefixStrategy = autoI18n.strategy === 'prefix'\n    const isPrefixExceptOrAndDefault = autoI18n.strategy === 'prefix_and_default' || autoI18n.strategy === 'prefix_except_default'\n    const xDefaultAndLocales = [{ code: 'x-default', _hreflang: 'x-default' }, ...autoI18n.locales] as Array<{ code: string, _hreflang: string }>\n    const defaultLocale = autoI18n.defaultLocale\n    const hasPages = !!autoI18n.pages\n    const hasDifferentDomains = !!autoI18n.differentDomains\n\n    validI18nUrlsForTransform = _urls.map((_e, i) => {\n      if (_e._abs)\n        return false\n      const split = splitForLocales(_e._relativeLoc, localeCodes)\n      let localeCode = split[0]\n      const pathWithoutPrefix = split[1]\n      if (!localeCode)\n        localeCode = defaultLocale\n      const e = _e as NormalizedI18n\n      e._pathWithoutPrefix = pathWithoutPrefix\n      const locale = localeByCode.get(localeCode)\n      if (!locale)\n        return false\n      e._locale = locale\n      e._index = i\n      e._key = `${e._sitemap || ''}${e._path?.pathname || '/'}${e._path?.search || ''}`\n      withoutPrefixPaths[pathWithoutPrefix] = withoutPrefixPaths[pathWithoutPrefix] || []\n      if (!withoutPrefixPaths[pathWithoutPrefix].some(e => e._locale.code === locale.code))\n        withoutPrefixPaths[pathWithoutPrefix].push(e)\n      return e\n    }).filter(Boolean) as NormalizedI18n[]\n\n    for (const e of validI18nUrlsForTransform) {\n      if (!e._i18nTransform && !e.alternatives?.length) {\n        const alternatives = (withoutPrefixPaths[e._pathWithoutPrefix] || [])\n          .map((u) => {\n            const entries: AlternativeEntry[] = []\n            if (u._locale.code === defaultLocale) {\n              entries.push({\n                href: u.loc,\n                hreflang: 'x-default',\n              })\n            }\n            entries.push({\n              href: u.loc,\n              hreflang: u._locale._hreflang || defaultLocale,\n            })\n            return entries\n          })\n          .flat()\n          .filter(Boolean) as AlternativeEntry[]\n        if (alternatives.length)\n          e.alternatives = alternatives\n      }\n      else if (e._i18nTransform) {\n        delete e._i18nTransform\n        if (hasDifferentDomains) {\n          const defLocale = localeByCode.get(defaultLocale)\n          e.alternatives = [\n            {\n              ...defLocale,\n              code: 'x-default',\n            },\n            ...autoI18n.locales\n              .filter(l => !!l.domain),\n          ]\n            .map((locale) => {\n              return {\n                hreflang: locale._hreflang!,\n                href: joinURL(withHttps(locale.domain!), e._pathWithoutPrefix),\n              }\n            })\n        }\n        else {\n          const pageKey = hasPages ? getPageKey(e._pathWithoutPrefix) : ''\n          const pageMappings = hasPages ? autoI18n.pages![pageKey] : undefined\n          const pathSearch = e._path?.search || ''\n          const pathWithoutPrefix = e._pathWithoutPrefix\n\n          for (const l of autoI18n.locales) {\n            let loc = pathWithoutPrefix\n\n            if (hasPages && pageMappings && pageMappings[l.code] !== undefined) {\n              const customPath = pageMappings[l.code]\n              if (customPath === false)\n                continue\n              if (typeof customPath === 'string')\n                loc = customPath[0] === '/' ? customPath : `/${customPath}`\n            }\n            else if (!hasDifferentDomains && !(isPrefixExceptOrAndDefault && l.code === defaultLocale)) {\n              loc = joinURL(`/${l.code}`, pathWithoutPrefix)\n            }\n\n            const _sitemap = isI18nMapped ? l._sitemap : undefined\n            const alternatives: AlternativeEntry[] = []\n            for (const locale of xDefaultAndLocales) {\n              const code = locale.code === 'x-default' ? defaultLocale : locale.code\n              const isDefault = locale.code === 'x-default' || locale.code === defaultLocale\n              let href = pathWithoutPrefix\n\n              if (hasPages && pageMappings && pageMappings[code] !== undefined) {\n                const customPath = pageMappings[code]\n                if (customPath === false)\n                  continue\n                if (typeof customPath === 'string')\n                  href = customPath[0] === '/' ? customPath : `/${customPath}`\n              }\n              else if (isPrefixStrategy) {\n                href = joinURL('/', code, pathWithoutPrefix)\n              }\n              else if (isPrefixExceptOrAndDefault && !isDefault) {\n                href = joinURL('/', code, pathWithoutPrefix)\n              }\n\n              if (!filterPath(href))\n                continue\n              alternatives.push({\n                hreflang: locale._hreflang,\n                href,\n              })\n            }\n\n            const { _index: _, ...rest } = e\n            const newEntry = preNormalizeEntry({\n              _sitemap,\n              ...rest,\n              _key: `${_sitemap || ''}${loc || '/'}${pathSearch}`,\n              _locale: l,\n              loc,\n              alternatives,\n            } as SitemapUrl, resolvers) as NormalizedI18n\n            if (e._locale.code === newEntry._locale.code) {\n              _urls[e._index!] = newEntry\n              e._index = undefined\n            }\n            else {\n              _urls.push(newEntry)\n            }\n          }\n        }\n      }\n      if (isI18nMapped) {\n        e._sitemap = e._sitemap || e._locale._sitemap\n        e._key = `${e._sitemap || ''}${e.loc || '/'}${e._path?.search || ''}`\n      }\n      if (e._index)\n        _urls[e._index] = e\n    }\n  }\n  return _urls\n}\n\nconst resolvers: NitroUrlResolvers = {\n  canonicalUrlResolver: (url: string) => `https://example.com${url}`,\n  relativeBaseUrlResolver: (url: string) => url,\n  fixSlashes: (url: string) => url,\n}\n\nconst sitemap: SitemapDefinition = {\n  sitemapName: 'default',\n  include: undefined,\n  exclude: undefined,\n}\n\nconst locales = ['en', 'fr', 'de', 'es', 'it', 'pt', 'nl', 'pl', 'ru', 'ja'].map(code => ({\n  code,\n  language: code,\n  _sitemap: code,\n  _hreflang: code,\n}))\n\nconst autoI18nPrefix: AutoI18nConfig = {\n  locales,\n  defaultLocale: 'en',\n  strategy: 'prefix',\n}\n\nconst autoI18nPrefixExceptDefault: AutoI18nConfig = {\n  locales,\n  defaultLocale: 'en',\n  strategy: 'prefix_except_default',\n}\n\n// URLs with i18n prefixes (1000 urls across 10 locales)\nconst i18nUrls: SitemapUrlInput[] = locales.flatMap(locale =>\n  Array.from({ length: 100 }, (_, i) => ({\n    loc: `/${locale.code}/page-${i}`,\n    lastmod: '2024-01-01',\n  })),\n)\n\n// URLs that need _i18nTransform (each expands to 10 locale variants)\nconst transformUrls: SitemapUrlInput[] = Array.from({ length: 200 }, (_, i) => ({\n  loc: `/page-${i}`,\n  lastmod: '2024-01-01',\n  _i18nTransform: true,\n}))\n\n// Simple URLs without i18n\nconst simpleUrls: SitemapUrlInput[] = Array.from({ length: 1000 }, (_, i) => ({\n  loc: `/page-${i}`,\n  lastmod: '2024-01-01',\n}))\n\n// Large URL set for filtering benchmarks\nconst largeUrls: SitemapUrlInput[] = Array.from({ length: 5000 }, (_, i) => ({\n  loc: `/category-${i % 10}/product-${i}`,\n  lastmod: '2024-01-01',\n}))\n\n// Mixed URLs with various features\nconst mixedUrls: SitemapUrlInput[] = Array.from({ length: 1000 }, (_, i) => ({\n  loc: `/page-${i}?foo=bar`,\n  lastmod: '2024-01-01',\n  changefreq: 'weekly' as const,\n  priority: 0.8,\n}))\n\n// Sitemap with string pattern filtering (glob-style)\nconst sitemapWithStringFilters: SitemapDefinition = {\n  sitemapName: 'filtered',\n  include: ['/category-0/**', '/category-1/**', '/category-2/**'],\n  exclude: ['/category-*/product-0', '/category-*/product-1'],\n}\n\n// Sitemap with regex filtering\nconst sitemapWithRegexFilters: SitemapDefinition = {\n  sitemapName: 'regex-filtered',\n  include: [/^\\/category-[0-2]\\//, /^\\/category-5\\//],\n  exclude: [/product-\\d$/, /product-1\\d$/],\n}\n\n// Sitemap with many filter rules (stress test)\nconst sitemapWithManyFilters: SitemapDefinition = {\n  sitemapName: 'many-filters',\n  include: Array.from({ length: 20 }, (_, i) => `/category-${i % 10}/**`),\n  exclude: Array.from({ length: 10 }, (_, i) => `/category-*/product-${i}`),\n}\n\ndescribe('resolveSitemapEntries', () => {\n  bench('1000 simple urls (no i18n)', () => {\n    resolveSitemapEntries(sitemap, simpleUrls, { autoI18n: undefined, isI18nMapped: false }, resolvers)\n  }, { iterations: 100 })\n\n  bench('1000 mixed urls with query (no i18n)', () => {\n    resolveSitemapEntries(sitemap, mixedUrls, { autoI18n: undefined, isI18nMapped: false }, resolvers)\n  }, { iterations: 100 })\n\n  bench('1000 i18n urls (prefix)', () => {\n    resolveSitemapEntries(sitemap, i18nUrls, { autoI18n: autoI18nPrefix, isI18nMapped: false }, resolvers)\n  }, { iterations: 50 })\n\n  bench('1000 i18n urls (prefix_except_default)', () => {\n    resolveSitemapEntries(sitemap, i18nUrls, { autoI18n: autoI18nPrefixExceptDefault, isI18nMapped: false }, resolvers)\n  }, { iterations: 50 })\n\n  bench('200 urls _i18nTransform (prefix)', () => {\n    resolveSitemapEntries(sitemap, transformUrls, { autoI18n: autoI18nPrefix, isI18nMapped: false }, resolvers)\n  }, { iterations: 20 })\n\n  bench('200 urls _i18nTransform (prefix_except_default)', () => {\n    resolveSitemapEntries(sitemap, transformUrls, { autoI18n: autoI18nPrefixExceptDefault, isI18nMapped: false }, resolvers)\n  }, { iterations: 20 })\n})\n\ndescribe('createPathFilter performance', () => {\n  bench('5000 urls with string pattern filters', () => {\n    resolveSitemapEntries(sitemapWithStringFilters, largeUrls, { autoI18n: undefined, isI18nMapped: false }, resolvers)\n  }, { iterations: 20 })\n\n  bench('5000 urls with regex filters', () => {\n    resolveSitemapEntries(sitemapWithRegexFilters, largeUrls, { autoI18n: undefined, isI18nMapped: false }, resolvers)\n  }, { iterations: 20 })\n\n  bench('5000 urls with many filter rules', () => {\n    resolveSitemapEntries(sitemapWithManyFilters, largeUrls, { autoI18n: undefined, isI18nMapped: false }, resolvers)\n  }, { iterations: 20 })\n\n  bench('createPathFilter - isolated filter calls (1000x)', () => {\n    const filter = createPathFilter({\n      include: ['/category-0/**', '/category-1/**'],\n      exclude: ['/category-*/product-0'],\n    })\n    for (let i = 0; i < 1000; i++) {\n      filter(`/category-${i % 10}/product-${i}`)\n    }\n  }, { iterations: 100 })\n})\n"
  },
  {
    "path": "test/bench/xml.bench.ts",
    "content": "import type { ResolvedSitemapUrl } from '../../src/runtime/types'\nimport { bench, describe } from 'vitest'\nimport { urlsToXml } from '../../src/runtime/server/sitemap/builder/xml'\n\nconst resolvers = {\n  canonicalUrlResolver: (url: string) => `https://example.com${url}`,\n  relativeBaseUrlResolver: (url: string) => url,\n  fixSlashes: (url: string) => url,\n}\n\nconst simpleUrls: ResolvedSitemapUrl[] = Array.from({ length: 1000 }, (_, i) => ({\n  loc: `https://example.com/page-${i}`,\n  lastmod: '2024-01-01',\n  changefreq: 'weekly' as const,\n  priority: 0.8,\n  _key: `page-${i}`,\n  _path: null,\n}))\n\nconst urlsWithImages: ResolvedSitemapUrl[] = Array.from({ length: 1000 }, (_, i) => ({\n  loc: `https://example.com/page-${i}`,\n  lastmod: '2024-01-01',\n  images: [\n    { loc: `https://example.com/img-${i}-1.jpg`, title: 'Image 1', caption: 'A caption' },\n    { loc: `https://example.com/img-${i}-2.jpg`, title: 'Image 2' },\n  ],\n  _key: `page-${i}`,\n  _path: null,\n}))\n\nconst urlsWithVideos: ResolvedSitemapUrl[] = Array.from({ length: 500 }, (_, i) => ({\n  loc: `https://example.com/video-${i}`,\n  videos: [{\n    title: `Video ${i}`,\n    description: 'A video description',\n    thumbnail_loc: `https://example.com/thumb-${i}.jpg`,\n    content_loc: `https://example.com/video-${i}.mp4`,\n    duration: 300,\n    rating: 4.5,\n    view_count: 1000,\n    family_friendly: true,\n    live: false,\n    tag: ['tag1', 'tag2'],\n  }],\n  _key: `video-${i}`,\n  _path: null,\n}))\n\nconst mixedUrls: ResolvedSitemapUrl[] = [\n  ...simpleUrls.slice(0, 500),\n  ...urlsWithImages.slice(0, 300),\n  ...urlsWithVideos.slice(0, 200),\n]\n\nconst config = { version: '7.0.0', xsl: false, credits: false, minify: false }\nconst configMinify = { version: '7.0.0', xsl: false, credits: false, minify: true }\n\ndescribe('xml generation', () => {\n  bench('1000 simple urls', () => {\n    urlsToXml(simpleUrls, resolvers, config)\n  }, { iterations: 100 })\n\n  bench('1000 urls with images', () => {\n    urlsToXml(urlsWithImages, resolvers, config)\n  }, { iterations: 100 })\n\n  bench('500 urls with videos', () => {\n    urlsToXml(urlsWithVideos, resolvers, config)\n  }, { iterations: 100 })\n\n  bench('1000 mixed urls', () => {\n    urlsToXml(mixedUrls, resolvers, config)\n  }, { iterations: 100 })\n\n  bench('1000 simple urls (minified)', () => {\n    urlsToXml(simpleUrls, resolvers, configMinify)\n  }, { iterations: 100 })\n\n  bench('1000 mixed urls (minified)', () => {\n    urlsToXml(mixedUrls, resolvers, configMinify)\n  }, { iterations: 100 })\n})\n"
  },
  {
    "path": "test/e2e/chunks/cache-headers.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\n// Set up chunked sitemaps\nawait setup({\n  rootDir: resolve('../../fixtures/chunks'),\n  nuxtConfig: {\n    sitemap: {\n      // Global automatic chunking\n      chunks: true,\n      defaultSitemapsChunkSize: 100,\n      cacheMaxAgeSeconds: 900, // 15 minutes\n      runtimeCacheStorage: {\n        driver: 'memory', // Use memory driver to avoid Redis connection issues\n      },\n    },\n  },\n})\n\ndescribe('chunked sitemap caching with headers', () => {\n  it('should return proper cache headers for sitemap index', async () => {\n    const response = await fetch('/sitemap_index.xml')\n\n    expect(response.headers.get('content-type')).toMatch(/xml/)\n\n    // Check cache headers\n    const cacheControl = response.headers.get('cache-control')\n    expect(cacheControl).toBeDefined()\n    expect(cacheControl).toContain('max-age=900')\n    expect(cacheControl).toContain('s-maxage=900')\n    expect(cacheControl).toContain('public')\n    expect(cacheControl).toContain('stale-while-revalidate')\n\n    // Check debug headers\n    expect(response.headers.get('X-Sitemap-Generated')).toBeDefined()\n    expect(response.headers.get('X-Sitemap-Cache-Duration')).toBe('900s')\n    expect(response.headers.get('X-Sitemap-Cache-Expires')).toBeDefined()\n    expect(response.headers.get('X-Sitemap-Cache-Remaining')).toBeDefined()\n\n    const xml = await response.text()\n    expect(xml).toContain('<sitemapindex')\n    expect(xml).toContain('<sitemap>')\n    expect(xml).toContain('<loc>')\n  }, 10000)\n\n  it('should return proper cache headers for first chunk', async () => {\n    const response = await fetch('/__sitemap__/0.xml')\n\n    expect(response.headers.get('content-type')).toMatch(/xml/)\n\n    // Check cache headers\n    const cacheControl = response.headers.get('cache-control')\n    expect(cacheControl).toBeDefined()\n    expect(cacheControl).toContain('max-age=900')\n    expect(cacheControl).toContain('s-maxage=900')\n    expect(cacheControl).toContain('public')\n    expect(cacheControl).toContain('stale-while-revalidate')\n\n    // Check debug headers\n    expect(response.headers.get('X-Sitemap-Generated')).toBeDefined()\n    expect(response.headers.get('X-Sitemap-Cache-Duration')).toBe('900s')\n    expect(response.headers.get('X-Sitemap-Cache-Expires')).toBeDefined()\n    expect(response.headers.get('X-Sitemap-Cache-Remaining')).toBeDefined()\n  })\n\n  it('should properly generate chunked sitemaps in index', async () => {\n    const response = await fetch('/sitemap_index.xml')\n\n    const xml = await response.text()\n    expect(xml).toContain('<sitemapindex')\n    expect(xml).toContain('/__sitemap__/0.xml')\n  }, 10000)\n})\n"
  },
  {
    "path": "test/e2e/chunks/chunk-count.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/chunk-count'),\n})\n\ndescribe('declared chunkCount', () => {\n  it('renders the index from the declared count without hitting the source', async () => {\n    const before = (await $fetch<{ count: number }>('/api/posts-call-count')).count\n    const indexXml = await $fetch<string>('/sitemap_index.xml')\n    const after = (await $fetch<{ count: number }>('/api/posts-call-count')).count\n\n    expect(after - before).toBe(0)\n\n    for (let i = 0; i < 4; i++) {\n      expect(indexXml).toContain(`/__sitemap__/posts-${i}.xml`)\n    }\n    expect(indexXml).not.toContain('/__sitemap__/posts-4.xml')\n  }, 30000)\n\n  it('chunks fetch sources on demand and the data is correct', async () => {\n    const chunk0 = await $fetch<string>('/__sitemap__/posts-0.xml')\n    expect(chunk0).toContain('/posts/1')\n    expect(chunk0).toContain('/posts/5')\n  }, 30000)\n})\n"
  },
  {
    "path": "test/e2e/chunks/default.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/chunks'),\n})\ndescribe.skipIf(process.env.CI)('multi chunks', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/sitemap_index.xml')\n    // remove lastmods before tresting\n    sitemap = sitemap.replace(/lastmod>(.*?)</g, 'lastmod><')\n    // basic test to make sure we get a valid response\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/0.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/1.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/2.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/3.xml</loc>\n          </sitemap>\n      </sitemapindex>\"\n    `)\n    const sitemap0 = await $fetch('/__sitemap__/0.xml')\n    expect(sitemap0).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/foo/1</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/2</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/3</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/4</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/5</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/chunks/generate.test.ts",
    "content": "import { readFile } from 'node:fs/promises'\nimport { buildNuxt, createResolver, loadNuxt } from '@nuxt/kit'\nimport { describe, expect, it } from 'vitest'\n\ndescribe.skipIf(process.env.CI)('generate', () => {\n  it('basic', async () => {\n    process.env.NODE_ENV = 'production'\n    // @ts-expect-error untyped\n    process.env.prerender = true\n    process.env.NITRO_PRESET = 'static'\n    process.env.NUXT_PUBLIC_SITE_URL = 'https://nuxtseo.com'\n    const { resolve } = createResolver(import.meta.url)\n    const rootDir = resolve('../../fixtures/chunks')\n    const nuxt = await loadNuxt({\n      rootDir,\n      overrides: {\n        nitro: {\n          preset: 'static',\n        },\n        _generate: true,\n      },\n    })\n\n    await buildNuxt(nuxt)\n\n    await new Promise(resolve => setTimeout(resolve, 1000))\n\n    const sitemap = (await readFile(resolve(rootDir, '.output/public/sitemap_index.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')\n    // ignore lastmod entries\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/0.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/1.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/2.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/3.xml</loc>\n          </sitemap>\n      </sitemapindex>\"\n    `)\n    const sitemapEn = (await readFile(resolve(rootDir, '.output/public/__sitemap__/0.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')\n    expect(sitemapEn).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/foo/1</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/2</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/3</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/4</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/5</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 1200000)\n})\n"
  },
  {
    "path": "test/e2e/chunks/memoization.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/chunk-cache'),\n})\n\ndescribe('chunk resolved-urls memoization', () => {\n  it('all chunks of the same base share one source fetch', async () => {\n    // 17 entries × chunk size 5 → 4 chunks (0..3)\n    await $fetch('/__sitemap__/posts-0.xml')\n    await $fetch('/__sitemap__/posts-1.xml')\n    await $fetch('/__sitemap__/posts-2.xml')\n    await $fetch('/__sitemap__/posts-3.xml')\n\n    const { count } = await $fetch<{ count: number }>('/api/source-call-count')\n    expect(count).toBe(1)\n  }, 30000)\n\n  it('chunked output reflects the shared sorted slice', async () => {\n    const chunk0 = await $fetch<string>('/__sitemap__/posts-0.xml')\n    const chunk3 = await $fetch<string>('/__sitemap__/posts-3.xml')\n\n    expect(chunk0).toContain('/posts/1')\n    expect(chunk0).toContain('/posts/5')\n    expect(chunk0).not.toContain('/posts/6')\n\n    expect(chunk3).toContain('/posts/16')\n    expect(chunk3).toContain('/posts/17')\n    expect(chunk3).not.toContain('/posts/15')\n  }, 30000)\n})\n"
  },
  {
    "path": "test/e2e/content-v3/default.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/content-v3'),\n})\ndescribe('nuxt/content v3 default', () => {\n  it('basic', async () => {\n    const nuxtContentUrls = await $fetch('/__sitemap__/nuxt-content-urls.json')\n    expect(nuxtContentUrls).toMatchInlineSnapshot(`\n      [\n        {\n          \"changefreq\": \"daily\",\n          \"images\": [\n            {\n              \"loc\": \"https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg\",\n            },\n          ],\n          \"lastmod\": \"2021-10-20\",\n          \"loc\": \"/bar\",\n          \"priority\": 0.5,\n        },\n        {\n          \"loc\": \"/foo\",\n          \"priority\": 0.5,\n        },\n        {\n          \"lastmod\": \"2021-10-20\",\n          \"loc\": \"/posts/bar\",\n        },\n        {\n          \"lastmod\": \"2021-10-20\",\n          \"loc\": \"/posts/fallback\",\n        },\n        {\n          \"loc\": \"/posts/foo\",\n        },\n        {\n          \"changefreq\": \"weekly\",\n          \"lastmod\": \"2025-05-14\",\n          \"loc\": \"/test-json\",\n          \"priority\": 0.9,\n        },\n        {\n          \"changefreq\": \"monthly\",\n          \"lastmod\": \"2025-05-13\",\n          \"loc\": \"/test-yaml\",\n          \"priority\": 0.8,\n        },\n      ]\n    `)\n\n    const sitemap = await $fetch('/sitemap.xml')\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/bar</loc>\n              <lastmod>2021-10-20</lastmod>\n              <changefreq>daily</changefreq>\n              <priority>0.5</priority>\n              <image:image>\n                  <image:loc>https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg</image:loc>\n              </image:image>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo</loc>\n              <priority>0.5</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/test-json</loc>\n              <lastmod>2025-05-14</lastmod>\n              <changefreq>weekly</changefreq>\n              <priority>0.9</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/test-yaml</loc>\n              <lastmod>2025-05-13</lastmod>\n              <changefreq>monthly</changefreq>\n              <priority>0.8</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/posts/bar</loc>\n              <lastmod>2021-10-20</lastmod>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/posts/fallback</loc>\n              <lastmod>2021-10-20</lastmod>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/posts/foo</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/content-v3/define-schema.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/content-v3-define-schema'),\n  build: true,\n})\n\ndescribe('nuxt/content v3 defineSitemapSchema', () => {\n  it('includes content with sitemap schema', async () => {\n    const urls = await $fetch<any[]>('/__sitemap__/nuxt-content-urls.json')\n    const paths = urls.map(u => u.loc)\n\n    expect(paths).toContain('/foo')\n    expect(paths).toContain('/bar')\n    expect(paths).toContain('/published')\n  })\n\n  it('filters content entries using defineSitemapSchema filter', async () => {\n    const urls = await $fetch<any[]>('/__sitemap__/nuxt-content-urls.json')\n    const paths = urls.map(u => u.loc)\n\n    // draft.md (draft: true) should be excluded\n    expect(paths).not.toContain('/draft')\n    // future.md (date: 2099-01-01) should be excluded\n    expect(paths).not.toContain('/future')\n  })\n\n  it('preserves sitemap frontmatter values', async () => {\n    const urls = await $fetch<any[]>('/__sitemap__/nuxt-content-urls.json')\n    const foo = urls.find(u => u.loc === '/foo')\n    expect(foo).toBeDefined()\n    expect(foo.priority).toBe(0.5)\n  })\n})\n"
  },
  {
    "path": "test/e2e/content-v3/filtering.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/content-v3-filtering'),\n  build: true,\n})\n\ndescribe('nuxt/content v3 filtering', () => {\n  it('filters content entries using collection filter', async () => {\n    const urls = await $fetch<any[]>('/__sitemap__/nuxt-content-urls.json')\n    const paths = urls.map(u => u.loc)\n\n    // draft.md (draft: true) should be excluded\n    expect(paths).not.toContain('/draft')\n    // future.md (date: 2099-01-01) should be excluded\n    expect(paths).not.toContain('/future')\n\n    // published.md (date in past, draft: false) should be included\n    expect(paths).toContain('/published')\n    // regular posts without draft/date fields should be included\n    expect(paths).toContain('/foo')\n    expect(paths).toContain('/bar')\n  })\n})\n"
  },
  {
    "path": "test/e2e/content-v3/i18n.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/content-v3-i18n'),\n})\n\ndescribe('nuxt/content v3 + i18n', () => {\n  it('content URLs have correct locale prefixes', async () => {\n    const urls = await $fetch('/__sitemap__/nuxt-content-urls.json')\n    // en collection should produce un-prefixed paths (default locale)\n    // ja collection should produce /ja/ prefixed paths (from collection prefix config)\n    const locs = (urls as { loc: string }[]).map(u => u.loc).sort()\n    expect(locs).toContain('/')\n    expect(locs).toContain('/getting-started')\n    expect(locs).toContain('/ja')\n    expect(locs).toContain('/ja/getting-started')\n  }, 60000)\n\n  it('en sitemap contains only en URLs', async () => {\n    const sitemap = await $fetch('/__sitemap__/en-US.xml')\n    // should contain en URLs (dev mode uses local origin)\n    expect(sitemap).toContain('<loc>')\n    expect(sitemap).toContain('/getting-started')\n  }, 60000)\n\n  it('ja sitemap contains only ja URLs', async () => {\n    const sitemap = await $fetch('/__sitemap__/ja-JP.xml')\n    // should contain ja URLs (dev mode uses local origin)\n    expect(sitemap).toContain('/ja')\n    expect(sitemap).toContain('/ja/getting-started')\n  }, 60000)\n\n  it('sitemap index lists both locale sitemaps', async () => {\n    const index = await $fetch('/sitemap_index.xml')\n    expect(index).toContain('en-US.xml')\n    expect(index).toContain('ja-JP.xml')\n  }, 60000)\n}, 120000)\n"
  },
  {
    "path": "test/e2e/content-v3/yaml-json.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/content-v3'),\n  nuxtConfig: {\n    sitemap: {\n      xsl: false,\n    },\n    site: {\n      url: 'https://nuxtseo.com',\n    },\n  },\n})\n\ndescribe('content-v3 YAML/JSON', () => {\n  it('basic', async () => {\n    const sitemapContents = await $fetch('/sitemap.xml', { responseType: 'text' })\n\n    // Check that YAML content with sitemap metadata is included\n    expect(sitemapContents).toMatch('https://nuxtseo.com/test-yaml')\n\n    // Check YAML sitemap metadata is extracted\n    const yamlMatch = sitemapContents.match(/<url>.*?<loc>https:\\/\\/nuxtseo\\.com\\/test-yaml<\\/loc>.*?<\\/url>/s)\n    expect(yamlMatch).toBeTruthy()\n    if (yamlMatch) {\n      expect(yamlMatch[0]).toMatch('<lastmod>2025-05-13')\n      expect(yamlMatch[0]).toMatch('<changefreq>monthly</changefreq>')\n      expect(yamlMatch[0]).toMatch('<priority>0.8</priority>')\n    }\n\n    // Check that JSON content with sitemap metadata is included\n    expect(sitemapContents).toMatch('https://nuxtseo.com/test-json')\n\n    // Check JSON sitemap metadata is extracted\n    const jsonMatch = sitemapContents.match(/<url>.*?<loc>https:\\/\\/nuxtseo\\.com\\/test-json<\\/loc>.*?<\\/url>/s)\n    expect(jsonMatch).toBeTruthy()\n    if (jsonMatch) {\n      expect(jsonMatch[0]).toMatch('<lastmod>2025-05-14')\n      expect(jsonMatch[0]).toMatch('<changefreq>weekly</changefreq>')\n      expect(jsonMatch[0]).toMatch('<priority>0.9</priority>')\n    }\n  })\n})\n"
  },
  {
    "path": "test/e2e/global-setup.ts",
    "content": "import fsp from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst fixturesDir = fileURLToPath(new URL('../fixtures', import.meta.url))\n\nexport default async function setup() {\n  for (const project of await fsp.readdir(fixturesDir)) {\n    await fsp.rm(join(fixturesDir, project, 'node_modules/.cache'), {\n      recursive: true,\n      force: true,\n    })\n    await fsp.rm(join(fixturesDir, project, '.data'), {\n      recursive: true,\n      force: true,\n    })\n  }\n}\n"
  },
  {
    "path": "test/e2e/hooks/sources-hook-simple.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\ndescribe('sitemap:sources hook', async () => {\n  await setup({\n    rootDir: resolve('../../fixtures/sources-hook'),\n    server: true,\n  })\n\n  it('can add new sources dynamically', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    // Should have URLs from the dynamically added source\n    expect(sitemap).toContain('<loc>https://example.com/dynamic-source-url</loc>')\n  })\n\n  it('can modify existing sources', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    // Should have URLs showing the headers were modified\n    expect(sitemap).toContain('<loc>https://example.com/hook-modified</loc>')\n  })\n\n  it('can filter out sources', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    // The skipped source should not appear in the sitemap\n    expect(sitemap).not.toContain('<loc>https://example.com/should-be-filtered</loc>')\n  })\n})\n"
  },
  {
    "path": "test/e2e/i18n/custom-paths-no-prefix.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n-no-prefix'),\n  server: true,\n  sitemap: {\n    urls: [\n      // test custom path mapping with no_prefix - should warn\n      {\n        loc: '/test',\n        _i18nTransform: true,\n      },\n      {\n        loc: '/about',\n        _i18nTransform: true,\n      },\n    ],\n  },\n})\n\ndescribe('i18n custom paths with no_prefix strategy', () => {\n  it('should generate alternatives with custom paths even with no_prefix when pages config is present', async () => {\n    // The actual behavior is that _i18nTransform works with no_prefix when pages config is present\n    // This is counter-intuitive but is the current implementation\n    const sitemap = await $fetch('/sitemap.xml')\n\n    // The implementation still creates all locale variants with custom paths\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/test</loc>')\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/about</loc>')\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/prueba</loc>')\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/teste</loc>')\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/acerca-de</loc>')\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/a-propos</loc>')\n\n    // And it includes hreflang alternatives\n    expect(sitemap).toContain('xhtml:link')\n    expect(sitemap).toContain('hreflang')\n\n    // The warning is still issued because this is not recommended behavior\n  })\n\n  it('should have warning in dev mode for _i18nTransform with no_prefix', async () => {\n    // The warning is important because while the transformation works,\n    // it's not recommended with no_prefix strategy\n    const sitemap = await $fetch('/sitemap.xml')\n\n    // Even though it transforms, the warning tells users this is not intended behavior\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/test</loc>')\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/about</loc>')\n  })\n})\n"
  },
  {
    "path": "test/e2e/i18n/custom-paths.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\n// Use dedicated fixture with custom path config including dynamic routes\nawait setup({\n  rootDir: resolve('../../fixtures/i18n-custom-paths'),\n  server: true,\n})\n\ndescribe('i18n custom paths with _i18nTransform', () => {\n  it('should use custom paths from pages config for _i18nTransform', async () => {\n    // With prefix_except_default, we get separate sitemaps per locale\n    const enSitemap = await $fetch('/__sitemap__/en-US.xml')\n    const esSitemap = await $fetch('/__sitemap__/es-ES.xml')\n    const frSitemap = await $fetch('/__sitemap__/fr-FR.xml')\n\n    // Test that /test with _i18nTransform generates custom paths\n    expect(enSitemap).toContain('<loc>https://nuxtseo.com/test</loc>')\n    expect(esSitemap).toContain('<loc>https://nuxtseo.com/es/prueba</loc>')\n    expect(frSitemap).toContain('<loc>https://nuxtseo.com/fr/teste</loc>')\n\n    // Test about with custom paths\n    expect(enSitemap).toContain('<loc>https://nuxtseo.com/about</loc>')\n    expect(esSitemap).toContain('<loc>https://nuxtseo.com/es/acerca-de</loc>')\n    expect(frSitemap).toContain('<loc>https://nuxtseo.com/fr/a-propos</loc>')\n\n    // Check that alternatives use custom paths in the English sitemap\n    expect(enSitemap).toContain('href=\"https://nuxtseo.com/es/prueba\"')\n    expect(enSitemap).toContain('href=\"https://nuxtseo.com/fr/teste\"')\n  })\n\n  it('should generate correct alternatives for URLs with _i18nTransform', async () => {\n    const enSitemap = await $fetch('/__sitemap__/en-US.xml')\n\n    // Check the test URL entry\n    const testUrlMatch = enSitemap.match(/<url>[\\s\\S]*?<loc>https:\\/\\/nuxtseo\\.com\\/test<\\/loc>[\\s\\S]*?<\\/url>/g)\n    expect(testUrlMatch).toBeDefined()\n\n    const testUrl = testUrlMatch![0]\n    // Verify it has the correct alternatives with custom paths\n    expect(testUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/prueba\" />')\n    expect(testUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/teste\" />')\n    expect(testUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/test\" />')\n\n    // Check the about URL entry\n    const aboutUrlMatch = enSitemap.match(/<url>[\\s\\S]*?<loc>https:\\/\\/nuxtseo\\.com\\/about<\\/loc>[\\s\\S]*?<\\/url>/g)\n    expect(aboutUrlMatch).toBeDefined()\n\n    const aboutUrl = aboutUrlMatch![0]\n    // Verify it has the correct alternatives with custom paths\n    expect(aboutUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/acerca-de\" />')\n    expect(aboutUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />')\n    expect(aboutUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/about\" />')\n  })\n\n  // Issue #542: dynamic route with parameters should apply custom path transformation\n  it('should apply custom paths to dynamic routes with single parameter', async () => {\n    const enSitemap = await $fetch('/__sitemap__/en-US.xml')\n    const esSitemap = await $fetch('/__sitemap__/es-ES.xml')\n    const frSitemap = await $fetch('/__sitemap__/fr-FR.xml')\n\n    // Test that /posts/my-slug with _i18nTransform generates custom paths with parameter substitution\n    expect(enSitemap).toContain('<loc>https://nuxtseo.com/posts/my-slug</loc>')\n    expect(esSitemap).toContain('<loc>https://nuxtseo.com/es/articulos/my-slug</loc>')\n    expect(frSitemap).toContain('<loc>https://nuxtseo.com/fr/article/my-slug</loc>')\n  })\n\n  it('should generate correct alternatives for dynamic routes with parameter', async () => {\n    const enSitemap = await $fetch('/__sitemap__/en-US.xml')\n\n    // Check the posts URL entry - should have parameter substitution in alternatives\n    const postsUrlMatch = enSitemap.match(/<url>[\\s\\S]*?<loc>https:\\/\\/nuxtseo\\.com\\/posts\\/my-slug<\\/loc>[\\s\\S]*?<\\/url>/g)\n    expect(postsUrlMatch).toBeDefined()\n\n    const postsUrl = postsUrlMatch![0]\n    expect(postsUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/articulos/my-slug\" />')\n    expect(postsUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/article/my-slug\" />')\n    expect(postsUrl).toContain('<xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/posts/my-slug\" />')\n  })\n\n  it('should apply custom paths to dynamic routes with multiple parameters', async () => {\n    const enSitemap = await $fetch('/__sitemap__/en-US.xml')\n    const esSitemap = await $fetch('/__sitemap__/es-ES.xml')\n    const frSitemap = await $fetch('/__sitemap__/fr-FR.xml')\n\n    // Test that /products/electronics/laptop-123 generates custom paths with both parameters\n    expect(enSitemap).toContain('<loc>https://nuxtseo.com/products/electronics/laptop-123</loc>')\n    expect(esSitemap).toContain('<loc>https://nuxtseo.com/es/productos/electronics/laptop-123</loc>')\n    expect(frSitemap).toContain('<loc>https://nuxtseo.com/fr/produits/electronics/laptop-123</loc>')\n  })\n})\n"
  },
  {
    "path": "test/e2e/i18n/custom-sitemaps-i18n.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\n// Test for issue #486: Automatic I18n Multi Sitemap + custom sitemaps not working\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  nuxtConfig: {\n    sitemap: {\n      sitemaps: {\n        pages: {\n          // This should be expanded to per-locale sitemaps (en-US, es-ES, fr-FR)\n          includeAppSources: true,\n          exclude: ['/secret/**'],\n        },\n        custom: {\n          // This should stay as a single sitemap\n          sources: ['/__sitemap'],\n        },\n      },\n    },\n  },\n})\n\ndescribe('i18n with custom sitemaps (#486)', () => {\n  it('generates sitemap index with locale-prefixed sitemaps and custom sitemap', async () => {\n    const index = await $fetch('/sitemap_index.xml')\n\n    // Should have locale-prefixed sitemaps: {locale}-{name} format\n    expect(index).toContain('en-US-pages.xml')\n    expect(index).toContain('es-ES-pages.xml')\n    expect(index).toContain('fr-FR-pages.xml')\n    expect(index).toContain('custom.xml')\n\n    // Should NOT have unprefixed \"pages\" or plain locale sitemaps\n    expect(index).not.toMatch(/\\/pages\\.xml/)\n    expect(index).not.toMatch(/\\/en-US\\.xml[^-]/)\n  })\n\n  it('locale sitemap inherits exclude config from custom sitemap', async () => {\n    const enSitemap = await $fetch('/__sitemap__/en-US-pages.xml')\n\n    // Should have normal pages\n    expect(enSitemap).toContain('/en')\n\n    // The exclude pattern should be applied (no /secret/** URLs)\n    expect(enSitemap).not.toContain('/secret')\n  })\n\n  it('custom sitemap without includeAppSources stays separate', async () => {\n    const customSitemap = await $fetch('/__sitemap__/custom.xml')\n\n    // Should have content from the source\n    expect(customSitemap).toContain('urlset')\n  })\n\n  it('locale sitemaps have proper i18n alternatives', async () => {\n    const frSitemap = await $fetch('/__sitemap__/fr-FR-pages.xml')\n\n    // Should have French URLs with alternatives\n    expect(frSitemap).toContain('/fr')\n    expect(frSitemap).toContain('hreflang')\n    expect(frSitemap).toContain('x-default')\n  })\n}, 60000)\n"
  },
  {
    "path": "test/e2e/i18n/domains.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      differentDomains: true,\n      locales: [\n        {\n          code: 'en',\n          iso: 'en-US',\n          domain: 'nuxtseo.com',\n        },\n        {\n          code: 'es',\n          iso: 'es-ES',\n          domain: 'es.nuxtseo.com',\n        },\n        {\n          code: 'fr',\n          iso: 'fr-FR',\n          domain: 'fr.nuxtseo.com',\n        },\n      ],\n    },\n    sitemap: {\n    },\n  },\n})\ndescribe('i18n domains', () => {\n  it('basic', async () => {\n    const index = await $fetch('/sitemap.xml')\n\n    expect(index).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/en-US.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/es-ES.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/fr-FR.xml</loc>\n          </sitemap>\n      </sitemapindex>\"\n    `)\n\n    const fr = await $fetch('/__sitemap__/fr-FR.xml')\n    expect(fr).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://fr.nuxtseo.com/fr</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://es.nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://fr.nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://fr.nuxtseo.com/fr/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://es.nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://fr.nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/dynamic-urls.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  nuxtConfig: {\n    i18n: {\n      strategy: 'prefix_except_default',\n      locales: [\n        { code: 'en', iso: 'en-US' },\n        { code: 'fr', iso: 'fr-FR' },\n      ],\n    },\n    sitemap: {\n      excludeAppSources: true,\n      sources: [\n        '/i18n-urls',\n      ],\n    },\n  },\n})\ndescribe('i18n dynamic urls', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/__sitemap__/en-US.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/endless-dungeon</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/endless-dungeon\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/endless-dungeon\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/endless-dungeon\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/endless-dungeon\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/english-url</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/english-url\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/english-url\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://www.somedomain.com/abc/def</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/dynamic/foo</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/en/dynamic/foo\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/en/dynamic/foo\" hreflang=\"en-US\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/fr/dynamic/foo\" hreflang=\"fr-FR\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/filtering-base-url.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  nuxtConfig: {\n    app: {\n      baseURL: '/base',\n    },\n    sitemap: {\n      exclude: [\n        '/test',\n      ],\n    },\n  },\n})\n\ndescribe('i18n filtering with base url', () => {\n  it('excludes /test', async () => {\n    let sitemap = await $fetch('/base/__sitemap__/en-US.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).not.toContain('/base/en/test')\n    expect(sitemap).not.toContain('/base/test')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/filtering-include.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\n// With i18n + includeAppSources, sitemaps are automatically expanded to per-locale sitemaps\n// The include filter is applied to each locale sitemap\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  nuxtConfig: {\n    sitemap: {\n      sitemaps: {\n        main: {\n          includeAppSources: true,\n          include: ['/', '/test'],\n        },\n      },\n    },\n  },\n})\ndescribe('i18n filtering with include', () => {\n  it('generates per-locale sitemaps with include filter applied', async () => {\n    // With the fix for #486, includeAppSources sitemaps are expanded to {locale}-{name} sitemaps\n    const index = await $fetch('/sitemap_index.xml')\n    expect(index).toContain('en-US-main.xml')\n    expect(index).toContain('fr-FR-main.xml')\n    expect(index).toContain('es-ES-main.xml')\n    // main.xml should NOT exist - it's expanded to locale sitemaps\n    expect(index).not.toContain('/main.xml')\n\n    // English sitemap should have filtered URLs with alternatives\n    const enSitemap = await $fetch('/__sitemap__/en-US-main.xml')\n    expect(enSitemap).toContain('/en')\n    expect(enSitemap).toContain('/en/test')\n    expect(enSitemap).toContain('hreflang')\n    expect(enSitemap).toContain('x-default')\n\n    // French sitemap should have filtered URLs with alternatives\n    const frSitemap = await $fetch('/__sitemap__/fr-FR-main.xml')\n    expect(frSitemap).toContain('/fr')\n    expect(frSitemap).toContain('/fr/test')\n    expect(frSitemap).toContain('hreflang')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/filtering-regexp.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  nuxtConfig: {\n    sitemap: {\n      exclude: [\n        /.*test.*/g,\n        /.no-i18n/,\n        '/en/__sitemap/**',\n        '/__sitemap/**',\n        // exclude fr\n        '/fr',\n      ],\n    },\n  },\n})\ndescribe('i18n filtering with regexp', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/__sitemap__/en-US.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/en</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/filtering.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  nuxtConfig: {\n    sitemap: {\n      exclude: [\n        '/test',\n      ],\n    },\n  },\n})\ndescribe('i18n filtering', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/__sitemap__/en-US.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/en</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/no-i18n</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/generate-prefix-except-default.test.ts",
    "content": "import { readFile } from 'node:fs/promises'\nimport { buildNuxt, createResolver, loadNuxt } from '@nuxt/kit'\nimport { describe, expect, it } from 'vitest'\n\ndescribe('generate prefix_except_default', () => {\n  it('root path should have all alternatives when prerendered', async () => {\n    process.env.NODE_ENV = 'production'\n    // @ts-expect-error untyped\n    process.env.prerender = true\n    process.env.NITRO_PRESET = 'static'\n    process.env.NUXT_PUBLIC_SITE_URL = 'https://nuxtseo.com'\n    const { resolve } = createResolver(import.meta.url)\n    const rootDir = resolve('../../fixtures/i18n-generate')\n    const nuxt = await loadNuxt({\n      rootDir,\n      overrides: {\n        _generate: true,\n        nitro: {\n          preset: 'static',\n        },\n      },\n    })\n\n    await buildNuxt(nuxt)\n\n    await new Promise(resolve => setTimeout(resolve, 1000))\n\n    // Multi-sitemap mode creates per-locale sitemaps\n    const sitemap = (await readFile(resolve(rootDir, '.output/public/__sitemap__/en-US.xml'), 'utf-8'))\n      .replace(/lastmod>(.*?)</g, 'lastmod><')\n\n    // Check root path has all alternatives\n    // With prefix_except_default: / is en (default), /de is de\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/</loc>')\n\n    // Root path should have en-US alternate pointing to /\n    expect(sitemap).toContain('hreflang=\"en-US\"')\n    expect(sitemap).toContain('href=\"https://nuxtseo.com/\"')\n\n    // Root path should have de-DE alternate\n    expect(sitemap).toContain('hreflang=\"de-DE\"')\n    expect(sitemap).toContain('href=\"https://nuxtseo.com/de\"')\n\n    // Root path should have x-default alternate pointing to /\n    expect(sitemap).toContain('hreflang=\"x-default\"')\n  }, 120000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/generate.test.ts",
    "content": "import { readFile } from 'node:fs/promises'\nimport { buildNuxt, createResolver, loadNuxt } from '@nuxt/kit'\nimport { describe, expect, it } from 'vitest'\n\ndescribe('generate', () => {\n  it('basic', async () => {\n    process.env.NODE_ENV = 'production'\n    // @ts-expect-error untyped\n    process.env.prerender = true\n    process.env.NUXT_PUBLIC_SITE_URL = 'https://nuxtseo.com'\n    const { resolve } = createResolver(import.meta.url)\n    const rootDir = resolve('../../fixtures/i18n')\n    const nuxt = await loadNuxt({\n      rootDir,\n      overrides: {\n        _generate: true,\n        nitro: { static: true },\n      },\n    })\n\n    await buildNuxt(nuxt)\n\n    await new Promise(resolve => setTimeout(resolve, 1000))\n\n    const sitemap = (await readFile(resolve(rootDir, '.output/public/sitemap_index.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')\n    // ignore lastmod entries\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/en-US.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/es-ES.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/fr-FR.xml</loc>\n          </sitemap>\n      </sitemapindex>\"\n    `)\n    const sitemapEn = (await readFile(resolve(rootDir, '.output/public/__sitemap__/en-US.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')\n    expect(sitemapEn).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/en</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/no-i18n</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 1200000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/no-prefix.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      locales: [\n        'en',\n        'fr',\n      ],\n      strategy: 'no_prefix',\n    },\n    sitemap: {\n      urls: ['/extra'],\n      sitemaps: false,\n    },\n  },\n})\ndescribe('i18n prefix', () => {\n  it('basic', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/extra</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/no-i18n</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/test</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/pages-multi.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      locales: [\n        {\n          code: 'en',\n          iso: 'en-US',\n        },\n        {\n          code: 'es',\n          iso: 'es-ES',\n        },\n        {\n          code: 'fr',\n          iso: 'fr-FR',\n        },\n      ],\n      pages: {\n        'about': {\n          en: '/about',\n          fr: '/a-propos',\n        },\n        'services/index': {\n          en: '/services',\n          fr: '/offres',\n        },\n        'services/development/index': {\n          en: '/services/development',\n          fr: '/offres/developement',\n        },\n        'services/development/app/index': {\n          en: '/services/development/app',\n          fr: '/offres/developement/app',\n        },\n        'services/development/website/index': {\n          en: '/services/development/website',\n          fr: '/offres/developement/site-web',\n        },\n        'services/coaching/index': {\n          en: '/services/coaching',\n          fr: '/offres/formation',\n        },\n        'random': {\n          en: '/random',\n          fr: false,\n        },\n      },\n    },\n  },\n})\ndescribe('i18n pages multi', () => {\n  it('basic', async () => {\n    const index = await $fetch('/sitemap_index.xml')\n    expect(index).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/en-US.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/es-ES.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/fr-FR.xml</loc>\n          </sitemap>\n      </sitemapindex>\"\n    `)\n    const fr = await $fetch('/__sitemap__/fr-FR.xml')\n    expect(fr).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/fr/a-propos</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/formation</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/site-web</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/website\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/pages.disabled-routes.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      baseUrl: 'https://i18n-locale-test.com',\n      locales: [\n        { code: 'en', iso: 'en-US', name: 'English' },\n        { code: 'fr', iso: 'fr-FR', name: 'Français' },\n      ],\n      defaultLocale: 'en',\n      strategy: 'no_prefix',\n      pages: {\n        '/about': {\n          en: '/about',\n          fr: false, // Disabled route\n        },\n        '/contact': {\n          en: '/contact',\n          fr: '/contact',\n        },\n      },\n    },\n  },\n})\n\ndescribe('i18n pages with disabled routes', () => {\n  it('handles disabled routes properly with no_prefix strategy', async () => {\n    const xml = await $fetch('/sitemap.xml')\n\n    // Should not throw error and contain urlset\n    expect(xml).toContain('<urlset')\n\n    // Should only have URLs for enabled routes\n    expect(xml).toContain('<loc>https://i18n-locale-test.com/about</loc>')\n    expect(xml).toContain('<loc>https://i18n-locale-test.com/contact</loc>')\n\n    // Disabled routes should not have alternatives pointing to them\n    expect(xml).not.toContain('/fr/about')\n\n    // Alternatives should only include enabled routes\n    const urlPattern = /<url>(.*?)<\\/url>/gs\n    const urls = xml.match(urlPattern) || []\n\n    // Find the about URL\n    const aboutUrl = urls.find((url: string) => url.includes('/about</loc>'))\n    if (aboutUrl) {\n      // There should be only one alternate link for the English version\n      const alternateLinks = aboutUrl.match(/<xhtml:link[^>]*\\/>/g) || []\n      expect(alternateLinks.length).toBeLessThanOrEqual(2) // at most en-US and x-default\n      expect(aboutUrl).not.toContain('hreflang=\"fr-FR\"') // French alternative should not exist\n    }\n  })\n})\n"
  },
  {
    "path": "test/e2e/i18n/pages.no-prefix.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    sitemap: { sitemaps: false },\n    i18n: {\n      strategy: 'no_prefix',\n      locales: [\n        'en',\n        'fr',\n      ],\n      pages: {\n        'about': {\n          en: '/about',\n          fr: '/a-propos',\n        },\n        'services/index': {\n          en: '/services',\n          fr: '/offres',\n        },\n        'services/development/index': {\n          en: '/services/development',\n          fr: '/offres/developement',\n        },\n        'services/development/app/index': {\n          en: '/services/development/app',\n          fr: '/offres/developement/app',\n        },\n        'services/development/website/index': {\n          en: '/services/development/website',\n          fr: '/offres/developement/site-web',\n        },\n        'services/coaching/index': {\n          en: '/services/coaching',\n          fr: '/offres/formation',\n        },\n      },\n    },\n  },\n})\ndescribe('i18n pages with no prefix strategy', () => {\n  it('no_prefix', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/a-propos</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/about</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/offres</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/offres/developement</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/offres/formation</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/coaching</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/offres/developement/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/offres/developement/site-web</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/website\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development/website</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/website\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/pages.only-locales.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      locales: [\n        {\n          code: 'en',\n          iso: 'en-US',\n        },\n        {\n          code: 'es',\n          iso: 'es-ES',\n        },\n        {\n          code: 'fr',\n          iso: 'fr-FR',\n        },\n      ],\n      bundle: {\n        onlyLocales: ['en', 'fr'],\n      },\n      pages: {\n        'about': {\n          en: '/about',\n          fr: '/a-propos',\n        },\n        'services/index': {\n          en: '/services',\n          fr: '/offres',\n        },\n        'services/development/index': {\n          en: '/services/development',\n          fr: '/offres/developement',\n        },\n        'services/development/app/index': {\n          en: '/services/development/app',\n          fr: '/offres/developement/app',\n        },\n        'services/development/website/index': {\n          en: '/services/development/website',\n          fr: '/offres/developement/site-web',\n        },\n        'services/coaching/index': {\n          en: '/services/coaching',\n          fr: '/offres/formation',\n        },\n        'random': {\n          en: '/random',\n          fr: false,\n        },\n      },\n    },\n  },\n})\ndescribe('i18n pages only locale', () => {\n  it('basic', async () => {\n    const index = await $fetch('/sitemap_index.xml')\n    expect(index).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/en-US.xml</loc>\n          </sitemap>\n          <sitemap>\n              <loc>https://nuxtseo.com/__sitemap__/fr-FR.xml</loc>\n          </sitemap>\n      </sitemapindex>\"\n    `)\n    const fr = await $fetch('/__sitemap__/fr-FR.xml')\n    expect(fr).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/fr/a-propos</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/formation</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/site-web</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/website\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/pages.prefix-and-default.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    sitemap: { sitemaps: false },\n    i18n: {\n      strategy: 'prefix_and_default',\n      defaultLocale: 'en',\n      locales: [\n        'en',\n        'fr',\n      ],\n      pages: {\n        'about': {\n          en: '/about',\n          fr: '/a-propos',\n        },\n        'services/index': {\n          en: '/services',\n          fr: '/offres',\n        },\n        'services/development/index': {\n          en: '/services/development',\n          fr: '/offres/developement',\n        },\n        'services/development/app/index': {\n          en: '/services/development/app',\n          fr: '/offres/developement/app',\n        },\n        'services/development/website/index': {\n          en: '/services/development/website',\n          fr: '/offres/developement/site-web',\n        },\n        'services/coaching/index': {\n          en: '/services/coaching',\n          fr: '/offres/formation',\n        },\n      },\n    },\n  },\n})\ndescribe('i18n pages with prefix and default strategy', () => {\n  it('prefix_and_default', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/about</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/about</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/a-propos</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/coaching</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/coaching</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/formation</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development/website</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/website\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development/website</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/website\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/site-web</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/website\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/pages.prefix-except-default.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    sitemap: { sitemaps: false },\n    i18n: {\n      strategy: 'prefix_except_default',\n      defaultLocale: 'en',\n      locales: [\n        'en',\n        'fr',\n      ],\n      pages: {\n        'about': {\n          en: '/about',\n          fr: '/a-propos',\n        },\n        'services/index': {\n          en: '/services',\n          fr: '/offres',\n        },\n        'services/development/index': {\n          en: '/services/development',\n          fr: '/offres/developement',\n        },\n        'services/development/app/index': {\n          en: '/services/development/app',\n          fr: '/offres/developement/app',\n        },\n        'services/development/website/index': {\n          en: '/services/development/website',\n          fr: '/offres/developement/site-web',\n        },\n        'services/coaching/index': {\n          en: '/services/coaching',\n          fr: '/offres/formation',\n        },\n      },\n    },\n  },\n})\ndescribe('i18n pages with prefix except default strategy', () => {\n  it('prefix_except_default', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/about</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/a-propos</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/coaching</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/formation</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/services/development/website</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/website\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/site-web</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/services/development/website\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/pages.prefix.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    sitemap: { sitemaps: false },\n    i18n: {\n      strategy: 'prefix',\n      locales: ['en', 'fr'],\n      pages: {\n        'about': {\n          en: '/about',\n          fr: '/a-propos',\n        },\n        'services/index': {\n          en: '/services',\n          fr: '/offres',\n        },\n        'services/development/index': {\n          en: '/services/development',\n          fr: '/offres/developement',\n        },\n        'services/development/app/index': {\n          en: '/services/development/app',\n          fr: '/offres/developement/app',\n        },\n        'services/development/website/index': {\n          en: '/services/development/website',\n          fr: '/offres/developement/site-web',\n        },\n        'services/coaching/index': {\n          en: '/services/coaching',\n          fr: '/offres/formation',\n        },\n      },\n    },\n  },\n})\ndescribe('i18n pages with prefix strategy', () => {\n  it('prefix', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/en/about</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/a-propos</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/coaching</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/formation</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development/website</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/website\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/site-web</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/website\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/pages.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    sitemap: { sitemaps: false },\n    i18n: {\n      locales: [\n        'en',\n        'fr',\n      ],\n      pages: {\n        'about': {\n          en: '/about',\n          fr: '/a-propos',\n        },\n        'services/index': {\n          en: '/services',\n          fr: '/offres',\n        },\n        'services/development/index': {\n          en: '/services/development',\n          fr: '/offres/developement',\n        },\n        'services/development/app/index': {\n          en: '/services/development/app',\n          fr: '/offres/developement/app',\n        },\n        'services/development/website/index': {\n          en: '/services/development/website',\n          fr: '/offres/developement/site-web',\n        },\n        'services/coaching/index': {\n          en: '/services/coaching',\n          fr: '/offres/formation',\n        },\n      },\n    },\n  },\n})\ndescribe('i18n', () => {\n  it('basic', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/en/about</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/a-propos</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/about\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/a-propos\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/about\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/coaching</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/formation</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/coaching\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/formation\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/coaching\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/services/development/website</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/website\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/app</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/app\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/app\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/offres/developement/site-web</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/services/development/website\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/offres/developement/site-web\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/services/development/website\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/prefix-and-default.test.ts",
    "content": "import type { SitemapUrlInput } from '../../../src/runtime/types'\nimport { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      locales: [\n        'en',\n        'fr',\n      ],\n      strategy: 'prefix_and_default',\n    },\n    sitemap: {\n      autoI18n: true,\n      urls: [\n        <SitemapUrlInput> {\n          loc: '/extra',\n          _i18nTransform: true,\n        },\n      ],\n      sitemaps: false,\n    },\n  },\n})\ndescribe('i18n prefix and default', () => {\n  it('basic', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/extra</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/extra\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/no-i18n</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/extra</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/extra\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/extra</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/extra\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/prefix-except-default.test.ts",
    "content": "import type { SitemapUrlInput } from '../../../src/runtime/types'\nimport { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      locales: [\n        'en',\n        'fr',\n      ],\n      strategy: 'prefix_except_default',\n    },\n    sitemap: {\n      autoI18n: true,\n      urls: [\n        <SitemapUrlInput> {\n          loc: '/extra',\n          _i18nTransform: true,\n        },\n      ],\n      sitemaps: false,\n    },\n  },\n})\ndescribe('i18n prefix except default', () => {\n  it('basic', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/extra</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/extra\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/no-i18n</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/extra</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/extra\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/extra</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/extra\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/extra\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/prefix-iso.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      locales: [\n        {\n          code: 'fr',\n          iso: 'fr-FR',\n        },\n        {\n          code: 'en',\n          iso: 'en-US',\n        },\n      ],\n      strategy: 'prefix',\n    },\n    sitemap: {\n      autoI18n: true,\n      urls: ['/extra'],\n      sitemaps: false,\n    },\n  },\n})\ndescribe('i18n prefix', () => {\n  it('basic', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/en</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/extra</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/extra\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/extra\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/no-i18n</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/prefix-simple.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    i18n: {\n      locales: [\n        'en',\n        'fr',\n      ],\n      strategy: 'prefix',\n    },\n    sitemap: {\n      urls: ['/extra'],\n      sitemaps: false,\n    },\n  },\n})\ndescribe('i18n prefix', () => {\n  it('basic', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/en</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/extra</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/extra\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/extra\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/no-i18n</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/test</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/route-rules.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  nuxtConfig: {\n    i18n: {\n      strategy: 'prefix_except_default',\n      locales: [\n        { code: 'en', iso: 'en-US' },\n        { code: 'fr', iso: 'fr-FR' },\n      ],\n    },\n    sitemap: {\n      excludeAppSources: true,\n      sitemaps: false,\n      urls: [\n        // simply matching\n        '/hidden',\n        '/defaults',\n        '/wildcard/defaults/foo',\n        '/wildcard/hidden/foo',\n        // i18n matching, should inherit the top level rules (without the locale)\n        '/fr/hidden',\n        '/fr/defaults',\n        '/fr/wildcard/defaults/foo',\n        '/fr/wildcard/hidden/foo',\n      ],\n    },\n    routeRules: {\n      '/hidden': {\n        // @ts-expect-error untyped\n        robots: false,\n      },\n      '/defaults': {\n        sitemap: {\n          changefreq: 'daily',\n          priority: 1,\n        },\n      },\n      '/wildcard/defaults/**': {\n        sitemap: {\n          changefreq: 'daily',\n          priority: 1,\n        },\n      },\n      '/wildcard/hidden/**': {\n        // @ts-expect-error untyped\n        robots: false,\n      },\n    },\n  },\n})\ndescribe('i18n route rules', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/sitemap.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/defaults</loc>\n              <changefreq>daily</changefreq>\n              <priority>1.0</priority>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/defaults\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/defaults\" hreflang=\"en-US\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/fr/defaults\" hreflang=\"fr-FR\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/defaults</loc>\n              <changefreq>daily</changefreq>\n              <priority>1.0</priority>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/defaults\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/defaults\" hreflang=\"en-US\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/fr/defaults\" hreflang=\"fr-FR\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/wildcard/defaults/foo</loc>\n              <changefreq>daily</changefreq>\n              <priority>1.0</priority>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/wildcard/defaults/foo\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/wildcard/defaults/foo\" hreflang=\"en-US\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/fr/wildcard/defaults/foo\" hreflang=\"fr-FR\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/wildcard/defaults/foo</loc>\n              <changefreq>daily</changefreq>\n              <priority>1.0</priority>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/wildcard/defaults/foo\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/wildcard/defaults/foo\" hreflang=\"en-US\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/fr/wildcard/defaults/foo\" hreflang=\"fr-FR\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/i18n/simple-trailing.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/i18n'),\n  build: true,\n  server: true,\n  nuxtConfig: {\n    site: {\n      trailingSlash: true,\n    },\n    i18n: {\n      locales: [\n        'en',\n        'fr',\n      ],\n      trailingSlash: true,\n    },\n    sitemap: {\n      urls: ['/extra'],\n      sitemaps: false,\n    },\n  },\n})\ndescribe('i18n prefix', () => {\n  it('basic', async () => {\n    const posts = await $fetch('/sitemap.xml')\n\n    expect(posts).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/en/</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/extra/</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/extra/\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/extra/\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/no-i18n/</loc>\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n/\" hreflang=\"x-default\" />\n              <xhtml:link rel=\"alternate\" href=\"https://nuxtseo.com/no-i18n/\" hreflang=\"en-US\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/test/</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/test/</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/test/</loc>\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/test/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/test/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/en/__sitemap/url/</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/es/__sitemap/url/</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url/\" />\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/fr/__sitemap/url/</loc>\n              <changefreq>weekly</changefreq>\n              <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://nuxtseo.com/en/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"en-US\" href=\"https://nuxtseo.com/en/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"es-ES\" href=\"https://nuxtseo.com/es/__sitemap/url/\" />\n              <xhtml:link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://nuxtseo.com/fr/__sitemap/url/\" />\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/issues/504-duplicate-api-calls.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/issue-504'),\n  server: true,\n})\n\ndescribe('issue #504 - duplicate API calls with includeAppSources', () => {\n  it('should only call API source once per sitemap request', async () => {\n    // Get initial count before first request\n    const initial = await $fetch<{ count: number }>('/api/__sitemap__/call-count')\n    const startCount = initial.count\n\n    // First request to sitemap - should only increment by 1\n    await $fetch('/test.xml')\n    const after1 = await $fetch<{ count: number }>('/api/__sitemap__/call-count')\n    expect(after1.count - startCount).toBe(1)\n\n    // Second request to sitemap - should only increment by 1, not N+1\n    await $fetch('/test.xml')\n    const after2 = await $fetch<{ count: number }>('/api/__sitemap__/call-count')\n    expect(after2.count - after1.count).toBe(1)\n\n    // Third request to sitemap - should only increment by 1, not N+1\n    await $fetch('/test.xml')\n    const after3 = await $fetch<{ count: number }>('/api/__sitemap__/call-count')\n    expect(after3.count - after2.count).toBe(1)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/issues/issue-384.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/issue-384'),\n})\n\ndescribe('issue #384 - sitemap with robots disallow /', () => {\n  it('should still generate sitemap URLs when robots disallows everything', async () => {\n    // robots.txt disallow should NOT prevent URLs from appearing in sitemaps\n    const sitemap = await $fetch('/sitemap.xml')\n    expect(sitemap).toContain('<loc>')\n    expect(sitemap).toContain('/about')\n  }, 60000)\n\n  it('should block crawlers from the sitemap via X-Robots-Tag', async () => {\n    const res = await fetch('/sitemap.xml')\n    expect(res.headers.get('x-robots-tag')).toBe('noindex, nofollow')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/issues/issue-561.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/issue-561'),\n  server: true,\n  dev: false,\n})\n\ndescribe('issue #561 - autoI18n: false generates empty sitemap', () => {\n  it('should generate a single sitemap.xml (not redirect to sitemap_index)', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    // should be a sitemap, not a redirect to sitemap_index\n    expect(sitemap).toContain('<urlset')\n    expect(sitemap).not.toContain('sitemap_index')\n  }, 60000)\n\n  it('should contain all locale routes without hreflang alternates', async () => {\n    let sitemap = await $fetch('/sitemap.xml')\n\n    // strip lastmod for cleaner assertions\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    // should contain URL entries - not empty\n    expect(sitemap).toContain('<url>')\n    expect(sitemap).toContain('<loc>')\n\n    // should contain the homepage and locale variants\n    expect(sitemap).toContain('https://example.com/')\n    expect(sitemap).toContain('/en')\n\n    // should contain custom i18n page routes\n    expect(sitemap).toContain('/envoyer-tableau')\n    expect(sitemap).toContain('/en/submit-art')\n    expect(sitemap).toContain('/politique-de-confidentialite')\n    expect(sitemap).toContain('/en/privacy-policy')\n\n    // autoI18n: false should suppress hreflang alternatives (#586)\n    expect(sitemap).not.toContain('xhtml:link')\n    expect(sitemap).not.toContain('hreflang')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/issues/issue-564.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    app: {\n      baseURL: '/test',\n    },\n    sitemap: {\n      sitemaps: true,\n    },\n  },\n})\n\ndescribe('issue 564 - base URL in sitemap redirect with multi sitemaps', () => {\n  it('redirects /test/sitemap.xml to /test/sitemap_index.xml', async () => {\n    const response = await fetch('/test/sitemap.xml', { redirect: 'manual' })\n    expect(response.status).toBe(307)\n    const location = response.headers.get('location')\n    expect(location).toContain('/test/sitemap_index.xml')\n    expect(location).not.toBe('/sitemap_index.xml')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/issues/issue-588.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/issue-588'),\n  server: true,\n  dev: false,\n})\n\ndescribe('issue #588 - useHead hreflang should not leak into sitemap when autoI18n: false', () => {\n  it('should not contain hreflang alternates from useHead()', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    // should contain all pages\n    expect(sitemap).toContain('https://example.com/')\n    expect(sitemap).toContain('https://example.com/about')\n    expect(sitemap).toContain('https://example.com/contact')\n\n    // autoI18n: false should suppress hreflang alternatives even when added via useHead()\n    expect(sitemap).not.toContain('xhtml:link')\n    expect(sitemap).not.toContain('hreflang')\n    expect(sitemap).not.toContain('example.de')\n    expect(sitemap).not.toContain('example.fr')\n    expect(sitemap).not.toContain('example.it')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/multi/cache-filesystem.test.ts",
    "content": "import fs from 'node:fs'\nimport path from 'node:path'\nimport { createResolver } from '@nuxt/kit'\nimport { fetch, setup } from '@nuxt/test-utils'\nimport { afterAll, beforeAll, describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\n// Create a temporary directory for cache storage\nconst cacheDir = resolve('../../fixtures/.cache-test')\n\n// Ensure cache directory exists\nbeforeAll(() => {\n  if (!fs.existsSync(cacheDir)) {\n    fs.mkdirSync(cacheDir, { recursive: true })\n  }\n})\n\n// Clean up cache directory after tests\nafterAll(() => {\n  if (fs.existsSync(cacheDir)) {\n    fs.rmSync(cacheDir, { recursive: true, force: true })\n  }\n})\n\n// Basic multi-sitemap fixture with filesystem cache\nawait setup({\n  rootDir: resolve('../../fixtures/multi-with-chunks'),\n  dev: false, // Run in production mode to enable caching\n  nuxtConfig: {\n    sitemap: {\n      sitemaps: {\n        pages: {\n          includeAppSources: true,\n        },\n        posts: {\n          includeAppSources: false,\n          urls: [\n            { url: '/post-1' },\n            { url: '/post-2' },\n          ],\n        },\n      },\n      cacheMaxAgeSeconds: 600, // 10 minutes\n      runtimeCacheStorage: {\n        driver: 'fs',\n        base: cacheDir,\n      },\n    },\n  },\n})\n\ndescribe('multi-sitemap filesystem caching', () => {\n  it('should cache sitemap files to filesystem', async () => {\n    // Clear cache directory\n    const files = fs.readdirSync(cacheDir)\n    for (const file of files) {\n      fs.rmSync(path.join(cacheDir, file), { recursive: true, force: true })\n    }\n\n    // First request - should create cache files\n    const response1 = await fetch('/sitemap_index.xml')\n    expect(response1.status).toBe(200)\n\n    // Give it a moment to write to filesystem\n    await new Promise(resolve => setTimeout(resolve, 500))\n\n    // Check that cache files were created\n    const cacheFiles = fs.readdirSync(cacheDir)\n    expect(cacheFiles.length).toBeGreaterThan(0)\n\n    // Should have the sitemap group directory\n    const sitemapCacheDir = path.join(cacheDir, 'sitemap')\n    expect(fs.existsSync(sitemapCacheDir)).toBe(true)\n\n    // Check for specific cache files\n    const sitemapFiles = fs.readdirSync(sitemapCacheDir)\n\n    // We should have cache files with keys based on our sitemap structure\n    const hasCacheFiles = sitemapFiles.length > 0\n    expect(hasCacheFiles).toBe(true)\n\n    // Second request - should hit cache\n    const response2 = await fetch('/sitemap_index.xml')\n    expect(response2.status).toBe(200)\n\n    // Content should be the same\n    const content1 = await response1.text()\n    const content2 = await response2.text()\n    expect(content1).toBe(content2)\n  })\n\n  it('should cache individual sitemap files', async () => {\n    // Request individual sitemap\n    const response = await fetch('/__sitemap__/pages.xml')\n    expect(response.status).toBe(200)\n\n    // Give it a moment to write to filesystem\n    await new Promise(resolve => setTimeout(resolve, 1000))\n\n    // Check cache structure\n    const cacheFiles = fs.readdirSync(cacheDir)\n\n    const sitemapCacheDir = path.join(cacheDir, 'sitemap')\n    if (fs.existsSync(sitemapCacheDir)) {\n      const sitemapFiles = fs.readdirSync(sitemapCacheDir)\n\n      // The cache structure seems to be different, let's check if we have more files after the request\n      expect(sitemapFiles.length).toBeGreaterThan(0)\n    }\n    else {\n      // Cache might be at the root level\n      const hasSitemapCache = cacheFiles.some(file => file.includes('sitemap'))\n      expect(hasSitemapCache).toBe(true)\n    }\n  })\n\n  it('should respect cache expiration', async () => {\n    // Note: This test is conceptual - we can't easily test actual expiration\n    // without mocking time or waiting for the cache to expire\n\n    // Request a sitemap\n    const response = await fetch('/__sitemap__/posts.xml')\n    expect(response.status).toBe(200)\n\n    // Check that cache headers indicate proper expiration\n    const cacheControl = response.headers.get('cache-control')\n    expect(cacheControl).toContain('max-age=600')\n    expect(cacheControl).toContain('s-maxage=600')\n\n    // Debug headers should show expiration info\n    expect(response.headers.get('X-Sitemap-Cache-Duration')).toBe('600s')\n    expect(response.headers.get('X-Sitemap-Cache-Expires')).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "test/e2e/multi/cache-swr.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { fetch, setup } from '@nuxt/test-utils'\nimport { isCI } from 'std-env'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\n// Set up with SWR enabled and very short cache time\nawait setup({\n  rootDir: resolve('../../fixtures/multi-with-chunks'),\n  dev: false, // Run in production mode to enable caching\n  nuxtConfig: {\n    sitemap: {\n      sitemaps: {\n        pages: {\n          includeAppSources: true,\n        },\n        posts: {\n          includeAppSources: false,\n          urls: [\n            { url: '/post-1' },\n            { url: '/post-2' },\n          ],\n        },\n      },\n      cacheMaxAgeSeconds: 2, // 2 seconds for fast testing\n      runtimeCacheStorage: {\n        driver: 'memory',\n      },\n    },\n  },\n})\n\ndescribe.skipIf(isCI)('multi-sitemap SWR behavior with cache expiration', () => {\n  it('should return SWR cache headers for sitemap index', async () => {\n    const response = await fetch('/sitemap_index.xml')\n\n    expect(response.headers.get('content-type')).toMatch(/xml/)\n\n    // Check cache headers - when SWR is enabled, we should see stale-while-revalidate directive\n    const cacheControl = response.headers.get('cache-control')\n    expect(cacheControl).toBeDefined()\n    expect(cacheControl).toContain('max-age=2')\n    expect(cacheControl).toContain('public')\n    expect(cacheControl).toContain('s-maxage=2')\n    expect(cacheControl).toContain('stale-while-revalidate=3600')\n\n    const xml = await response.text()\n    expect(xml).toContain('<sitemapindex')\n  })\n\n  it('should serve fresh content before cache expires', async () => {\n    // First request to populate cache\n    const response1 = await fetch('/__sitemap__/pages.xml')\n    const generated1 = response1.headers.get('X-Sitemap-Generated')\n    expect(generated1).toBeDefined()\n\n    // Immediate second request - should be from cache\n    const response2 = await fetch('/__sitemap__/pages.xml')\n    const generated2 = response2.headers.get('X-Sitemap-Generated')\n\n    // Timestamps should be very close (within 5ms) since it's cached\n    const time1 = new Date(generated1!).getTime()\n    const time2 = new Date(generated2!).getTime()\n    const diff = Math.abs(time2 - time1)\n    // TODO possibly this is broken\n    expect(diff).toBeLessThanOrEqual(7) // Allow up to 5ms difference for cached response\n\n    const xml = await response2.text()\n    expect(xml).toContain('<urlset')\n  })\n\n  it('should serve stale content after cache expires', async () => {\n    // First request to populate cache\n    const response1 = await fetch('/__sitemap__/posts.xml')\n    const generated1 = response1.headers.get('X-Sitemap-Generated')\n    const expires1 = response1.headers.get('X-Sitemap-Cache-Expires')\n    expect(generated1).toBeDefined()\n    expect(expires1).toBeDefined()\n\n    // Wait for cache to expire (3 seconds to be safe)\n    await new Promise(resolve => setTimeout(resolve, 3000))\n\n    // After expiration - should get new content with new timestamp\n    const response2 = await fetch('/__sitemap__/posts.xml')\n    const generated2 = response2.headers.get('X-Sitemap-Generated')\n    const expires2 = response2.headers.get('X-Sitemap-Cache-Expires')\n\n    // With SWR, we might get either stale or fresh content\n    // The key is that the response should be successful\n    expect(response2.status).toBe(200)\n\n    // Check that cache headers are still present\n    const cacheControl = response2.headers.get('cache-control')\n    expect(cacheControl).toContain('stale-while-revalidate')\n\n    // If we got fresh content, timestamps should be different\n    if (generated2 !== generated1) {\n      expect(expires2).not.toBe(expires1)\n    }\n\n    const xml = await response2.text()\n    expect(xml).toContain('/post-1')\n    expect(xml).toContain('/post-2')\n  }, 10000) // Increase timeout for this test\n\n  it('should update cache after expiration', async () => {\n    // Unique sitemap to avoid conflicts with other tests\n    const response1 = await fetch('/__sitemap__/products.xml')\n    const generated1 = response1.headers.get('X-Sitemap-Generated')\n\n    // Wait for cache to expire\n    await new Promise(resolve => setTimeout(resolve, 3000))\n\n    // Request after expiration\n    await fetch('/__sitemap__/products.xml')\n\n    // Give it a moment to update cache\n    await new Promise(resolve => setTimeout(resolve, 100))\n\n    // Third request should get the updated cache\n    const response3 = await fetch('/__sitemap__/products.xml')\n    const generated3 = response3.headers.get('X-Sitemap-Generated')\n\n    // First and third should be different (cache was updated)\n    expect(generated3).not.toBe(generated1)\n    // Second and third might be the same if second got fresh content\n    // or different if second got stale content\n\n    expect(response3.status).toBe(200)\n  }, 10000)\n\n  it('should verify debug headers show correct expiration', async () => {\n    const response = await fetch('/sitemap_index.xml')\n\n    // Check debug headers\n    const duration = response.headers.get('X-Sitemap-Cache-Duration')\n    const generated = response.headers.get('X-Sitemap-Generated')\n    const expires = response.headers.get('X-Sitemap-Cache-Expires')\n    const remaining = response.headers.get('X-Sitemap-Cache-Remaining')\n\n    expect(duration).toBe('2s')\n    expect(generated).toBeDefined()\n    expect(expires).toBeDefined()\n    expect(remaining).toBeDefined()\n\n    // Parse timestamps\n    const generatedTime = new Date(generated!).getTime()\n    const expiresTime = new Date(expires!).getTime()\n\n    // Expiration should be 2 seconds after generation (allow 1ms tolerance)\n    const diff = expiresTime - generatedTime\n    expect(diff).toBeGreaterThanOrEqual(1999) // Allow 1ms tolerance\n    expect(diff).toBeLessThanOrEqual(2001) // Allow 1ms tolerance\n\n    // Remaining should be a positive number less than or equal to 2\n    const remainingSeconds = Number.parseInt(remaining!.replace('s', ''))\n    expect(remainingSeconds).toBeLessThanOrEqual(2)\n    expect(remainingSeconds).toBeGreaterThanOrEqual(0)\n  })\n\n  it('should vary cache based on headers', async () => {\n    // First request with default headers\n    const response1 = await fetch('/sitemap_index.xml')\n    const generated1 = response1.headers.get('X-Sitemap-Generated')\n    expect(response1.status).toBe(200)\n    expect(generated1).toBeDefined()\n\n    // Wait for cache to expire plus buffer\n    await new Promise(resolve => setTimeout(resolve, 2500))\n\n    // Second request with different host header - should create new cache entry\n    const response2 = await fetch('/sitemap_index.xml', {\n      headers: {\n        Host: 'example.com',\n      },\n    })\n    const generated2 = response2.headers.get('X-Sitemap-Generated')\n    expect(response2.status).toBe(200)\n    expect(generated2).toBeDefined()\n\n    // If headers properly vary the cache, the timestamps can be different\n    // Note: In test environments, headers might not pass through correctly\n    // but we at least verify the responses are valid\n\n    // Third request with default headers again - within cache window\n    await new Promise(resolve => setTimeout(resolve, 100))\n    const response3 = await fetch('/sitemap_index.xml')\n    const generated3 = response3.headers.get('X-Sitemap-Generated')\n    expect(response3.status).toBe(200)\n    expect(generated3).toBeDefined()\n\n    // This should be from cache (either first or a fresh regeneration)\n    // We verify it's valid rather than checking exact match due to test environment\n    expect(new Date(generated3!).getTime()).toBeGreaterThan(0)\n\n    // Verify that different headers can generate different keys (if supported)\n    const response4 = await fetch('/sitemap_index.xml', {\n      headers: {\n        'X-Forwarded-Proto': 'http',\n      },\n    })\n    const generated4 = response4.headers.get('X-Sitemap-Generated')\n    expect(response4.status).toBe(200)\n    expect(generated4).toBeDefined()\n\n    // The cache key mechanism is implemented correctly\n    // but the test environment might not distinguish headers properly\n    // So we just verify all responses are successful\n  }, 5000)\n})\n"
  },
  {
    "path": "test/e2e/multi/chunking-edge-cases.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/multi-with-chunks'),\n  server: true,\n  nuxtConfig: {\n    hooks: {\n      'nitro:config': function (config) {\n        config.runtimeConfig ??= {}\n        config.runtimeConfig.public ??= {}\n        config.runtimeConfig.public.siteUrl = 'https://nuxtseo.com'\n      },\n    },\n  },\n})\n\ndescribe('chunking edge cases', () => {\n  describe('empty chunks', () => {\n    it('returns 404 for non-existent chunk', async () => {\n      // The posts sitemap has 12 posts with chunkSize: 3, so it should have chunks 0-3\n      // Chunk 4 should not exist\n      try {\n        await $fetch('/__sitemap__/posts-4.xml')\n        throw new Error('Should have thrown 404')\n      }\n      catch (error: any) {\n        expect(error.data?.statusCode || error.statusCode).toBe(404)\n      }\n    })\n\n    it('returns 404 for chunk of non-chunked sitemap', async () => {\n      // pages sitemap doesn't have chunking enabled\n      try {\n        await $fetch('/__sitemap__/pages-0.xml')\n        throw new Error('Should have thrown 404')\n      }\n      catch (error: any) {\n        expect(error.data?.statusCode || error.statusCode).toBe(404)\n      }\n    })\n  })\n\n  describe('chunk boundary validation', () => {\n    it('handles last valid chunk', async () => {\n      // posts has 12 items with chunkSize: 3, so chunk 3 (the 4th chunk) is the last valid one\n      const chunk = await $fetch('/__sitemap__/posts-3.xml')\n      expect(chunk).toContain('<urlset')\n      expect(chunk).toContain('<loc>https://nuxtseo.com/posts/10</loc>')\n      expect(chunk).toContain('<loc>https://nuxtseo.com/posts/11</loc>')\n      expect(chunk).toContain('<loc>https://nuxtseo.com/posts/12</loc>')\n    })\n\n    it('handles products chunk boundaries', async () => {\n      // products has 25 items with chunkSize: 10\n      // chunk 0: 1-10, chunk 1: 11-20, chunk 2: 21-25\n\n      const chunk2 = await $fetch('/__sitemap__/products-2.xml')\n      expect(chunk2).toContain('<urlset')\n      expect(chunk2).toContain('<loc>https://nuxtseo.com/products/21</loc>')\n      expect(chunk2).toContain('<loc>https://nuxtseo.com/products/25</loc>')\n\n      // chunk 3 should not exist\n      try {\n        await $fetch('/__sitemap__/products-3.xml')\n        throw new Error('Should have thrown 404')\n      }\n      catch (error: any) {\n        expect(error.data?.statusCode || error.statusCode).toBe(404)\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "test/e2e/multi/chunking.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/multi-with-chunks'),\n  server: true,\n  nuxtConfig: {\n    hooks: {\n      'nitro:config': function (config) {\n        config.runtimeConfig ??= {}\n        config.runtimeConfig.public ??= {}\n        config.runtimeConfig.public.siteUrl = 'https://nuxtseo.com'\n      },\n    },\n  },\n})\n\ndescribe('multi sitemaps with chunking', () => {\n  it('basic index', async () => {\n    const index = await $fetch('/sitemap_index.xml')\n\n    expect(index).toContain('<sitemapindex')\n    expect(index).toContain('<loc>https://nuxtseo.com/__sitemap__/pages.xml</loc>')\n\n    // Should have 4 chunks for posts (12 posts / 3 per chunk)\n    expect(index).toContain('<loc>https://nuxtseo.com/__sitemap__/posts-0.xml</loc>')\n    expect(index).toContain('<loc>https://nuxtseo.com/__sitemap__/posts-1.xml</loc>')\n    expect(index).toContain('<loc>https://nuxtseo.com/__sitemap__/posts-2.xml</loc>')\n    expect(index).toContain('<loc>https://nuxtseo.com/__sitemap__/posts-3.xml</loc>')\n\n    // Should have 3 chunks for products (25 products / 10 per chunk)\n    expect(index).toContain('<loc>https://nuxtseo.com/__sitemap__/products-0.xml</loc>')\n    expect(index).toContain('<loc>https://nuxtseo.com/__sitemap__/products-1.xml</loc>')\n    expect(index).toContain('<loc>https://nuxtseo.com/__sitemap__/products-2.xml</loc>')\n  })\n\n  // Debug test\n  it('posts sources', async () => {\n    const posts = await $fetch('/api/posts')\n    expect(posts).toHaveLength(12)\n    expect(posts[0]).toEqual({\n      loc: '/posts/1',\n      lastmod: expect.any(String),\n    })\n  })\n\n  it('posts chunk 0', async () => {\n    const chunk = await $fetch('/__sitemap__/posts-0.xml')\n\n    expect(chunk).toContain('<urlset')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/1</loc>')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/2</loc>')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/3</loc>')\n    expect(chunk).not.toContain('<loc>https://nuxtseo.com/posts/4</loc>')\n  })\n\n  it('posts chunk 1', async () => {\n    const chunk = await $fetch('/__sitemap__/posts-1.xml')\n\n    expect(chunk).toContain('<urlset')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/4</loc>')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/5</loc>')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/6</loc>')\n    expect(chunk).not.toContain('<loc>https://nuxtseo.com/posts/3</loc>')\n    expect(chunk).not.toContain('<loc>https://nuxtseo.com/posts/7</loc>')\n  })\n\n  it('posts chunk 3 (last)', async () => {\n    const chunk = await $fetch('/__sitemap__/posts-3.xml')\n\n    expect(chunk).toContain('<urlset')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/10</loc>')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/11</loc>')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/posts/12</loc>')\n    expect(chunk).not.toContain('<loc>https://nuxtseo.com/posts/9</loc>')\n  })\n\n  it('products chunk 0', async () => {\n    const chunk = await $fetch('/__sitemap__/products-0.xml')\n\n    expect(chunk).toContain('<urlset')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/products/1</loc>')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/products/10</loc>')\n    expect(chunk).not.toContain('<loc>https://nuxtseo.com/products/11</loc>')\n  })\n\n  it('products chunk 2 (last)', async () => {\n    const chunk = await $fetch('/__sitemap__/products-2.xml')\n\n    expect(chunk).toContain('<urlset')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/products/21</loc>')\n    expect(chunk).toContain('<loc>https://nuxtseo.com/products/25</loc>')\n    expect(chunk).not.toContain('<loc>https://nuxtseo.com/products/20</loc>')\n  })\n\n  it('non-chunked pages sitemap', async () => {\n    const pages = await $fetch('/__sitemap__/pages.xml')\n\n    expect(pages).toContain('<urlset')\n    expect(pages).toContain('<loc>https://nuxtseo.com/page/1</loc>')\n    expect(pages).toContain('<loc>https://nuxtseo.com/page/20</loc>')\n  })\n\n  it('404 for non-existent chunk', async () => {\n    // Should return 404 for chunks that don't exist\n    try {\n      await $fetch('/__sitemap__/posts-4.xml')\n      throw new Error('Should have thrown 404')\n    }\n    catch (error: any) {\n      expect(error.data?.statusCode || error.statusCode).toBe(404)\n    }\n  })\n\n  it('404 for non-existent chunked sitemap', async () => {\n    // Should return 404 for sitemap that doesn't support chunking\n    try {\n      await $fetch('/__sitemap__/pages-0.xml')\n      throw new Error('Should have thrown 404')\n    }\n    catch (error: any) {\n      expect(error.data?.statusCode || error.statusCode).toBe(404)\n    }\n  })\n})\n"
  },
  {
    "path": "test/e2e/multi/defaults.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      sitemaps: {\n        foo: {\n          include: ['/foo/*'],\n          urls: [\n            '/foo/1',\n            '/foo/2',\n          ],\n          defaults: {\n            changefreq: 'weekly',\n            priority: 0.7,\n          },\n        },\n        bar: {\n          urls: [\n            '/bar/1',\n            '/bar/2',\n          ],\n          defaults: {\n            changefreq: 'monthly',\n            priority: 0.5,\n          },\n        },\n      },\n    },\n  },\n})\ndescribe('mutli defaults', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/__sitemap__/foo.xml')\n    // remove lastmods before tresting\n    sitemap = sitemap.replace(/lastmod>(.*?)</g, 'lastmod><')\n    // basic test to make sure we get a valid response\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/foo/1</loc>\n              <changefreq>weekly</changefreq>\n              <priority>0.7</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/2</loc>\n              <changefreq>weekly</changefreq>\n              <priority>0.7</priority>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/multi/endpoints.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      sitemaps: {\n        foo: {\n          sources: ['/api/sitemap/foo'],\n          defaults: {\n            changefreq: 'weekly',\n            priority: 0.7,\n          },\n        },\n        bar: {\n          sources: ['/api/sitemap/bar'],\n        },\n      },\n    },\n  },\n})\ndescribe('multi endpoints', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/__sitemap__/foo.xml')\n    // remove lastmods before tresting\n    sitemap = sitemap.replace(/lastmod>(.*?)</g, 'lastmod><')\n    // basic test to make sure we get a valid response\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/foo/1</loc>\n              <changefreq>weekly</changefreq>\n              <priority>0.7</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/2</loc>\n              <changefreq>weekly</changefreq>\n              <priority>0.7</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/3</loc>\n              <changefreq>weekly</changefreq>\n              <priority>0.7</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/4</loc>\n              <changefreq>weekly</changefreq>\n              <priority>0.7</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo/5</loc>\n              <changefreq>weekly</changefreq>\n              <priority>0.7</priority>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/multi/filtering.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      sitemaps: {\n        foo: {\n          urls: [\n            // default blocked routes\n            '/_nuxt',\n            '/_nuxt/foo',\n            '/api',\n            '/api/foo',\n            '/api/foo/bar',\n            // custom blocked routes\n            '/admin',\n            '/admin/foo',\n            '/admin/foo/bar',\n            // should be only route\n            '/valid',\n          ],\n          exclude: [\n            '/api',\n            '/api/**',\n            '/admin/**',\n          ],\n        },\n      },\n    },\n  },\n})\ndescribe('multi filtering', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/__sitemap__/foo.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/valid</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/multi/issue-514.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/issue-514'),\n  server: true,\n  nuxtConfig: {\n    hooks: {\n      'nitro:config': function (config) {\n        config.runtimeConfig ??= {}\n        config.runtimeConfig.public ??= {}\n        config.runtimeConfig.public.siteUrl = 'https://example.com'\n      },\n    },\n  },\n})\n\ndescribe('issue 514 - multi sitemap with chunks and / prefix', () => {\n  it('sitemap index contains chunked sitemaps', async () => {\n    const index = await $fetch('/sitemap_index.xml')\n\n    expect(index).toContain('<sitemapindex')\n    expect(index).toContain('<loc>https://example.com/pages.xml</loc>')\n    // 15 urls with chunk size 10 = 2 chunks\n    expect(index).toContain('<loc>https://example.com/dynamic-0.xml</loc>')\n    expect(index).toContain('<loc>https://example.com/dynamic-1.xml</loc>')\n  })\n\n  it('pages sitemap works', async () => {\n    const pages = await $fetch('/pages.xml')\n    expect(pages).toContain('<urlset')\n    expect(pages).toContain('<loc>https://example.com/</loc>')\n  })\n\n  it('dynamic chunk 0 works', async () => {\n    const chunk = await $fetch('/dynamic-0.xml')\n    expect(chunk).toContain('<urlset')\n    expect(chunk).toContain('<loc>https://example.com/dynamic/1</loc>')\n    expect(chunk).toContain('<loc>https://example.com/dynamic/10</loc>')\n    expect(chunk).not.toContain('<loc>https://example.com/dynamic/11</loc>')\n  })\n\n  it('dynamic chunk 1 works', async () => {\n    const chunk = await $fetch('/dynamic-1.xml')\n    expect(chunk).toContain('<urlset')\n    expect(chunk).toContain('<loc>https://example.com/dynamic/11</loc>')\n    expect(chunk).toContain('<loc>https://example.com/dynamic/15</loc>')\n    expect(chunk).not.toContain('<loc>https://example.com/dynamic/10</loc>')\n  })\n\n  it('non-existent chunk returns 404', async () => {\n    try {\n      await $fetch('/dynamic-2.xml')\n      throw new Error('Should have thrown 404')\n    }\n    catch (error: any) {\n      expect(error.data?.statusCode || error.statusCode).toBe(404)\n    }\n  })\n\n  it('regular page routes still work', async () => {\n    const about = await $fetch('/about')\n    expect(about).toContain('About page')\n  })\n})\n"
  },
  {
    "path": "test/e2e/single/baseUrl.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    app: {\n      baseURL: '/base',\n    },\n  },\n})\ndescribe('base', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/base/sitemap.xml')\n    expect(sitemap).not.match(/\\/base\\/base\\//g)\n    sitemap = sitemap.replace(/lastmod>(.*?)</g, 'lastmod><')\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/base/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/base</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/base/about</loc>\n              <changefreq>daily</changefreq>\n              <priority>0.8</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/base/crawled</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/base/sub/page</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/baseUrlTrailingSlash.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    app: {\n      baseURL: '/subdir/',\n    },\n    site: {\n      trailingSlash: true,\n    },\n  },\n})\ndescribe('base url trailing slash', () => {\n  it('basic', async () => {\n    const sitemap = await $fetch('/subdir/sitemap.xml')\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/subdir/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/subdir/</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/subdir/about/</loc>\n              <changefreq>daily</changefreq>\n              <priority>0.8</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/subdir/crawled/</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/subdir/sub/page/</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/changeApiUrl.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      sources: ['/__sitemap'],\n    },\n  },\n})\ndescribe('base', () => {\n  it('basic', async () => {\n    const posts = await $fetch('/__sitemap')\n\n    expect(posts).toMatchInlineSnapshot(`\n      [\n        \"/__sitemap/url\",\n        {\n          \"loc\": \"/__sitemap/loc\",\n        },\n        {\n          \"loc\": \"https://nuxtseo.com/__sitemap/abs\",\n        },\n      ]\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/encodeDynamicUrls.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      urls: [\n        // Pre-encoded URL with reserved characters - marked as encoded\n        {\n          loc: `/${encodeURIComponent('$-:)')}`,\n          _encoded: true,\n        },\n        // Pre-encoded emoji - marked as encoded\n        {\n          loc: `/${encodeURIComponent('😅')}`,\n          _encoded: true,\n        },\n        // Regular path without _encoded - will be auto-encoded\n        '/Bücher',\n      ],\n    },\n  },\n})\n\ndescribe('_encoded: true', () => {\n  it('should preserve pre-encoded URLs without double-encoding', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    // Pre-encoded reserved characters should stay encoded ($ and : stay encoded, ) is safe so gets decoded)\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/%24-%3A)</loc>')\n\n    // Pre-encoded emoji should stay encoded\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/%F0%9F%98%85</loc>')\n\n    // Regular URL should be auto-encoded\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/B%C3%BCcher</loc>')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/filtering.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      excludeAppSources: true,\n      urls: [\n        // default blocked routes\n        '/_nuxt',\n        '/_nuxt/foo',\n        '/api',\n        '/api/foo',\n        '/api/foo/bar',\n        // custom blocked routes\n        '/admin',\n        '/admin/foo',\n        '/admin/foo/bar',\n        // should be only route\n        '/valid',\n      ],\n      exclude: [\n        '/api/**',\n        '/admin/**',\n      ],\n    },\n  },\n})\ndescribe('filtering', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/sitemap.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/valid</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/generate.test.ts",
    "content": "import { readFile } from 'node:fs/promises'\nimport { buildNuxt, createResolver, loadNuxt } from '@nuxt/kit'\nimport { describe, expect, it } from 'vitest'\n\ndescribe.skipIf(process.env.CI)('generate', () => {\n  it('basic', async () => {\n    process.env.NODE_ENV = 'production'\n    // @ts-expect-error untyped\n    process.env.prerender = true\n    process.env.NITRO_PRESET = 'static'\n    process.env.NUXT_PUBLIC_SITE_URL = 'https://nuxtseo.com'\n    const { resolve } = createResolver(import.meta.url)\n    const rootDir = resolve('../../fixtures/generate')\n    const nuxt = await loadNuxt({\n      rootDir,\n      overrides: {\n        nitro: {\n          preset: 'static',\n        },\n        _generate: true,\n      },\n    })\n    await buildNuxt(nuxt)\n\n    await new Promise(resolve => setTimeout(resolve, 1000))\n\n    const sitemap = (await readFile(resolve(rootDir, '.output/public/sitemap.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')\n    // verify /noindex is not in the sitemap\n    expect(sitemap).not.toContain('/noindex')\n\n    // #568: verify definePageMeta sitemap data is preserved during generate\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/about</loc>')\n    expect(sitemap).toContain('<changefreq>daily</changefreq>')\n    expect(sitemap).toContain('<priority>0.8</priority>')\n\n    // #568: verify route rules sitemap data is applied during generate\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/sub/page</loc>')\n    expect(sitemap).toContain('<changefreq>weekly</changefreq>')\n    expect(sitemap).toContain('<priority>0.5</priority>')\n  }, 1200000)\n})\n"
  },
  {
    "path": "test/e2e/single/issue-592.test.ts",
    "content": "import { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { buildNuxt, createResolver, loadNuxt } from '@nuxt/kit'\nimport { describe, expect, it } from 'vitest'\n\ndescribe('issue #592: zeroRuntime should prerender sitemaps without manual nitro.prerender.routes', () => {\n  it('generates sitemap index and child sitemaps with zeroRuntime and i18n', async () => {\n    process.env.NODE_ENV = 'production'\n    process.env.NUXT_PUBLIC_SITE_URL = 'https://nuxtseo.com'\n    const { resolve } = createResolver(import.meta.url)\n    const rootDir = resolve('../../fixtures/issue-592')\n    const nuxt = await loadNuxt({\n      rootDir,\n      overrides: {\n        // SSR build, not nuxt generate: _generate is false, preset is node-server\n        _generate: false,\n        nitro: {\n          preset: 'node-server',\n          prerender: {\n            // no manual routes, zeroRuntime should handle it\n            crawlLinks: false,\n          },\n        },\n      },\n    })\n\n    await buildNuxt(nuxt)\n\n    await new Promise(resolve => setTimeout(resolve, 1000))\n\n    const outputDir = resolve(rootDir, '.output/public')\n\n    // sitemap_index.xml should exist\n    expect(existsSync(resolve(outputDir, 'sitemap_index.xml'))).toBe(true)\n    const sitemapIndex = await readFile(resolve(outputDir, 'sitemap_index.xml'), 'utf-8')\n    expect(sitemapIndex).toContain('__sitemap__/en-US.xml')\n    expect(sitemapIndex).toContain('__sitemap__/de-DE.xml')\n\n    // child sitemaps should exist\n    expect(existsSync(resolve(outputDir, '__sitemap__/en-US.xml'))).toBe(true)\n    expect(existsSync(resolve(outputDir, '__sitemap__/de-DE.xml'))).toBe(true)\n  }, 120000)\n})\n"
  },
  {
    "path": "test/e2e/single/lastmod.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      urls: [\n        {\n          loc: '/foo',\n          // valid but with milliseconds, should be removed\n          lastmod: '2023-12-21T13:49:27.963745',\n        },\n        {\n          loc: 'bar',\n          lastmod: '2023-12-21', // valid - no timezone\n        },\n        {\n          loc: 'baz',\n          lastmod: '2023-12-21T13:49:27', // valid - timezone\n        },\n        {\n          loc: 'qux',\n          lastmod: '2023-12-21T13:49:27Z',\n        },\n        {\n          loc: 'quux',\n          lastmod: '2023 tuesday 3rd march', // very broken\n        },\n        {\n          loc: '/issue/206',\n          lastmod: '2023-12-21T22:46:58.441+00:00',\n        },\n      ],\n    },\n  },\n})\ndescribe('lastmod', () => {\n  it('basic', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/about</loc>\n              <changefreq>daily</changefreq>\n              <priority>0.8</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/bar</loc>\n              <lastmod>2023-12-21</lastmod>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/baz</loc>\n              <lastmod>2023-12-21T13:49:27Z</lastmod>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/crawled</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/foo</loc>\n              <lastmod>2023-12-21T13:49:27Z</lastmod>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/quux</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/qux</loc>\n              <lastmod>2023-12-21T13:49:27Z</lastmod>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/issue/206</loc>\n              <lastmod>2023-12-21T22:46:58Z</lastmod>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/sub/page</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/news.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      urls() {\n        return [\n          {\n            loc: 'https://nuxtseo.com/',\n            news: {\n              publication: {\n                name: 'Nuxt SEO',\n                language: 'en',\n              },\n              title: 'Nuxt SEO',\n              publication_date: '2008-12-23',\n            },\n          },\n          {\n            loc: 'https://harlanzw.com/',\n            news: {\n              publication: {\n                name: 'Harlan Wilton',\n                language: 'en',\n              },\n              title: 'Sitemap test',\n              publication_date: '2008-12-23',\n            },\n          },\n        ]\n      },\n    },\n  },\n})\ndescribe('news', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/sitemap.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://harlanzw.com/</loc>\n              <news:news>\n                  <news:publication>\n                      <news:name>Harlan Wilton</news:name>\n                      <news:language>en</news:language>\n                  </news:publication>\n                  <news:title>Sitemap test</news:title>\n                  <news:publication_date>2008-12-23</news:publication_date>\n              </news:news>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n              <news:news>\n                  <news:publication>\n                      <news:name>Nuxt SEO</news:name>\n                      <news:language>en</news:language>\n                  </news:publication>\n                  <news:title>Nuxt SEO</news:title>\n                  <news:publication_date>2008-12-23</news:publication_date>\n              </news:news>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/about</loc>\n              <changefreq>daily</changefreq>\n              <priority>0.8</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/crawled</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/sub/page</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/pageMetaSitemap.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n})\n\ndescribe('definePageMeta sitemap', () => {\n  it('applies sitemap meta from definePageMeta to output', async () => {\n    const sitemap = await $fetch<string>('/sitemap.xml')\n    // about.vue has definePageMeta({ sitemap: { priority: 0.8, changefreq: 'daily' } })\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/about</loc>')\n    expect(sitemap).toContain('<changefreq>daily</changefreq>')\n    expect(sitemap).toContain('<priority>0.8</priority>')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/queryRoutes.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      urls: [\n        '/',\n        '/query-no-slash?foo=bar',\n        '/query-slash/?foo=bar',\n        '/query-slash-hash/?foo=bar#hash',\n      ],\n    },\n  },\n})\ndescribe('query routes', () => {\n  it('basic', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/query-no-slash?foo=bar</loc>')\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/query-slash?foo=bar</loc>')\n    expect(sitemap).not.toContain('<loc>https://nuxtseo.com/query-slash-hash?foo=bar#hash</loc>')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/routeRules.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      excludeAppSources: true,\n      urls: ['/x-robots-tag', '/redirect', '/hidden', '/defaults', '/wildcard/defaults/foo', '/wildcard/hidden/foo'],\n    },\n    routeRules: {\n      '/x-robots-tag': {\n        headers: {\n          'x-robots-tag': 'noindex',\n        },\n      },\n      // won't be indexed\n      '/redirect': {\n        redirect: '/defaults',\n      },\n      '/hidden': {\n        // @ts-expect-error untyped\n        robots: false,\n      },\n      '/defaults': {\n        sitemap: {\n          changefreq: 'daily',\n          priority: 1,\n        },\n      },\n      '/wildcard/defaults/**': {\n        sitemap: {\n          changefreq: 'daily',\n          priority: 1,\n        },\n      },\n      '/wildcard/hidden/**': {\n        // @ts-expect-error untyped\n        robots: false,\n      },\n    },\n  },\n})\ndescribe('route rules', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/sitemap.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/defaults</loc>\n              <changefreq>daily</changefreq>\n              <priority>1.0</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/wildcard/defaults/foo</loc>\n              <changefreq>daily</changefreq>\n              <priority>1.0</priority>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/routeRulesTrailingSlash.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    site: {\n      trailingSlash: true,\n    },\n    sitemap: {\n      excludeAppSources: true,\n      urls: ['/hidden/', '/defaults/', '/wildcard/defaults/foo/', '/wildcard/hidden/foo/'],\n    },\n    routeRules: {\n      '/hidden': {\n        // @ts-expect-error untyped\n        robots: false,\n      },\n      '/hidden/': {\n        // @ts-expect-error untyped\n        robots: false,\n      },\n      '/defaults': {\n        sitemap: {\n          changefreq: 'daily',\n          priority: 1,\n        },\n      },\n      '/wildcard/defaults/**': {\n        sitemap: {\n          changefreq: 'daily',\n          priority: 1,\n        },\n      },\n      '/wildcard/hidden/**': {\n        // @ts-expect-error untyped\n        robots: false,\n      },\n    },\n  },\n})\ndescribe('route rules', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/sitemap.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/defaults/</loc>\n              <changefreq>daily</changefreq>\n              <priority>1.0</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/wildcard/defaults/foo/</loc>\n              <changefreq>daily</changefreq>\n              <priority>1.0</priority>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/sitemapName.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      sitemapName: 'test.xml',\n    },\n  },\n})\ndescribe('sitemapName', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/test.xml')\n    // remove lastmods before tresting\n    sitemap = sitemap.replace(/lastmod>(.*?)</g, 'lastmod><')\n    // basic test to make sure we get a valid response\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/about</loc>\n              <changefreq>daily</changefreq>\n              <priority>0.8</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/crawled</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/sub/page</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/trailingSlashes.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    site: {\n      url: 'https://nuxtseo.com',\n      trailingSlash: true,\n    },\n    sitemap: {\n      // test from endpoint as well\n      sources: ['/__sitemap'],\n    },\n  },\n})\ndescribe('trailing slashes', () => {\n  it('basic', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n    // extract the URLs from loc using regex\n    // @ts-expect-error untyped\n    const sitemapUrls = sitemap.match(/<loc>(.*?)<\\/loc>/g)!.map(url => url.replace(/<\\/?loc>/g, ''))\n    // @ts-expect-error untyped\n    sitemapUrls.forEach((url) => {\n      expect(url.endsWith('/')).toBeTruthy()\n    })\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/urlEncoded.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      urls: [\n        '/Bücher',\n        '/Bibliothèque',\n      ],\n    },\n  },\n})\ndescribe('query routes', () => {\n  it('should be url encoded', async () => {\n    const sitemap = await $fetch('/sitemap.xml')\n\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/B%C3%BCcher</loc>')\n    expect(sitemap).toContain('<loc>https://nuxtseo.com/Biblioth%C3%A8que</loc>')\n    expect(sitemap).not.toContain('https://nuxtseo.com/Bücher')\n    expect(sitemap).not.toContain('https://nuxtseo.com/Bibliothèque')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/video.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      urls: [\n        {\n          loc: 'https://www.example.com/videos/some_video_landing_page.html',\n          videos: [\n            {\n              title: 'Grilling steaks for summer',\n              thumbnail_loc: 'https://www.example.com/thumbs/123.jpg',\n              description: 'Alkis shows you how to get perfectly done steaks every time',\n              content_loc: 'https://streamserver.example.com/video123.mp4',\n              player_loc: 'https://www.example.com/videoplayer.php?video=123',\n              duration: 600,\n              expiration_date: '2022-12-12T00:00:00+00:00',\n              rating: 4.2,\n              view_count: 12345,\n              publication_date: '2007-11-05T19:00:00+00:00',\n              family_friendly: 'yes',\n              restriction: {\n                relationship: 'allow',\n                restriction: 'IE GB US CA',\n              },\n              platform: {\n                relationship: 'allow',\n                platform: 'web mobile',\n              },\n              requires_subscription: 'yes',\n              price: [\n                {\n                  currency: 'EUR',\n                  type: 'rent',\n                  price: 3.99,\n                },\n              ],\n              uploader: {\n                uploader: 'GrillyMcGrillerson',\n                info: 'https://example.com/users/grillymcgrillerson',\n              },\n              live: 'no',\n              tag: ['steak', 'grilling', 'summer'],\n            },\n          ],\n        },\n      ],\n    },\n  },\n})\ndescribe('video', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/sitemap.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/about</loc>\n              <changefreq>daily</changefreq>\n              <priority>0.8</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/crawled</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/sub/page</loc>\n          </url>\n          <url>\n              <loc>https://www.example.com/videos/some_video_landing_page.html</loc>\n              <video:video>\n                  <video:title>Grilling steaks for summer</video:title>\n                  <video:thumbnail_loc>https://www.example.com/thumbs/123.jpg</video:thumbnail_loc>\n                  <video:description>Alkis shows you how to get perfectly done steaks every time</video:description>\n                  <video:content_loc>https://streamserver.example.com/video123.mp4</video:content_loc>\n                  <video:player_loc>https://www.example.com/videoplayer.php?video=123</video:player_loc>\n                  <video:duration>600</video:duration>\n                  <video:expiration_date>2022-12-12T00:00:00+00:00</video:expiration_date>\n                  <video:rating>4.2</video:rating>\n                  <video:view_count>12345</video:view_count>\n                  <video:publication_date>2007-11-05T19:00:00+00:00</video:publication_date>\n                  <video:family_friendly>yes</video:family_friendly>\n                  <video:restriction relationship=\"allow\">IE GB US CA</video:restriction>\n                  <video:platform relationship=\"allow\">web mobile</video:platform>\n                  <video:requires_subscription>yes</video:requires_subscription>\n                  <video:price currency=\"EUR\" type=\"rent\">3.99</video:price>\n                  <video:uploader info=\"https://example.com/users/grillymcgrillerson\">GrillyMcGrillerson</video:uploader>\n                  <video:live>no</video:live>\n                  <video:tag>steak</video:tag>\n                  <video:tag>grilling</video:tag>\n                  <video:tag>summer</video:tag>\n              </video:video>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/xsl.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  nuxtConfig: {\n    sitemap: {\n      xsl: false,\n    },\n  },\n})\ndescribe('xsl false', () => {\n  it('basic', async () => {\n    let sitemap = await $fetch('/sitemap.xml')\n\n    // strip lastmod\n    sitemap = sitemap.replace(/<lastmod>.*<\\/lastmod>/g, '')\n\n    expect(sitemap).toMatchInlineSnapshot(`\n      \"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n      <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n          <url>\n              <loc>https://nuxtseo.com/</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/about</loc>\n              <changefreq>daily</changefreq>\n              <priority>0.8</priority>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/crawled</loc>\n          </url>\n          <url>\n              <loc>https://nuxtseo.com/sub/page</loc>\n          </url>\n      </urlset>\"\n    `)\n  }, 60000)\n})\n"
  },
  {
    "path": "test/e2e/single/zero-runtime-build.test.ts",
    "content": "import { readFile } from 'node:fs/promises'\nimport { buildNuxt, createResolver, loadNuxt } from '@nuxt/kit'\nimport { setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  dev: true,\n  nuxtConfig: {\n    sitemap: {\n      zeroRuntime: true,\n    },\n  },\n})\n\ndescribe('zeroRuntime', () => {\n  describe.skipIf(process.env.CI)('prerender', () => {\n    it('generates sitemap during prerender', async () => {\n      const rootDir = resolve('../../fixtures/generate')\n      const nuxt = await loadNuxt({\n        rootDir,\n        overrides: {\n          sitemap: {\n            zeroRuntime: true,\n            autoLastmod: false,\n            credits: false,\n          },\n        },\n      })\n      await buildNuxt(nuxt)\n\n      const sitemap = (await readFile(resolve(rootDir, '.output/public/sitemap.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')\n      expect(sitemap).toMatchInlineSnapshot(`\n        \"<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"/__sitemap__/style.xsl\"?>\n        <urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd\" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n            <url>\n                <loc>https://nuxtseo.com/</loc>\n            </url>\n            <url>\n                <loc>https://nuxtseo.com/about</loc>\n                <changefreq>daily</changefreq>\n                <priority>0.8</priority>\n            </url>\n            <url>\n                <loc>https://nuxtseo.com/crawled</loc>\n            </url>\n            <url>\n                <loc>https://nuxtseo.com/dynamic/crawled</loc>\n            </url>\n            <url>\n                <loc>https://nuxtseo.com/sub/page</loc>\n                <changefreq>weekly</changefreq>\n                <priority>0.5</priority>\n            </url>\n        </urlset>\"\n      `)\n      expect(sitemap).not.toContain('/noindex')\n    }, 1200000)\n  })\n})\n"
  },
  {
    "path": "test/e2e/single/zero-runtime-dev.test.ts",
    "content": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it } from 'vitest'\n\nconst { resolve } = createResolver(import.meta.url)\n\nawait setup({\n  rootDir: resolve('../../fixtures/basic'),\n  dev: true,\n  nuxtConfig: {\n    sitemap: {\n      zeroRuntime: true,\n    },\n  },\n})\n\ndescribe.skipIf(process.env.CI)('zero runtime dev', () => {\n  it('serves sitemap in dev mode', async () => {\n    // zeroRuntime handlers still work in dev (import.meta.dev === true)\n    // In dev mode, URLs use the local origin rather than the configured site URL\n    const sitemap = await $fetch('/sitemap.xml')\n    expect(sitemap).toContain('<urlset')\n    expect(sitemap).toContain('<loc>')\n    expect(sitemap).toContain('/about')\n  }, 60000)\n})\n"
  },
  {
    "path": "test/fixtures/basic/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\n// https://v3.nuxtjs.org/api/configuration/nuxt.config\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  routeRules: {\n    '/foo-redirect': {\n      redirect: '/foo',\n    },\n  },\n\n  compatibilityDate: '2025-01-15',\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/basic/pages/about.vue",
    "content": "<script setup lang=\"ts\">\ndefinePageMeta({\n  sitemap: {\n    priority: 0.8,\n    changefreq: 'daily',\n  },\n})\n</script>\n\n<template>\n  <div>About</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/basic/pages/crawled.vue",
    "content": "<template>\n  <div>\n    <div>hi</div>\n    <NuxtLink to=\"/dynamic/crawled\">\n      dynamic crawl\n    </NuxtLink>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/basic/pages/dynamic/[slug].vue",
    "content": "<template>\n  <div>Hello world</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/basic/pages/index.vue",
    "content": "<template>\n  <div>\n    <h1>Hello World</h1>\n    <NuxtLink to=\"/crawled\">\n      crawled\n    </NuxtLink>\n    <NuxtLink to=\"/foo-redirect\">\n      should be ignored as its a redirect\n    </NuxtLink>\n    <br>\n    <a href=\"/sitemap.xml\">\n      sitemap.xml\n    </a>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/basic/pages/sub/page.vue",
    "content": "<template>\n  <div>Hello world</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/basic/server/api/sitemap/bar.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/bar/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/basic/server/api/sitemap/foo.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/foo/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/basic/server/routes/__sitemap.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  return [\n    '/__sitemap/url',\n    {\n      loc: '/__sitemap/loc',\n    },\n    {\n      loc: 'https://nuxtseo.com/__sitemap/abs',\n    },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/chunk-cache/app.vue",
    "content": "<template>\n  <div>chunk cache fixture</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/chunk-cache/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [NuxtSitemap],\n  site: { url: 'https://nuxtseo.com' },\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    cacheMaxAgeSeconds: 600,\n    runtimeCacheStorage: { driver: 'memory' },\n    sitemaps: {\n      posts: {\n        sources: ['/api/posts'],\n        chunks: 5,\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "test/fixtures/chunk-cache/server/api/posts.ts",
    "content": "import { defineEventHandler } from 'h3'\n\ndeclare global {\n  // eslint-disable-next-line vars-on-top, no-var\n  var __postsSourceCallCount: number\n}\n\nglobalThis.__postsSourceCallCount ??= 0\n\nexport default defineEventHandler(() => {\n  globalThis.__postsSourceCallCount++\n  return Array.from({ length: 17 }, (_, i) => ({\n    loc: `/posts/${i + 1}`,\n  }))\n})\n"
  },
  {
    "path": "test/fixtures/chunk-cache/server/api/source-call-count.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  return { count: globalThis.__postsSourceCallCount ?? 0 }\n})\n"
  },
  {
    "path": "test/fixtures/chunk-count/app.vue",
    "content": "<template>\n  <div>chunk count fixture</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/chunk-count/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [NuxtSitemap],\n  site: { url: 'https://nuxtseo.com' },\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    sitemaps: {\n      posts: {\n        sources: ['/api/posts'],\n        chunks: 5,\n        chunkCount: 4,\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "test/fixtures/chunk-count/server/api/posts-call-count.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => ({ count: globalThis.__chunkCountPostsCalls ?? 0 }))\n"
  },
  {
    "path": "test/fixtures/chunk-count/server/api/posts.ts",
    "content": "import { defineEventHandler } from 'h3'\n\ndeclare global {\n  // eslint-disable-next-line vars-on-top, no-var\n  var __chunkCountPostsCalls: number\n}\n\nglobalThis.__chunkCountPostsCalls ??= 0\n\nexport default defineEventHandler(() => {\n  globalThis.__chunkCountPostsCalls++\n  return Array.from({ length: 17 }, (_, i) => ({ loc: `/posts/${i + 1}` }))\n})\n"
  },
  {
    "path": "test/fixtures/chunks/app.vue",
    "content": "<template>\n  <div>hello world</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/chunks/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\n// https://v3.nuxtjs.org/api/configuration/nuxt.config\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n    defaultSitemapsChunkSize: 5,\n    sitemaps: true,\n    urls: Array.from({ length: 20 }, (_, i) => `/foo/${i + 1}`),\n    excludeAppSources: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/chunks/server/api/sitemap/bar.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/bar/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/chunks/server/api/sitemap/foo.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/foo/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/chunks/server/routes/__sitemap.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  return [\n    '/__sitemap/url',\n    {\n      loc: '/__sitemap/loc',\n    },\n    {\n      loc: 'https://nuxtseo.com/__sitemap/abs',\n    },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/content-v3/.nuxtrc",
    "content": "imports.autoImport=true\ntypescript.includeWorkspace=true\n"
  },
  {
    "path": "test/fixtures/content-v3/app.vue",
    "content": "<template>\n  <NuxtPage />\n</template>\n"
  },
  {
    "path": "test/fixtures/content-v3/content/.navigation.yml",
    "content": "\ntitle: 'test'\n"
  },
  {
    "path": "test/fixtures/content-v3/content/_partial.md",
    "content": "---\nsitemap: false\n---\n\n# bar\n"
  },
  {
    "path": "test/fixtures/content-v3/content/bar.md",
    "content": "---\nsitemap:\n  lastmod: 2021-10-20\n  priority: 0.5\n  changefreq: daily\n---\n\n# bar\n\n<img src=\"https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg\" alt=\"Sponsors\" />\n"
  },
  {
    "path": "test/fixtures/content-v3/content/foo.md",
    "content": "---\nsitemap:\n  priority: 0.5\n---\n\n# foo\n"
  },
  {
    "path": "test/fixtures/content-v3/content/posts/.navigation.yml",
    "content": "title: 'test'\n"
  },
  {
    "path": "test/fixtures/content-v3/content/posts/bar.md",
    "content": "---\nsitemap:\n  lastmod: 2021-10-20\n---\n# bar\n"
  },
  {
    "path": "test/fixtures/content-v3/content/posts/fallback.md",
    "content": "---\nsitemap:\n  lastmod: 2021-10-20\n---\n\n# foo\n\nno sitemap config\n"
  },
  {
    "path": "test/fixtures/content-v3/content/posts/foo.md",
    "content": "# foo\n\nno sitemap config\n"
  },
  {
    "path": "test/fixtures/content-v3/content/test-json.json",
    "content": "{\n  \"title\": \"Test JSON Content\",\n  \"description\": \"This is a test JSON file for sitemap\",\n  \"sitemap\": {\n    \"lastmod\": \"2025-05-14\",\n    \"changefreq\": \"weekly\",\n    \"priority\": 0.9\n  },\n  \"content\": \"This is some JSON content\"\n}"
  },
  {
    "path": "test/fixtures/content-v3/content/test-yaml.yml",
    "content": "title: Test YAML Content\ndescription: This is a test YAML file for sitemap\nsitemap:\n  lastmod: 2025-05-13\n  changefreq: monthly\n  priority: 0.8\ncontent: This is some YAML content"
  },
  {
    "path": "test/fixtures/content-v3/content.config.ts",
    "content": "import { resolve, dirname } from 'node:path'\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { asSitemapCollection } from '../../../src/content'\nimport { z } from 'zod'\n\n// conjvert file path to url\nconst dirName = dirname(import.meta.url.replace('file://', ''))\n\nexport default defineContentConfig({\n  collections: {\n    content: defineCollection(\n      asSitemapCollection({\n        type: 'page',\n        source: {\n          include: '**/*',\n          cwd: resolve(dirName, 'content'),\n        },\n        schema: z.object({\n          date: z.string().optional(),\n        }),\n      }),\n    ),\n  },\n})\n"
  },
  {
    "path": "test/fixtures/content-v3/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxt/content',\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n  compatibilityDate: '2024-12-06',\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/content-v3/pages/[...slug].vue",
    "content": "<script setup lang=\"ts\">\nimport { queryCollection, useRoute } from '#imports'\n\nconst route = useRoute()\nconst { data: page } = await useAsyncData(`page-${route.path}`, () => {\n  return queryCollection('content').path(route.path).first()\n})\nuseSeoMeta(page.value?.seo || {})\n</script>\n\n<template>\n  <div>\n    <ContentRenderer\n      v-if=\"page\"\n      :value=\"page\"\n    />\n    <div v-else>\n      Page not found\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/app.vue",
    "content": "<template>\n  <NuxtPage />\n</template>\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/content/bar.md",
    "content": "---\nsitemap:\n  priority: 0.5\n---\n\n# Bar\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/content/draft.md",
    "content": "---\ndraft: true\nsitemap:\n  priority: 0.5\n---\n\n# Draft Post\n\nThis should be filtered from the sitemap.\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/content/foo.md",
    "content": "---\nsitemap:\n  priority: 0.5\n---\n\n# Foo\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/content/future.md",
    "content": "---\ndate: '2099-01-01'\nsitemap:\n  priority: 0.5\n---\n\n# Future Post\n\nThis should be filtered from the sitemap.\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/content/published.md",
    "content": "---\ndate: '2024-01-01'\ndraft: false\nsitemap:\n  priority: 0.5\n---\n\n# Published Post\n\nThis should appear in the sitemap.\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/content.config.ts",
    "content": "import { resolve, dirname } from 'node:path'\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { defineSitemapSchema } from '../../../src/content'\nimport { z } from 'zod'\n\nconst dirName = dirname(import.meta.url.replace('file://', ''))\n\nexport default defineContentConfig({\n  collections: {\n    content: defineCollection({\n      type: 'page',\n      source: {\n        include: '**/*',\n        cwd: resolve(dirName, 'content'),\n      },\n      schema: z.object({\n        date: z.string().optional(),\n        draft: z.boolean().optional(),\n        sitemap: defineSitemapSchema({\n          name: 'content',\n          filter: (entry) => {\n            if (entry.draft)\n              return false\n            if (entry.date && new Date(entry.date) > new Date())\n              return false\n            return true\n          },\n        }),\n      }),\n    }),\n  },\n})\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxt/content',\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n  compatibilityDate: '2024-12-06',\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/content-v3-define-schema/pages/[...slug].vue",
    "content": "<template>\n  <ContentRenderer :value=\"page\" />\n</template>\n\n<script setup lang=\"ts\">\nconst { data: page } = await useAsyncData('page', () => queryCollection('content').path(useRoute().path).first())\n</script>\n"
  },
  {
    "path": "test/fixtures/content-v3-filtering/content/bar.md",
    "content": "---\nsitemap:\n  priority: 0.5\n---\n\n# Bar\n"
  },
  {
    "path": "test/fixtures/content-v3-filtering/content/draft.md",
    "content": "---\ndraft: true\nsitemap:\n  priority: 0.5\n---\n\n# Draft Post\n\nThis should be filtered from the sitemap.\n"
  },
  {
    "path": "test/fixtures/content-v3-filtering/content/foo.md",
    "content": "---\nsitemap:\n  priority: 0.5\n---\n\n# Foo\n"
  },
  {
    "path": "test/fixtures/content-v3-filtering/content/future.md",
    "content": "---\ndate: '2099-01-01'\nsitemap:\n  priority: 0.5\n---\n\n# Future Post\n\nThis should be filtered from the sitemap.\n"
  },
  {
    "path": "test/fixtures/content-v3-filtering/content/published.md",
    "content": "---\ndate: '2024-01-01'\ndraft: false\nsitemap:\n  priority: 0.5\n---\n\n# Published Post\n\nThis should appear in the sitemap.\n"
  },
  {
    "path": "test/fixtures/content-v3-filtering/content.config.ts",
    "content": "import { resolve, dirname } from 'node:path'\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { asSitemapCollection } from '../../../src/content'\nimport { z } from 'zod'\n\nconst dirName = dirname(import.meta.url.replace('file://', ''))\n\nexport default defineContentConfig({\n  collections: {\n    content: defineCollection(\n      asSitemapCollection({\n        type: 'page',\n        source: {\n          include: '**/*',\n          cwd: resolve(dirName, 'content'),\n        },\n        schema: z.object({\n          date: z.string().optional(),\n          draft: z.boolean().optional(),\n        }),\n      }, {\n        name: 'content',\n        filter: (entry) => {\n          if (entry.draft)\n            return false\n          if (entry.date && new Date(entry.date) > new Date())\n            return false\n          return true\n        },\n      }),\n    ),\n  },\n})\n"
  },
  {
    "path": "test/fixtures/content-v3-filtering/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxt/content',\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n  compatibilityDate: '2024-12-06',\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/.nuxtrc",
    "content": "imports.autoImport=true\ntypescript.includeWorkspace=true\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/app.vue",
    "content": "<template>\n  <NuxtPage />\n</template>\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/content/en/getting-started.md",
    "content": "---\nsitemap: {}\n---\n\n# Getting Started\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/content/en/index.md",
    "content": "---\nsitemap: {}\n---\n\n# Home\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/content/ja/getting-started.md",
    "content": "---\nsitemap: {}\n---\n\n# はじめに\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/content/ja/index.md",
    "content": "---\nsitemap: {}\n---\n\n# ホーム\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/content.config.ts",
    "content": "import { dirname, resolve } from 'node:path'\nimport { defineCollection, defineContentConfig } from '@nuxt/content'\nimport { defineSitemapSchema } from '../../../src/content'\nimport { z } from 'zod'\n\nconst dirName = dirname(import.meta.url.replace('file://', ''))\n\nexport default defineContentConfig({\n  collections: {\n    content_en: defineCollection({\n      type: 'page',\n      source: {\n        include: 'en/**',\n        prefix: '/',\n        cwd: resolve(dirName, 'content'),\n      },\n      schema: z.object({\n        sitemap: defineSitemapSchema(),\n      }),\n    }),\n    content_ja: defineCollection({\n      type: 'page',\n      source: {\n        include: 'ja/**',\n        prefix: '/ja',\n        cwd: resolve(dirName, 'content'),\n      },\n      schema: z.object({\n        sitemap: defineSitemapSchema(),\n      }),\n    }),\n  },\n})\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxt/content',\n    '@nuxtjs/i18n',\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n  compatibilityDate: '2024-12-06',\n\n  i18n: {\n    baseUrl: 'https://nuxtseo.com',\n    detectBrowserLanguage: false,\n    defaultLocale: 'en',\n    strategy: 'prefix_except_default',\n    locales: [\n      {\n        code: 'en',\n        iso: 'en-US',\n      },\n      {\n        code: 'ja',\n        iso: 'ja-JP',\n      },\n    ],\n  },\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/content-v3-i18n/pages/[...slug].vue",
    "content": "<template>\n  <div>\n    <ContentRenderer v-if=\"page\" :value=\"page\" />\n    <div v-else>\n      Page not found\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/generate/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\n// https://v3.nuxtjs.org/api/configuration/nuxt.config\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  routeRules: {\n    '/foo-redirect': {\n      redirect: '/foo',\n    },\n    '/sub/page': {\n      sitemap: {\n        changefreq: 'weekly',\n        priority: 0.5,\n      },\n    },\n  },\n\n  compatibilityDate: '2025-01-15',\n\n  nitro: {\n    prerender: {\n      crawlLinks: true,\n      routes: ['/', '/about', '/noindex', '/sub/page'],\n    },\n  },\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/generate/pages/about.vue",
    "content": "<script setup lang=\"ts\">\ndefinePageMeta({\n  sitemap: {\n    priority: 0.8,\n    changefreq: 'daily',\n  },\n})\n</script>\n\n<template>\n  <div>About</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/generate/pages/crawled.vue",
    "content": "<template>\n  <div>\n    <div>hi</div>\n    <NuxtLink to=\"/dynamic/crawled\">\n      dynamic crawl\n    </NuxtLink>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/generate/pages/dynamic/[slug].vue",
    "content": "<template>\n  <div>Hello world</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/generate/pages/index.vue",
    "content": "<template>\n  <div>\n    <h1>Hello World</h1>\n    <NuxtLink to=\"/crawled\">\n      crawled\n    </NuxtLink>\n    <NuxtLink to=\"/foo-redirect\">\n      should be ignored as its a redirect\n    </NuxtLink>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/generate/pages/noindex.vue",
    "content": "<script setup lang=\"ts\">\nimport { useHead } from '#imports'\n\nuseHead({\n  meta: [\n    { name: 'robots', content: 'noindex' },\n  ],\n})\n</script>\n\n<template>\n  <div>This page should not be in the sitemap</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/generate/pages/sub/page.vue",
    "content": "<template>\n  <div>Hello world</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/generate/server/api/sitemap/bar.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/bar/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/generate/server/api/sitemap/foo.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  const posts = Array.from({ length: 5 }, (_, i) => i + 1)\n  return [\n    ...posts.map(post => ({\n      loc: `/foo/${post}`,\n    })),\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/generate/server/routes/__sitemap.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  return [\n    '/__sitemap/url',\n    {\n      loc: '/__sitemap/loc',\n    },\n    {\n      loc: 'https://nuxtseo.com/__sitemap/abs',\n    },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/hooks/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\n// https://v3.nuxtjs.org/api/configuration/nuxt.config\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  routeRules: {\n    '/foo-redirect': {\n      redirect: '/foo',\n    },\n  },\n\n  compatibilityDate: '2025-01-15',\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/hooks/pages/index.vue",
    "content": "<template>\n  <div>\n    <a href=\"/sitemap.xml\">\n      sitemap.xml\n    </a>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/hooks/server/plugins/sitemap.ts",
    "content": "import { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:input', async (ctx) => {\n    ctx.urls.push({\n      loc: '/test-1',\n    })\n\n    ctx.urls.push({\n      loc: '/test-2',\n    })\n  })\n})\n"
  },
  {
    "path": "test/fixtures/hooks/server/routes/__sitemap.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  return [\n    '/__sitemap/url',\n    {\n      loc: '/__sitemap/loc',\n    },\n    {\n      loc: 'https://nuxtseo.com/__sitemap/abs',\n    },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/i18n/locales/en.ts",
    "content": "export default {\n  welcome: 'Welcome',\n}\n"
  },
  {
    "path": "test/fixtures/i18n/locales/hr.ts",
    "content": "export default {\n  welcome: 'ようこそ',\n}\n"
  },
  {
    "path": "test/fixtures/i18n/locales/ja.ts",
    "content": "export default {\n  welcome: 'ようこそ',\n}\n"
  },
  {
    "path": "test/fixtures/i18n/locales/nl.ts",
    "content": "export default {\n  welcome: 'Welcome',\n}\n"
  },
  {
    "path": "test/fixtures/i18n/locales/zh.ts",
    "content": "export default {\n  welcome: '欢迎光临',\n}\n"
  },
  {
    "path": "test/fixtures/i18n/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\n// https://v3.nuxtjs.org/api/configuration/nuxt.config\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxtjs/i18n',\n  ],\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  compatibilityDate: '2024-07-22',\n  nitro: {\n    prerender: {\n      failOnError: false,\n      ignore: ['/'],\n    },\n  },\n  i18n: {\n    baseUrl: 'https://nuxtseo.com',\n    detectBrowserLanguage: false,\n    defaultLocale: 'en',\n    strategy: 'prefix',\n    locales: [\n      {\n        code: 'en',\n        iso: 'en-US',\n      },\n      {\n        code: 'es',\n        iso: 'es-ES',\n      },\n      {\n        code: 'fr',\n        iso: 'fr-FR',\n      },\n    ],\n  },\n  sitemap: {\n    sources: ['/__sitemap'],\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/i18n/pages/dynamic/[page].vue",
    "content": "<script setup lang=\"ts\">\nimport { useHead, useLocaleHead, useNuxtApp } from '#imports'\n\nconst i18n = useNuxtApp().$i18n\n\nconst i18nHead = useLocaleHead({\n  addDirAttribute: true,\n  identifierAttribute: 'id',\n  addSeoAttributes: true,\n  i18n,\n})\n\nuseHead({\n  htmlAttrs: {\n    lang: i18nHead.value.htmlAttrs!.lang,\n  },\n  link: [...(i18nHead.value.link || [])],\n  meta: [...(i18nHead.value.meta || [])],\n})\n</script>\n\n<template>\n  <div>{{ $route.params.page }}</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n/pages/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { prerenderRoutes } from '#imports'\n\nprerenderRoutes([\n  '/en/dynamic/foo',\n  '/en/dynamic/bar',\n])\n</script>\n\n<template>\n  <div>\n    <h1>{{ $t('welcome') }}</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n/pages/no-i18n.vue",
    "content": "<script setup lang=\"ts\">\ndefineI18nRoute(false)\n</script>\n\n<template>\n  <div>hello</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n/pages/test.vue",
    "content": "<script setup lang=\"ts\">\n</script>\n\n<template>\n  <div>\n    <h1>{{ $t('welcome') }}</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n/server/routes/__sitemap.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/__sitemap/url',\n      changefreq: 'weekly',\n      _i18nTransform: true,\n    },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/i18n/server/routes/i18n-urls.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/en/dynamic/foo',\n    },\n    {\n      loc: '/fr/dynamic/foo',\n    },\n    {\n      loc: 'endless-dungeon', // issue with en being picked up as the locale\n      _i18nTransform: true,\n    },\n    {\n      loc: 'english-url', // issue with en being picked up as the locale\n    },\n    // absolute URL issue\n    { loc: 'https://www.somedomain.com/abc/def' },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/i18n-custom-paths/app.vue",
    "content": "<template>\n  <div>\n    <NuxtPage />\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n-custom-paths/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxtjs/i18n',\n  ],\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  compatibilityDate: '2024-07-22',\n  nitro: {\n    prerender: {\n      failOnError: false,\n      ignore: ['/'],\n    },\n  },\n  i18n: {\n    baseUrl: 'https://nuxtseo.com',\n    detectBrowserLanguage: false,\n    defaultLocale: 'en',\n    strategy: 'prefix_except_default',\n    locales: [\n      {\n        code: 'en',\n        iso: 'en-US',\n      },\n      {\n        code: 'es',\n        iso: 'es-ES',\n      },\n      {\n        code: 'fr',\n        iso: 'fr-FR',\n      },\n    ],\n    pages: {\n      test: {\n        en: '/test',\n        es: '/prueba',\n        fr: '/teste',\n      },\n      about: {\n        en: '/about',\n        es: '/acerca-de',\n        fr: '/a-propos',\n      },\n      // dynamic route with single parameter (issue #542)\n      posts: {\n        en: '/posts/[slug]',\n        es: '/articulos/[slug]',\n        fr: '/article/[slug]',\n      },\n      // dynamic route with multiple parameters\n      products: {\n        en: '/products/[category]/[id]',\n        es: '/productos/[category]/[id]',\n        fr: '/produits/[category]/[id]',\n      },\n    },\n  },\n  sitemap: {\n    sources: ['/__sitemap'],\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/i18n-custom-paths/server/routes/__sitemap.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    // Static routes\n    {\n      loc: '/test',\n      _i18nTransform: true,\n    },\n    {\n      loc: '/about',\n      _i18nTransform: true,\n    },\n    {\n      loc: '/__sitemap/url',\n      changefreq: 'weekly',\n    },\n    // Dynamic route with single parameter (issue #542)\n    {\n      loc: '/posts/my-slug',\n      _i18nTransform: true,\n    },\n    // Dynamic route with multiple parameters\n    {\n      loc: '/products/electronics/laptop-123',\n      _i18nTransform: true,\n    },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/i18n-generate/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxtjs/i18n',\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  compatibilityDate: '2024-07-22',\n\n  nitro: {\n    prerender: {\n      routes: ['/', '/de'],\n      crawlLinks: false,\n    },\n  },\n\n  i18n: {\n    baseUrl: 'https://nuxtseo.com',\n    detectBrowserLanguage: false,\n    defaultLocale: 'en',\n    strategy: 'prefix_except_default',\n    locales: [\n      {\n        code: 'en',\n        iso: 'en-US',\n      },\n      {\n        code: 'de',\n        iso: 'de-DE',\n      },\n    ],\n  },\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/i18n-generate/pages/index.vue",
    "content": "<template>\n  <div>\n    <h1>Home</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n-micro/locales/en.ts",
    "content": "export default {\n  welcome: 'Welcome',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-micro/locales/hr.ts",
    "content": "export default {\n  welcome: 'ようこそ',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-micro/locales/ja.ts",
    "content": "export default {\n  welcome: 'ようこそ',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-micro/locales/nl.ts",
    "content": "export default {\n  welcome: 'Welcome',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-micro/locales/zh.ts",
    "content": "export default {\n  welcome: '欢迎光临',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-micro/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    'nuxt-i18n-micro',\n  ],\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  compatibilityDate: '2024-07-22',\n  nitro: {\n    prerender: {\n      failOnError: false,\n      ignore: ['/'],\n    },\n  },\n  i18n: {\n    baseUrl: 'https://nuxtseo.com',\n    detectBrowserLanguage: false,\n    defaultLocale: 'en',\n    strategy: 'prefix',\n    locales: [\n      {\n        code: 'en',\n        iso: 'en-US',\n      },\n      {\n        code: 'es',\n        iso: 'es-ES',\n      },\n      {\n        code: 'fr',\n        iso: 'fr-FR',\n      },\n    ],\n    meta: true,\n  },\n  sitemap: {\n    sources: ['/__sitemap'],\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/i18n-micro/pages/dynamic/[page].vue",
    "content": "<template>\n  <div>{{ $route.params.page }}</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n-micro/pages/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { prerenderRoutes } from '#imports'\n\nprerenderRoutes([\n  '/en/dynamic/foo',\n  '/en/dynamic/bar',\n])\n</script>\n\n<template>\n  <div>\n    <h1>{{ $t('welcome') }}</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n-micro/pages/test.vue",
    "content": "<template>\n  <div>\n    <h1>{{ $t('welcome') }}</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n-micro/server/routes/__sitemap.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/__sitemap/url',\n      changefreq: 'weekly',\n      _i18nTransform: true,\n    },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/i18n-micro/server/routes/i18n-urls.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/en/dynamic/foo',\n    },\n    {\n      loc: '/fr/dynamic/foo',\n    },\n    {\n      loc: 'endless-dungeon', // issue with en being picked up as the locale\n      _i18nTransform: true,\n    },\n    {\n      loc: 'english-url', // issue with en being picked up as the locale\n    },\n    // absolute URL issue\n    { loc: 'https://www.somedomain.com/abc/def' },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/locales/en.ts",
    "content": "export default {\n  welcome: 'Welcome',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/locales/hr.ts",
    "content": "export default {\n  welcome: 'ようこそ',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/locales/ja.ts",
    "content": "export default {\n  welcome: 'ようこそ',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/locales/nl.ts",
    "content": "export default {\n  welcome: 'Welcome',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/locales/zh.ts",
    "content": "export default {\n  welcome: '欢迎光临',\n}\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxtjs/i18n',\n  ],\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  compatibilityDate: '2024-07-22',\n  nitro: {\n    prerender: {\n      failOnError: false,\n      ignore: ['/'],\n    },\n  },\n  i18n: {\n    baseUrl: 'https://nuxtseo.com',\n    detectBrowserLanguage: false,\n    defaultLocale: 'en',\n    strategy: 'no_prefix',\n    locales: [\n      {\n        code: 'en',\n        iso: 'en-US',\n      },\n      {\n        code: 'es',\n        iso: 'es-ES',\n      },\n      {\n        code: 'fr',\n        iso: 'fr-FR',\n      },\n    ],\n    pages: {\n      test: {\n        en: '/test',\n        es: '/prueba',\n        fr: '/teste',\n      },\n      about: {\n        en: '/about',\n        es: '/acerca-de',\n        fr: '/a-propos',\n      },\n    },\n  },\n  sitemap: {\n    sources: ['/__sitemap'],\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n    discoverImages: false,\n    discoverVideos: false,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/pages/dynamic/[page].vue",
    "content": "<template>\n  <div>{{ $route.params.page }}</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/pages/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { prerenderRoutes } from '#imports'\n\nprerenderRoutes([\n  '/en/dynamic/foo',\n  '/en/dynamic/bar',\n])\n</script>\n\n<template>\n  <div>\n    <h1>{{ $t('welcome') }}</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/pages/test.vue",
    "content": "<template>\n  <div>\n    <h1>{{ $t('welcome') }}</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/server/routes/__sitemap.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/__sitemap/url',\n      changefreq: 'weekly',\n      _i18nTransform: true,\n    },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/i18n-no-prefix/server/routes/i18n-urls.ts",
    "content": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n  return [\n    {\n      loc: '/en/dynamic/foo',\n    },\n    {\n      loc: '/fr/dynamic/foo',\n    },\n    {\n      loc: 'endless-dungeon', // issue with en being picked up as the locale\n      _i18nTransform: true,\n    },\n    {\n      loc: 'english-url', // issue with en being picked up as the locale\n    },\n    // absolute URL issue\n    { loc: 'https://www.somedomain.com/abc/def' },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/issue-384/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    '@nuxtjs/robots',\n    NuxtSitemap,\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  robots: {\n    groups: [\n      {\n        userAgent: '*',\n        disallow: '/',\n      },\n    ],\n  },\n\n  compatibilityDate: '2025-01-15',\n\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/issue-384/pages/about.vue",
    "content": "<template>\n  <div>\n    <h1>About</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-384/pages/index.vue",
    "content": "<template>\n  <div>\n    <h1>Home</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-504/nuxt.config.ts",
    "content": "import { defineNuxtConfig } from 'nuxt/config'\nimport NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n  site: {\n    url: 'https://example.com',\n  },\n  sitemap: {\n    cacheMaxAgeSeconds: 0,\n    sitemapsPathPrefix: false,\n    sitemaps: {\n      test: {\n        includeAppSources: true,\n        sources: ['/api/__sitemap__/test'],\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "test/fixtures/issue-504/pages/about.vue",
    "content": "<template>\n  <div>About</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-504/pages/index.vue",
    "content": "<template>\n  <div>Index</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-504/server/api/__sitemap__/[s_type].ts",
    "content": "import { defineEventHandler, getRouterParam } from 'h3'\n\n// Track call count at module level\nlet callCount = 0\n\nexport default defineEventHandler((event) => {\n  const category = getRouterParam(event, 's_type')\n  callCount++\n  // eslint-disable-next-line no-console\n  console.log(`sitemap: ${category} (call ${callCount})`)\n\n  // Store count in app context for test retrieval\n  const storage = (globalThis as any).__sitemapTestStorage = (globalThis as any).__sitemapTestStorage || {}\n  storage.callCount = callCount\n\n  return [\n    { loc: '/dynamic-page-1' },\n    { loc: '/dynamic-page-2' },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/issue-504/server/api/__sitemap__/call-count.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  const storage = (globalThis as any).__sitemapTestStorage || {}\n  return { count: storage.callCount || 0 }\n})\n"
  },
  {
    "path": "test/fixtures/issue-514/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n  site: {\n    url: 'https://example.com',\n  },\n  sitemap: {\n    cacheMaxAgeSeconds: 0,\n    sitemapsPathPrefix: '/',\n    sitemaps: {\n      pages: {\n        includeAppSources: true,\n      },\n      dynamic: {\n        sources: ['/api/urls'],\n        chunks: 10,\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "test/fixtures/issue-514/pages/about.vue",
    "content": "<template>\n  <div>About page</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-514/pages/index.vue",
    "content": "<template>\n  <div>Home</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-514/server/api/urls.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  return Array.from({ length: 15 }, (_, i) => ({\n    loc: `/dynamic/${i + 1}`,\n    lastmod: new Date(2024, 0, i + 1).toISOString(),\n  }))\n})\n"
  },
  {
    "path": "test/fixtures/issue-561/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxtjs/i18n',\n  ],\n\n  site: {\n    url: 'https://example.com',\n  },\n\n  compatibilityDate: '2024-07-22',\n\n  i18n: {\n    baseUrl: 'https://example.com',\n    strategy: 'prefix_except_default',\n    defaultLocale: 'fr',\n    detectBrowserLanguage: false,\n    locales: [\n      {\n        code: 'en',\n        iso: 'en-CA',\n        language: 'en',\n      },\n      {\n        code: 'fr',\n        iso: 'fr-CA',\n        language: 'fr',\n      },\n    ],\n    trailingSlash: true,\n    customRoutes: 'config',\n    pages: {\n      'submit-art': {\n        fr: '/envoyer-tableau',\n        en: '/submit-art',\n      },\n      'privacy-policy': {\n        fr: '/politique-de-confidentialite',\n        en: '/privacy-policy',\n      },\n    },\n  },\n\n  sitemap: {\n    autoI18n: false,\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/issue-561/pages/index.vue",
    "content": "<template>\n  <div>\n    <h1>Home</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-561/pages/privacy-policy.vue",
    "content": "<template>\n  <div>\n    <h1>Privacy Policy</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-561/pages/submit-art.vue",
    "content": "<template>\n  <div>\n    <h1>Submit Art</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-588/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n\n  site: {\n    url: 'https://example.com',\n  },\n\n  compatibilityDate: '2024-07-22',\n\n  sitemap: {\n    autoI18n: false,\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/issue-588/pages/about.vue",
    "content": "<script setup lang=\"ts\">\nuseHead({\n  link: [\n    { rel: 'alternate', hreflang: 'de-DE', href: 'https://example.de/about' },\n    { rel: 'alternate', hreflang: 'fr-FR', href: 'https://example.fr/about' },\n    { rel: 'alternate', hreflang: 'it-IT', href: 'https://example.it/about' },\n  ],\n})\n</script>\n\n<template>\n  <div>\n    <h1>About</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-588/pages/contact.vue",
    "content": "<script setup lang=\"ts\">\nuseHead({\n  link: [\n    { rel: 'alternate', hreflang: 'de-DE', href: 'https://example.de/contact' },\n    { rel: 'alternate', hreflang: 'fr-FR', href: 'https://example.fr/contact' },\n    { rel: 'alternate', hreflang: 'it-IT', href: 'https://example.it/contact' },\n  ],\n})\n</script>\n\n<template>\n  <div>\n    <h1>Contact</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-588/pages/index.vue",
    "content": "<template>\n  <div>\n    <h1>Home</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/issue-592/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n    '@nuxtjs/i18n',\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  compatibilityDate: '2024-07-22',\n\n  i18n: {\n    baseUrl: 'https://nuxtseo.com',\n    detectBrowserLanguage: false,\n    defaultLocale: 'en',\n    strategy: 'prefix_except_default',\n    locales: [\n      {\n        code: 'en',\n        iso: 'en-US',\n      },\n      {\n        code: 'de',\n        iso: 'de-DE',\n      },\n    ],\n  },\n\n  sitemap: {\n    zeroRuntime: true,\n    autoLastmod: false,\n    credits: false,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/issue-592/pages/index.vue",
    "content": "<template>\n  <div>\n    <h1>Home</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/multi-with-chunks/app.vue",
    "content": "<template>\n  <div>\n    <h1>Multi Sitemap Chunking Test</h1>\n  </div>\n</template>\n"
  },
  {
    "path": "test/fixtures/multi-with-chunks/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\n// https://v3.nuxtjs.org/api/configuration/nuxt.config\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n  sitemap: {\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n    defaultSitemapsChunkSize: 5,\n    sitemaps: {\n      pages: {\n        urls: Array.from({ length: 20 }, (_, i) => `/page/${i + 1}`),\n        excludeAppSources: true,\n      },\n      posts: {\n        sources: [\n          '/api/posts',\n        ],\n        chunks: true,\n        chunkSize: 3,\n      },\n      products: {\n        sources: [\n          '/api/products',\n        ],\n        chunks: 10, // use 10 as chunk size\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "test/fixtures/multi-with-chunks/server/api/posts.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  // Generate 12 posts to test chunking with chunkSize: 3 (should create 4 chunks)\n  return Array.from({ length: 12 }, (_, i) => ({\n    loc: `/posts/${i + 1}`,\n    lastmod: new Date(2024, 0, i + 1).toISOString(),\n  }))\n})\n"
  },
  {
    "path": "test/fixtures/multi-with-chunks/server/api/products.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  // Generate 25 products to test chunking with chunkSize: 10 (should create 3 chunks)\n  return Array.from({ length: 25 }, (_, i) => ({\n    loc: `/products/${i + 1}`,\n    lastmod: new Date(2024, 1, i + 1).toISOString(),\n  }))\n})\n"
  },
  {
    "path": "test/fixtures/no-pages/app.vue",
    "content": "<template>\n  <div>hello world</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/no-pages/nuxt.config.ts",
    "content": "import NuxtSitemap from '../../../src/module'\n\n// https://v3.nuxtjs.org/api/configuration/nuxt.config\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n\n  site: {\n    url: 'https://nuxtseo.com',\n  },\n\n  compatibilityDate: '2025-03-14',\n\n  sitemap: {\n    sources: ['/__sitemap'],\n    autoLastmod: false,\n    credits: false,\n    debug: true,\n  },\n})\n"
  },
  {
    "path": "test/fixtures/sources-hook/nuxt.config.ts",
    "content": "import { defineNuxtConfig } from 'nuxt/config'\nimport NuxtSitemap from '../../../src/module'\n\nexport default defineNuxtConfig({\n  modules: [\n    NuxtSitemap,\n  ],\n  site: {\n    url: 'https://example.com',\n  },\n  nitro: {\n    plugins: ['~/server/plugins/sources-hook.ts'],\n  },\n  sitemap: {\n    sources: [\n      '/api/initial-source',\n    ],\n  },\n})\n"
  },
  {
    "path": "test/fixtures/sources-hook/pages/index.vue",
    "content": "<template>\n  <div>Test fixture for sources hook</div>\n</template>\n"
  },
  {
    "path": "test/fixtures/sources-hook/server/api/dynamic-source.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => {\n  return [\n    { loc: '/dynamic-source-url' },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/sources-hook/server/api/initial-source.ts",
    "content": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler((event) => {\n  const headers = event.node.req.headers\n\n  // Return different URLs based on whether headers were modified by hook\n  if (headers['x-hook-modified'] === 'true') {\n    return [\n      { loc: '/hook-modified' },\n    ]\n  }\n\n  return [\n    { loc: '/initial-source-default' },\n  ]\n})\n"
  },
  {
    "path": "test/fixtures/sources-hook/server/plugins/sources-hook.ts",
    "content": "import { defineNitroPlugin } from 'nitropack/runtime'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('sitemap:sources', async (ctx) => {\n    // Add a new source dynamically using simple string syntax\n    ctx.sources.push('/api/dynamic-source')\n\n    // Add a source to be filtered (also using simple string syntax)\n    ctx.sources.push('/api/skip-this')\n\n    // Modify existing sources to add headers\n    ctx.sources = ctx.sources.map((source) => {\n      if (typeof source === 'object' && 'fetch' in source && source.fetch === '/api/initial-source') {\n        // Modify fetch to add headers\n        source.fetch = ['/api/initial-source', { headers: { 'X-Hook-Modified': 'true' } }]\n      }\n      return source\n    })\n\n    // Filter out sources we don't want\n    ctx.sources = ctx.sources.filter((source) => {\n      if (typeof source === 'string')\n        return !source.includes('skip-this')\n      if (typeof source === 'object' && 'fetch' in source && source.fetch) {\n        const fetchUrl = Array.isArray(source.fetch) ? source.fetch[0] : source.fetch\n        return !fetchUrl.includes('skip-this')\n      }\n      return true\n    })\n  })\n})\n"
  },
  {
    "path": "test/types/templates.test-d.ts",
    "content": "import type {\n  SitemapIndexRenderCtx,\n  SitemapInputCtx,\n  SitemapItemDefaults,\n  SitemapOutputHookCtx,\n  SitemapRenderCtx,\n  SitemapSourceBase,\n  SitemapSourceResolved,\n  SitemapSourcesHookCtx,\n  SitemapUrl,\n} from '@nuxtjs/sitemap'\nimport type { NitroRouteConfig, NitroRouteRules, NitroRuntimeHooks, PrerenderRoute } from 'nitropack'\nimport type { NitroRouteConfig as NitroRouteConfigTypes, NitroRouteRules as NitroRouteRulesTypes, NitroRuntimeHooks as NitroRuntimeHooksTypes, PrerenderRoute as PrerenderRouteTypes } from 'nitropack/types'\nimport type { RouteMeta } from 'vue-router'\nimport type { PageMeta } from '#app'\nimport type { sources as childSources } from '#sitemap-virtual/child-sources.mjs'\nimport type { sources as globalSources } from '#sitemap-virtual/global-sources.mjs'\nimport type { readSourcesFromFilesystem } from '#sitemap-virtual/read-sources.mjs'\nimport { describe, expectTypeOf, it } from 'vitest'\n\n// Tests the actual generated type augmentations from .nuxt/types/nuxt-sitemap-augments.d.ts\n// and virtual module declarations from .nuxt/types/nuxt-sitemap-virtual.d.ts.\n// Requires `nuxi prepare` to have been run so .nuxt/ exists.\n\ndescribe('nitropack augmentations', () => {\n  it('PrerenderRoute._sitemap is SitemapUrl', () => {\n    expectTypeOf<PrerenderRoute['_sitemap']>().toEqualTypeOf<SitemapUrl | undefined>()\n  })\n\n  it('NitroRouteRules.sitemap is SitemapItemDefaults | false', () => {\n    expectTypeOf<NitroRouteRules['sitemap']>().toEqualTypeOf<SitemapItemDefaults | false | undefined>()\n  })\n\n  it('NitroRouteConfig.sitemap is SitemapItemDefaults | false', () => {\n    expectTypeOf<NitroRouteConfig['sitemap']>().toEqualTypeOf<SitemapItemDefaults | false | undefined>()\n  })\n\n  it('NitroRuntimeHooks has all sitemap hooks', () => {\n    expectTypeOf<NitroRuntimeHooks['sitemap:index-resolved']>()\n      .toEqualTypeOf<(ctx: SitemapIndexRenderCtx) => void | Promise<void>>()\n    expectTypeOf<NitroRuntimeHooks['sitemap:input']>()\n      .toEqualTypeOf<(ctx: SitemapInputCtx) => void | Promise<void>>()\n    expectTypeOf<NitroRuntimeHooks['sitemap:resolved']>()\n      .toEqualTypeOf<(ctx: SitemapRenderCtx) => void | Promise<void>>()\n    expectTypeOf<NitroRuntimeHooks['sitemap:output']>()\n      .toEqualTypeOf<(ctx: SitemapOutputHookCtx) => void | Promise<void>>()\n    expectTypeOf<NitroRuntimeHooks['sitemap:sources']>()\n      .toEqualTypeOf<(ctx: SitemapSourcesHookCtx) => void | Promise<void>>()\n  })\n})\n\ndescribe('nitropack/types augmentations', () => {\n  it('PrerenderRoute._sitemap is SitemapUrl', () => {\n    expectTypeOf<PrerenderRouteTypes['_sitemap']>().toEqualTypeOf<SitemapUrl | undefined>()\n  })\n\n  it('NitroRouteRules.sitemap is SitemapItemDefaults | false', () => {\n    expectTypeOf<NitroRouteRulesTypes['sitemap']>().toEqualTypeOf<SitemapItemDefaults | false | undefined>()\n  })\n\n  it('NitroRouteConfig.sitemap is SitemapItemDefaults | false', () => {\n    expectTypeOf<NitroRouteConfigTypes['sitemap']>().toEqualTypeOf<SitemapItemDefaults | false | undefined>()\n  })\n\n  it('NitroRuntimeHooks has sitemap hooks', () => {\n    expectTypeOf<NitroRuntimeHooksTypes['sitemap:resolved']>()\n      .toEqualTypeOf<(ctx: SitemapRenderCtx) => void | Promise<void>>()\n  })\n})\n\ndescribe('vue-router augmentations', () => {\n  it('RouteMeta.sitemap is SitemapItemDefaults | false', () => {\n    expectTypeOf<RouteMeta['sitemap']>().toEqualTypeOf<SitemapItemDefaults | false | undefined>()\n  })\n})\n\ndescribe('#app augmentations', () => {\n  it('PageMeta.sitemap is SitemapItemDefaults | false', () => {\n    expectTypeOf<PageMeta['sitemap']>().toEqualTypeOf<SitemapItemDefaults | false | undefined>()\n  })\n})\n\ndescribe('#sitemap-virtual/read-sources.mjs', () => {\n  it('exports readSourcesFromFilesystem(filename: string) => Promise<any | null>', () => {\n    expectTypeOf<typeof readSourcesFromFilesystem>().toBeFunction()\n    expectTypeOf<typeof readSourcesFromFilesystem>().parameter(0).toBeString()\n    expectTypeOf<typeof readSourcesFromFilesystem>().returns.toEqualTypeOf<Promise<any | null>>()\n  })\n})\n\ndescribe('#sitemap-virtual/global-sources.mjs', () => {\n  it('exports sources as (SitemapSourceBase | SitemapSourceResolved)[]', () => {\n    expectTypeOf<typeof globalSources>().toEqualTypeOf<(SitemapSourceBase | SitemapSourceResolved)[]>()\n  })\n})\n\ndescribe('#sitemap-virtual/child-sources.mjs', () => {\n  it('exports sources as Record<string, (SitemapSourceBase | SitemapSourceResolved)[]>', () => {\n    expectTypeOf<typeof childSources>().toEqualTypeOf<Record<string, (SitemapSourceBase | SitemapSourceResolved)[]>>()\n  })\n})\n"
  },
  {
    "path": "test/types/tsconfig.json",
    "content": "{\n  \"extends\": \"../../.nuxt/tsconfig.json\",\n  \"include\": [\n    \"../../.nuxt/nuxt.d.ts\",\n    \"./**/*.ts\",\n    \"./**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "test/unit/i18n-disabled-routes.test.ts",
    "content": "import { expect, it } from 'vitest'\nimport { generatePathForI18nPages } from '../../src/utils-internal/i18n'\n\nit('should handle string paths for generatePathForI18nPages', () => {\n  const result = generatePathForI18nPages({\n    localeCode: 'en',\n    pageLocales: '/about',\n    nuxtI18nConfig: {\n      locales: ['en', 'fr'],\n      defaultLocale: 'en',\n      strategy: 'no_prefix',\n    },\n    normalisedLocales: [\n      { code: 'en', _hreflang: 'en-US', _sitemap: 'en' },\n      { code: 'fr', _hreflang: 'fr-FR', _sitemap: 'fr' },\n    ],\n  })\n\n  expect(result).toBe('/about')\n})\n\nit('handles false values in generatePathForI18nPages', () => {\n  // When false is passed, the function treats it as a path value\n  // The fix in the module prevents false from reaching this function\n  const result = generatePathForI18nPages({\n    localeCode: 'en',\n    pageLocales: false as any, // Intentionally passing wrong type\n    nuxtI18nConfig: {\n      locales: ['en', 'fr'],\n      defaultLocale: 'en',\n      strategy: 'no_prefix',\n    },\n    normalisedLocales: [\n      { code: 'en', _hreflang: 'en-US', _sitemap: 'en' },\n      { code: 'fr', _hreflang: 'fr-FR', _sitemap: 'fr' },\n    ],\n  })\n\n  // It returns false value as-is for no_prefix strategy\n  expect(result).toBe(false)\n})\n"
  },
  {
    "path": "test/unit/i18n-dynamic-routes.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { applyDynamicParams, findPageMapping } from '../../src/runtime/utils-pure'\n\ndescribe('i18n dynamic routes', () => {\n  const pages = {\n    'about': { en: '/about', fr: '/a-propos' },\n    'posts': { en: '/posts/[slug]', fr: '/article/[slug]', es: '/articulo/[slug]' },\n    'products': { en: '/products/[category]/[id]', fr: '/produits/[category]/[id]' },\n    'blog/posts': { en: '/blog/posts/[slug]', fr: '/blog/articles/[slug]' },\n  }\n\n  describe('findPageMapping', () => {\n    it('exact match for static route', () => {\n      const result = findPageMapping('/about', pages)\n      expect(result).toEqual({ mappings: pages.about, paramSegments: [] })\n    })\n\n    it('prefix match for single param route', () => {\n      const result = findPageMapping('/posts/my-slug', pages)\n      expect(result).toEqual({ mappings: pages.posts, paramSegments: ['my-slug'] })\n    })\n\n    it('prefix match for multi param route', () => {\n      const result = findPageMapping('/products/electronics/laptop-123', pages)\n      expect(result).toEqual({ mappings: pages.products, paramSegments: ['electronics', 'laptop-123'] })\n    })\n\n    it('matches most specific key first', () => {\n      const result = findPageMapping('/blog/posts/hello', pages)\n      expect(result).toEqual({ mappings: pages['blog/posts'], paramSegments: ['hello'] })\n    })\n\n    it('returns null for no match', () => {\n      const result = findPageMapping('/unknown/path', pages)\n      expect(result).toBeNull()\n    })\n\n    it('handles path without leading slash', () => {\n      const result = findPageMapping('posts/test', pages)\n      expect(result).toEqual({ mappings: pages.posts, paramSegments: ['test'] })\n    })\n  })\n\n  describe('applyDynamicParams', () => {\n    it('replaces single param', () => {\n      expect(applyDynamicParams('/article/[slug]', ['my-post'])).toBe('/article/my-post')\n    })\n\n    it('replaces multiple params', () => {\n      expect(applyDynamicParams('/produits/[category]/[id]', ['tech', 'item-1'])).toBe('/produits/tech/item-1')\n    })\n\n    it('returns path unchanged when no params', () => {\n      expect(applyDynamicParams('/about', [])).toBe('/about')\n    })\n\n    it('handles missing params gracefully', () => {\n      expect(applyDynamicParams('/[a]/[b]/[c]', ['x', 'y'])).toBe('/x/y/')\n    })\n  })\n})\n"
  },
  {
    "path": "test/unit/i18n.test.ts",
    "content": "import type { AutoI18nConfig } from '../../src/runtime/types'\nimport { describe, expect, it } from 'vitest'\nimport { normalizeLocales, splitPathForI18nLocales } from '../../src/utils-internal/i18n'\n\nconst EnFrAutoI18n = {\n  locales: normalizeLocales({ locales: [{\n    code: 'en',\n    iso: 'en-US',\n  }, {\n    code: 'fr',\n    iso: 'fr-FR',\n  }] }),\n  defaultLocale: 'en',\n  strategy: 'prefix_except_default',\n} as AutoI18nConfig\n\ndescribe('i18n', () => {\n  it('filtering prefix_except_default', async () => {\n    const data = splitPathForI18nLocales('/about', EnFrAutoI18n)\n    expect(data).toMatchInlineSnapshot(`\n      [\n        \"/about\",\n        \"/fr/about\",\n      ]\n    `)\n    const data2 = splitPathForI18nLocales('/fr/about', EnFrAutoI18n)\n    expect(data2).toMatchInlineSnapshot('\"/fr/about\"')\n  })\n  it('filtering prefix_and_default', async () => {\n    const data = splitPathForI18nLocales('/about', { ...EnFrAutoI18n, strategy: 'prefix_and_default' })\n    expect(data).toMatchInlineSnapshot(`\n      [\n        \"/about\",\n        \"/en/about\",\n        \"/fr/about\",\n      ]\n    `)\n    const data2 = splitPathForI18nLocales('/fr/about', { ...EnFrAutoI18n, strategy: 'prefix_and_default' })\n    expect(data2).toMatchInlineSnapshot('\"/fr/about\"')\n    const data3 = splitPathForI18nLocales('/en/about', { ...EnFrAutoI18n, strategy: 'prefix_and_default' })\n    expect(data3).toMatchInlineSnapshot('\"/en/about\"')\n  })\n  it('filtering prefix', async () => {\n    const data = splitPathForI18nLocales('/about', { ...EnFrAutoI18n, strategy: 'prefix' })\n    expect(data).toMatchInlineSnapshot(`\n      [\n        \"/about\",\n        \"/en/about\",\n        \"/fr/about\",\n      ]\n    `)\n    const data2 = splitPathForI18nLocales('/fr/about', { ...EnFrAutoI18n, strategy: 'prefix' })\n    expect(data2).toMatchInlineSnapshot('\"/fr/about\"')\n  })\n  it('normalizes locales', () => {\n    const locales = [{\n      code: 'en',\n      iso: 'en-US',\n    }, {\n      code: 'fr',\n      iso: 'fr-FR',\n    }, {\n      code: 'es',\n    }, 'br', {\n      code: 'xx',\n      language: 'xx-XX',\n    }]\n    // @ts-expect-error untyped\n    const data = normalizeLocales({ locales })\n    expect(data).toMatchInlineSnapshot(`\n      [\n        {\n          \"_hreflang\": \"en-US\",\n          \"_sitemap\": \"en-US\",\n          \"code\": \"en\",\n          \"iso\": \"en-US\",\n          \"language\": \"en-US\",\n        },\n        {\n          \"_hreflang\": \"fr-FR\",\n          \"_sitemap\": \"fr-FR\",\n          \"code\": \"fr\",\n          \"iso\": \"fr-FR\",\n          \"language\": \"fr-FR\",\n        },\n        {\n          \"_hreflang\": \"es\",\n          \"_sitemap\": \"es\",\n          \"code\": \"es\",\n        },\n        {\n          \"_hreflang\": \"br\",\n          \"_sitemap\": \"br\",\n          \"code\": \"br\",\n        },\n        {\n          \"_hreflang\": \"xx-XX\",\n          \"_sitemap\": \"xx-XX\",\n          \"code\": \"xx\",\n          \"language\": \"xx-XX\",\n        },\n      ]\n    `)\n  })\n})\n"
  },
  {
    "path": "test/unit/lastmod.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { isValidW3CDate, normaliseDate } from '../../src/runtime/server/sitemap/urlset/normalise'\n\ndescribe('lastmod', () => {\n  it('w3c validate', () => {\n    expect(isValidW3CDate('2023-12-21')).toBeTruthy()\n    expect(isValidW3CDate('2023-12-21T22:46:58Z')).toBeTruthy()\n    expect(isValidW3CDate('2023-12-21T22:46:58+00:00')).toBeTruthy()\n    expect(isValidW3CDate('2023-12-21T22:46:58.441+00:00')).toBeTruthy()\n    expect(isValidW3CDate('1994-11-05T13:15:30Z')).toBeTruthy()\n    expect(isValidW3CDate('1994-11-05T08:15:30-05:00')).toBeTruthy()\n    expect(isValidW3CDate('1994-11-05T08:15:30-05:00')).toBeTruthy()\n  })\n  it('date create', () => {\n    // time without timezone\n    expect(normaliseDate('2023-12-21T13:49:27.963745')).toMatchInlineSnapshot(`\"2023-12-21T13:49:27Z\"`)\n  })\n})\n"
  },
  {
    "path": "test/unit/normalise.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { preNormalizeEntry } from '../../src/runtime/server/sitemap/urlset/normalise'\n\ndescribe('normalise', () => {\n  it('query', async () => {\n    const normalisedWithoutSlash = preNormalizeEntry({ loc: '/query?foo=bar' })\n    expect(normalisedWithoutSlash).toMatchInlineSnapshot(`\n      {\n        \"_abs\": false,\n        \"_key\": \"/query?foo=bar\",\n        \"_path\": {\n          \"hash\": \"\",\n          \"pathname\": \"/query\",\n          \"search\": \"?foo=bar\",\n        },\n        \"_relativeLoc\": \"/query?foo=bar\",\n        \"loc\": \"/query?foo=bar\",\n      }\n    `)\n    const normalisedWithSlash = preNormalizeEntry({ loc: '/query/?foo=bar' })\n    expect(normalisedWithSlash).toMatchInlineSnapshot(`\n      {\n        \"_abs\": false,\n        \"_key\": \"/query?foo=bar\",\n        \"_path\": {\n          \"hash\": \"\",\n          \"pathname\": \"/query\",\n          \"search\": \"?foo=bar\",\n        },\n        \"_relativeLoc\": \"/query?foo=bar\",\n        \"loc\": \"/query?foo=bar\",\n      }\n    `)\n  })\n\n  it('encoding', () => {\n    const normalisedWithoutSlash = preNormalizeEntry({ loc: '/this/is a test' })\n    expect(normalisedWithoutSlash).toMatchInlineSnapshot(`\n      {\n        \"_abs\": false,\n        \"_key\": \"/this/is%20a%20test\",\n        \"_path\": {\n          \"hash\": \"\",\n          \"pathname\": \"/this/is a test\",\n          \"search\": \"\",\n        },\n        \"_relativeLoc\": \"/this/is%20a%20test\",\n        \"loc\": \"/this/is%20a%20test\",\n      }\n    `)\n    const withQuery = preNormalizeEntry({ loc: '/this/is a test?withAQuery=foo' })\n    expect(withQuery).toMatchInlineSnapshot(`\n      {\n        \"_abs\": false,\n        \"_key\": \"/this/is%20a%20test?withAQuery=foo\",\n        \"_path\": {\n          \"hash\": \"\",\n          \"pathname\": \"/this/is a test\",\n          \"search\": \"?withAQuery=foo\",\n        },\n        \"_relativeLoc\": \"/this/is%20a%20test?withAQuery=foo\",\n        \"loc\": \"/this/is%20a%20test?withAQuery=foo\",\n      }\n    `)\n    const withQueryWeird = preNormalizeEntry({ loc: '/this/is a test?with A some weirdformat=foo' })\n    expect(withQueryWeird).toMatchInlineSnapshot(`\n      {\n        \"_abs\": false,\n        \"_key\": \"/this/is%20a%20test?with+A+some+weirdformat=foo\",\n        \"_path\": {\n          \"hash\": \"\",\n          \"pathname\": \"/this/is a test\",\n          \"search\": \"?with A some weirdformat=foo\",\n        },\n        \"_relativeLoc\": \"/this/is%20a%20test?with+A+some+weirdformat=foo\",\n        \"loc\": \"/this/is%20a%20test?with+A+some+weirdformat=foo\",\n      }\n    `)\n  })\n\n  it('_encoded: true preserves pre-encoded URLs', () => {\n    // Test reserved characters - user pre-encoded with encodeURIComponent\n    const reservedChars = preNormalizeEntry({ loc: '/%24-%3A%29', _encoded: true })\n    expect(reservedChars.loc).toBe('/%24-%3A%29')\n\n    // Test pre-encoded emoji stays intact\n    const emoji = preNormalizeEntry({ loc: '/%F0%9F%98%85', _encoded: true })\n    expect(emoji.loc).toBe('/%F0%9F%98%85')\n\n    // Test unencoded URL stays as-is when _encoded: true (user's responsibility)\n    const unencoded = preNormalizeEntry({ loc: '/😅', _encoded: true })\n    expect(unencoded.loc).toBe('/😅')\n  })\n\n  it('default encoding behavior', () => {\n    // Emoji should be encoded\n    const emoji = preNormalizeEntry({ loc: '/😅' })\n    expect(emoji.loc).toBe('/%F0%9F%98%85')\n\n    // Space should be encoded\n    const space = preNormalizeEntry({ loc: '/hello world' })\n    expect(space.loc).toBe('/hello%20world')\n\n    // Reserved chars like $ and : are NOT encoded by encodePath (per RFC-3986)\n    const reserved = preNormalizeEntry({ loc: '/$-:)' })\n    expect(reserved.loc).toBe('/$-:)')\n  })\n})\n"
  },
  {
    "path": "test/unit/parseHtmlExtractSitemapMeta.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { parseHtmlExtractSitemapMeta } from '../../src/utils/parseHtmlExtractSitemapMeta'\n\ndescribe('parseHtmlExtractSitemapMeta', () => {\n  it('lastmod', async () => {\n    // test article meta\n    const output = parseHtmlExtractSitemapMeta(`\n    <head>\n      <meta property=\"article:published_time\" content=\"2021-04-01T00:00:00Z\">\n      <meta property=\"article:modified_time\" content=\"2021-04-02T00:00:00Z\">\n    </head>\n`)\n    expect(output).toMatchInlineSnapshot(`\n      {\n        \"lastmod\": \"2021-04-02T00:00:00Z\",\n      }\n    `)\n    // test article meta\n    const output2 = parseHtmlExtractSitemapMeta(`\n    <head>\n      <meta content=\"2021-04-01T00:00:00Z\" property=\"article:published_time\"/>\n      <meta content=\"2021-04-02T00:00:00Z\" property=\"article:modified_time\"/>\n    </head>\n`)\n    expect(output2).toMatchInlineSnapshot(`\n      {\n        \"lastmod\": \"2021-04-02T00:00:00Z\",\n      }\n    `)\n  })\n\n  it('extracts images from HTML', async () => {\n    const mainTag = '<main>'\n    const mainClosingTag = '</main>'\n    const discoverableImageHTML = `\n      <img\n        src=\"https://res.cloudinary.com/dl6o1xpyq/image/upload/f_jpg,q_auto:best,dpr_auto,w_240,h_240/images/harlan-wilton\"\n        alt=\"Harlan Wilton\"\n      />\n    `\n    const excludeImageDataHTML = `\n      <img\n        src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...\"\n      />\n    `\n\n    const excludeImageBlobHTML = `\n      <img\n        src=\"blob:http://example.com/12345678-1234-5678-1234-567812345678\"\n      />\n    `\n    const excludeImageFileHTML = `\n      <img\n        src=\"file:///C:/path/to/image.jpg\"\n      />\n    `\n\n    // Test case 1 - Single discoverable image\n    const html1 = `${mainTag}${discoverableImageHTML}${mainClosingTag}`\n    const testcase1 = parseHtmlExtractSitemapMeta(html1)\n\n    expect(testcase1).toMatchInlineSnapshot(`\n      {\n        \"images\": [\n          {\n            \"loc\": \"https://res.cloudinary.com/dl6o1xpyq/image/upload/f_jpg,q_auto:best,dpr_auto,w_240,h_240/images/harlan-wilton\",\n          },\n        ],\n      }\n    `)\n\n    // Test case 2 - Single discoverable image with excluded image values\n    const html2 = `${mainTag}${discoverableImageHTML}${excludeImageDataHTML}${excludeImageBlobHTML}${excludeImageFileHTML}${mainClosingTag}`\n    const testcase2 = parseHtmlExtractSitemapMeta(html2)\n\n    expect(testcase2).toMatchInlineSnapshot(`\n      {\n        \"images\": [\n          {\n            \"loc\": \"https://res.cloudinary.com/dl6o1xpyq/image/upload/f_jpg,q_auto:best,dpr_auto,w_240,h_240/images/harlan-wilton\",\n          },\n        ],\n      }\n    `)\n\n    const html3 = `<div id=\"__nuxt\"><div><main><div class=\"document-driven-page\"><!--[--><div><h1 id=\"index\"><!--[-->index<!--]--></h1><ul><!--[--><li><!--[--><a href=\"/bar\" class=\"\"><!--[-->/bar<!--]--></a><!--]--></li><li><!--[--><a href=\"/foo\" class=\"\"><!--[-->/foo<!--]--></a><!--]--></li><!--]--></ul><img onerror=\"this.setAttribute(&#39;data-error&#39;, 1)\" alt=\"Test image\" data-nuxt-img srcset=\"/_ipx/_/logo.svg 1x, /_ipx/_/logo.svg 2x\" src=\"/_ipx/_/logo.svg\" class=\"test\"><p><!--[--><a href=\"/sitemap.xml\" class=\"\"><!--[-->/sitemap.xml<!--]--></a><!--]--></p></div><!--]--></div></main></div></div><div id=\"teleports\"></div>`\n    const testcase3 = parseHtmlExtractSitemapMeta(html3)\n    expect(testcase3).toMatchInlineSnapshot(`\n      {\n        \"images\": [\n          {\n            \"loc\": \"/_ipx/_/logo.svg\",\n          },\n        ],\n      }\n    `)\n  })\n\n  it('video: ignores invalid markup', async () => {\n    const mainTag = '<main>'\n    const mainClosingTag = '</main>'\n    const discoverableVideoSrcHTML = `\n      <video\n        controls\n        src=\"https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4\"\n        width=\"620\"\n      >\n        Sorry, your browser doesn't support embedded videos, but don't worry, you\n        can\n        <a href=\"https://archive.org/details/BigBuckBunny_124\">download it</a>\n        and watch it with your favorite video player!\n      </video>\n    `\n\n    // Test case 1 - Single discoverable video src element\n    const html1 = `${mainTag}${discoverableVideoSrcHTML}${mainClosingTag}`\n    const testcase1 = parseHtmlExtractSitemapMeta(html1)\n\n    expect(testcase1).toMatchInlineSnapshot(`{}`)\n  })\n\n  it('video: simple valid markup', async () => {\n    const mainTag = '<main>'\n    const mainClosingTag = '</main>'\n\n    const discoverableVideoWithPosterSrcHTML = `\n      <video\n        controls\n        src=\"https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4\"\n        poster=\"https://archive.org/download/BigBuckBunny_124/__ia_thumb.jpg\"\n        width=\"620\"\n        data-title=\"Big Buck Bunny\"\n        data-description=\"Big Buck Bunny in DivX 720p.\"\n      >\n        Sorry, your browser doesn't support embedded videos, but don't worry, you\n        can\n        <a href=\"https://archive.org/details/BigBuckBunny_124\">download it</a>\n        and watch it with your favorite video player!\n      </video>\n    `\n    // Test case 2 - Single discoverable video src element with poster\n    const html2 = `${mainTag}${discoverableVideoWithPosterSrcHTML}${mainClosingTag}`\n    const testcase2 = parseHtmlExtractSitemapMeta(html2)\n\n    expect(testcase2).toMatchInlineSnapshot(`\n      {\n        \"videos\": [\n          {\n            \"content_loc\": \"https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4\",\n            \"description\": \"Big Buck Bunny in DivX 720p.\",\n            \"thumbnail_loc\": \"https://archive.org/download/BigBuckBunny_124/__ia_thumb.jpg\",\n            \"title\": \"Big Buck Bunny\",\n          },\n        ],\n      }\n    `)\n  })\n\n  it('extracts videos from HTML #3', async () => {\n    const mainTag = '<main>'\n    const mainClosingTag = '</main>'\n\n    const discoverableVideoSourcesHTML = `\n      <video\n        controls\n        width=\"620\"\n      >\n        <source\n          src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\"\n          type=\"video/mp4\"\n        />\n        <source\n          src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi\"\n          type=\"video/x-msvideo\"\n        />\n        Sorry, your browser doesn't support embedded videos, but don't worry, you\n        can\n        <a href=\"https://archive.org/details/DuckAndCover_185\">download it</a>\n        and watch it with your favorite video player!\n      </video>\n    `\n\n    // Test case 3 - Multiple discoverable video sources\n    const html3 = `${mainTag}${discoverableVideoSourcesHTML}${mainClosingTag}`\n    const testcase3 = parseHtmlExtractSitemapMeta(html3)\n\n    expect(testcase3).toMatchInlineSnapshot(`{}`)\n  })\n\n  it('extracts videos from HTML #4', async () => {\n    const mainTag = '<main>'\n    const mainClosingTag = '</main>'\n\n    const discoverableVideoSourcesWithPosterHTML = `\n      <video\n        controls\n        poster=\"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\"\n        width=\"620\"\n        data-title=\"Duck and Cover\"\n        data-description=\"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\"\n      >\n        <source\n          src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\"\n          type=\"video/mp4\"\n        />\n        <source\n          src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi\"\n          type=\"video/x-msvideo\"\n        />\n        Sorry, your browser doesn't support embedded videos, but don't worry, you\n        can\n        <a href=\"https://archive.org/details/DuckAndCover_185\">download it</a>\n        and watch it with your favorite video player!\n      </video>\n    `\n\n    // Test case 4 - Multiple discoverable video sources\n    const html4 = `${mainTag}${discoverableVideoSourcesWithPosterHTML}${mainClosingTag}`\n    const testcase4 = parseHtmlExtractSitemapMeta(html4)\n\n    expect(testcase4).toMatchInlineSnapshot(`\n      {\n        \"videos\": [\n          {\n            \"content_loc\": \"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\",\n            \"description\": \"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\",\n            \"thumbnail_loc\": \"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\",\n            \"title\": \"Duck and Cover\",\n          },\n          {\n            \"content_loc\": \"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi\",\n            \"description\": \"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\",\n            \"thumbnail_loc\": \"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\",\n            \"title\": \"Duck and Cover\",\n          },\n        ],\n      }\n    `)\n  })\n\n  it('extracts videos from HTML #5', async () => {\n    const mainTag = '<main>'\n    const mainClosingTag = '</main>'\n\n    const discoverableVideoWithPosterSrcHTML = `\n      <video\n        controls\n        src=\"https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4\"\n        poster=\"https://archive.org/download/BigBuckBunny_124/__ia_thumb.jpg\"\n        width=\"620\"\n        data-title=\"Big Buck Bunny\"\n        data-description=\"Big Buck Bunny in DivX 720p.\"\n      >\n        Sorry, your browser doesn't support embedded videos, but don't worry, you\n        can\n        <a href=\"https://archive.org/details/BigBuckBunny_124\">download it</a>\n        and watch it with your favorite video player!\n      </video>\n    `\n    const discoverableVideoSourcesWithPosterHTML = `\n      <video\n        controls\n        poster=\"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\"\n        width=\"620\"\n        data-title=\"Duck and Cover\"\n        data-description=\"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\"\n      >\n        <source\n          src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\"\n          type=\"video/mp4\"\n        />\n        <source\n          src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi\"\n          type=\"video/x-msvideo\"\n        />\n        Sorry, your browser doesn't support embedded videos, but don't worry, you\n        can\n        <a href=\"https://archive.org/details/DuckAndCover_185\">download it</a>\n        and watch it with your favorite video player!\n      </video>\n    `\n\n    // Test case 4 - Mixture of single video src and multiple discoverable video sources\n    const html5 = `${mainTag}${discoverableVideoWithPosterSrcHTML}${discoverableVideoSourcesWithPosterHTML}${mainClosingTag}`\n    const testcase5 = parseHtmlExtractSitemapMeta(html5)\n\n    expect(testcase5).toMatchInlineSnapshot(`\n      {\n        \"videos\": [\n          {\n            \"content_loc\": \"https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4\",\n            \"description\": \"Big Buck Bunny in DivX 720p.\",\n            \"thumbnail_loc\": \"https://archive.org/download/BigBuckBunny_124/__ia_thumb.jpg\",\n            \"title\": \"Big Buck Bunny\",\n          },\n          {\n            \"content_loc\": \"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\",\n            \"description\": \"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\",\n            \"thumbnail_loc\": \"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\",\n            \"title\": \"Duck and Cover\",\n          },\n          {\n            \"content_loc\": \"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi\",\n            \"description\": \"This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.\",\n            \"thumbnail_loc\": \"https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg\",\n            \"title\": \"Duck and Cover\",\n          },\n        ],\n      }\n    `)\n  })\n  it('extracts relative poster as absolute', async () => {\n    const testcase5 = parseHtmlExtractSitemapMeta(`\n<main>\n      <video\n        controls\n        src=\"https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4\"\n        poster=\"/poster.jpg\"\n        width=\"620\"\n        data-title=\"Big Buck Bunny\"\n        data-description=\"Big Buck Bunny in DivX 720p.\"\n      >\n              <source\n          src=\"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\"\n          type=\"video/mp4\"\n        />\n        </video>\n        </main>\n       `, {\n      videos: true,\n      resolveUrl(s) {\n        return s.startsWith('/') ? `https://example.com${s}` : s\n      },\n    })\n    expect(testcase5).toMatchInlineSnapshot(`\n      {\n        \"videos\": [\n          {\n            \"content_loc\": \"https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4\",\n            \"description\": \"Big Buck Bunny in DivX 720p.\",\n            \"thumbnail_loc\": \"https://example.com/poster.jpg\",\n            \"title\": \"Big Buck Bunny\",\n          },\n        ],\n      }\n    `)\n  })\n\n  it('blocks pages with noindex meta tag', async () => {\n    const noindex = parseHtmlExtractSitemapMeta(`\n      <head>\n        <meta name=\"robots\" content=\"noindex\">\n      </head>\n    `)\n    expect(noindex).toBe(null)\n\n    const noindexFollow = parseHtmlExtractSitemapMeta(`\n      <head>\n        <meta name=\"robots\" content=\"noindex, follow\">\n      </head>\n    `)\n    expect(noindexFollow).toBe(null)\n\n    const none = parseHtmlExtractSitemapMeta(`\n      <head>\n        <meta name=\"robots\" content=\"none\">\n      </head>\n    `)\n    expect(none).toBe(null)\n  })\n\n  it('extracts alternatives from hreflang links', () => {\n    const output = parseHtmlExtractSitemapMeta(`\n    <head>\n      <link rel=\"alternate\" hreflang=\"de-DE\" href=\"https://example.de/about\">\n      <link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://example.fr/about\">\n    </head>\n    `, { alternatives: true })\n    expect(output?.alternatives).toEqual([\n      { hreflang: 'de-DE', href: '/about' },\n      { hreflang: 'fr-FR', href: '/about' },\n    ])\n  })\n\n  it('skips alternatives when alternatives option is false', () => {\n    const output = parseHtmlExtractSitemapMeta(`\n    <head>\n      <link rel=\"alternate\" hreflang=\"de-DE\" href=\"https://example.de/about\">\n      <link rel=\"alternate\" hreflang=\"fr-FR\" href=\"https://example.fr/about\">\n    </head>\n    `, { alternatives: false })\n    expect(output?.alternatives).toBeUndefined()\n  })\n})\n"
  },
  {
    "path": "test/unit/parsePages.test.ts",
    "content": "import type { NuxtPage } from 'nuxt/schema'\nimport { describe, expect, it } from 'vitest'\nimport { normalizeLocales } from '../../src/utils-internal/i18n'\nimport { convertNuxtPagesToSitemapEntries } from '../../src/utils-internal/nuxtSitemap'\n\nconst payload: NuxtPage[] = [\n  {\n    name: 'index___en',\n    path: '/',\n    file: 'playground/pages/index.vue',\n    children: [],\n  },\n  {\n    name: 'index___fr',\n    path: '/fr',\n    file: 'playground/pages/index.vue',\n    children: [],\n  },\n  {\n    name: 'custom',\n    path: '/custom',\n    file: 'playground/pages/[...slug].vue',\n    meta: { sitemap: { lastmod: '2021-08-24T14:00:00.000Z' } },\n    children: [],\n  },\n  {\n    name: 'slug___en',\n    path: '/:slug(.*)*',\n    file: 'playground/pages/[...slug].vue',\n    children: [],\n  },\n  {\n    name: 'slug___fr',\n    path: '/fr/:slug(.*)*',\n    file: 'playground/pages/[...slug].vue',\n    children: [],\n  },\n  {\n    name: 'about___en',\n    path: '/about',\n    file: 'playground/pages/about.vue',\n    children: [],\n  },\n  {\n    name: 'about___fr',\n    path: '/fr/a-propos',\n    file: 'playground/pages/about.vue',\n    children: [],\n  },\n  {\n    path: '/blog',\n    file: 'playground/pages/blog.vue',\n    children: [{\n      name: 'blog-id___en',\n      path: ':id()',\n      file: 'playground/pages/blog/[id].vue',\n      children: [],\n    }, {\n      name: 'blog-categories___en',\n      path: 'categories',\n      file: 'playground/pages/blog/categories.vue',\n      children: [],\n    }, {\n      name: 'blog___en',\n      path: '',\n      file: 'playground/pages/blog/index.vue',\n      children: [],\n    }, {\n      name: 'blog-tags___en',\n      path: 'tags',\n      file: 'playground/pages/blog/tags.vue',\n      children: [{\n        name: 'blog-tags-edit___en',\n        path: 'edit',\n        file: 'playground/pages/blog/tags/edit.vue',\n        children: [],\n      }, {\n        name: 'blog-tags-new___en',\n        path: 'new',\n        file: 'playground/pages/blog/tags/new.vue',\n        children: [],\n      }],\n    }],\n  },\n  {\n    path: '/fr/blog',\n    file: 'playground/pages/blog.vue',\n    children: [{\n      name: 'blog-id___fr',\n      path: ':id()',\n      file: 'playground/pages/blog/[id].vue',\n      children: [],\n    }, {\n      name: 'blog-categories___fr',\n      path: 'categories',\n      file: 'playground/pages/blog/categories.vue',\n      children: [],\n    }, {\n      name: 'blog___fr',\n      path: '',\n      file: 'playground/pages/blog/index.vue',\n      children: [],\n    }, {\n      name: 'blog-tags___fr',\n      path: 'tags',\n      file: 'playground/pages/blog/tags.vue',\n      children: [{\n        name: 'blog-tags-edit___fr',\n        path: 'edit',\n        file: 'playground/pages/blog/tags/edit.vue',\n        children: [],\n      }, {\n        name: 'blog-tags-new___fr',\n        path: 'new',\n        file: 'playground/pages/blog/tags/new.vue',\n        children: [],\n      }],\n    }],\n  },\n  {\n    name: 'hidden-path-but-in-sitemap___en',\n    path: '/hidden-path-but-in-sitemap',\n    file: 'playground/pages/hidden-path-but-in-sitemap/index.vue',\n    children: [],\n  },\n  {\n    name: 'hidden-path-but-in-sitemap___fr',\n    path: '/fr/hidden-path-but-in-sitemap',\n    file: 'playground/pages/hidden-path-but-in-sitemap/index.vue',\n    children: [],\n  },\n  {\n    name: 'index___en',\n    path: '/',\n    file: 'playground/pages/index.vue',\n    children: [],\n  },\n  {\n    name: 'index___fr',\n    path: '/fr',\n    file: 'playground/pages/index.vue',\n    children: [],\n  },\n  {\n    name: 'new-page___en',\n    path: '/new-page',\n    file: 'playground/pages/new-page.vue',\n    children: [],\n  },\n  {\n    name: 'new-page___fr',\n    path: '/fr/new-page',\n    file: 'playground/pages/new-page.vue',\n    children: [],\n  },\n  {\n    name: 'secret___en',\n    path: '/secret',\n    file: 'playground/pages/secret.vue',\n    children: [],\n  },\n  {\n    name: 'secret___fr',\n    path: '/fr/secret',\n    file: 'playground/pages/secret.vue',\n    children: [],\n  },\n  {\n    name: 'users-group-id___en',\n    path: '/users-:group()/:id()',\n    file: 'playground/pages/users-[group]/[id].vue',\n    children: [],\n  },\n  {\n    name: 'users-group-id___fr',\n    path: '/fr/users-:group()/:id()',\n    file: 'playground/pages/users-[group]/[id].vue',\n    children: [],\n  },\n  {\n    name: 'users-group___en',\n    path: '/users-:group()',\n    file: 'playground/pages/users-[group]/index.vue',\n    children: [],\n  },\n  {\n    name: 'users-group___fr',\n    path: '/fr/users-:group()',\n    file: 'playground/pages/users-[group]/index.vue',\n    children: [],\n  },\n]\n\ndescribe('page parser', () => {\n  it('is parsed', () => {\n    expect(convertNuxtPagesToSitemapEntries(payload, {\n      filter: {\n        include: [],\n        exclude: [],\n      },\n      isI18nMapped: true,\n      autoLastmod: false,\n      defaultLocale: 'en',\n      normalisedLocales: normalizeLocales({ locales: [{ code: 'en' }, { code: 'fr' }] }),\n      strategy: 'no_prefix',\n      isI18nMicro: false,\n      autoI18n: true,\n    })).toMatchInlineSnapshot(`\n      [\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"lastmod\": \"2021-08-24T14:00:00.000Z\",\n          \"loc\": \"/custom\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/about\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/a-propos\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/about\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/about\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/about\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/a-propos\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/about\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/a-propos\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog/categories\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog/categories\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog/categories\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/blog/categories\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog/categories\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog/categories\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog/categories\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/blog/categories\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/blog\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/blog\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog/tags\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog/tags\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog/tags\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/blog/tags\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog/tags\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog/tags\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog/tags\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/blog/tags\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog/tags/edit\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog/tags/edit\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog/tags/edit\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/blog/tags/edit\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog/tags/edit\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog/tags/edit\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog/tags/edit\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/blog/tags/edit\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog/tags/new\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog/tags/new\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog/tags/new\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/blog/tags/new\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/blog/tags/new\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/blog/tags/new\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/blog/tags/new\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/blog/tags/new\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/hidden-path-but-in-sitemap\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/hidden-path-but-in-sitemap\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/hidden-path-but-in-sitemap\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/hidden-path-but-in-sitemap\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/hidden-path-but-in-sitemap\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/hidden-path-but-in-sitemap\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/hidden-path-but-in-sitemap\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/hidden-path-but-in-sitemap\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/new-page\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/new-page\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/new-page\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/new-page\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/new-page\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/new-page\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/new-page\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/new-page\",\n        },\n        {\n          \"_sitemap\": \"en\",\n          \"alternatives\": [\n            {\n              \"href\": \"/secret\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/secret\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/secret\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/secret\",\n        },\n        {\n          \"_sitemap\": \"fr\",\n          \"alternatives\": [\n            {\n              \"href\": \"/secret\",\n              \"hreflang\": \"en\",\n            },\n            {\n              \"href\": \"/fr/secret\",\n              \"hreflang\": \"fr\",\n            },\n            {\n              \"href\": \"/secret\",\n              \"hreflang\": \"x-default\",\n            },\n          ],\n          \"loc\": \"/fr/secret\",\n        },\n      ]\n    `)\n  })\n\n  it ('i18n micro', () => {\n    expect(convertNuxtPagesToSitemapEntries([\n      {\n        name: 'index',\n        path: '/:locale(de|ja)/page',\n        file: 'playground/pages/index.vue',\n        children: [],\n      },\n    ], {\n      filter: {\n        include: [],\n        exclude: [],\n      },\n      isI18nMapped: true,\n      autoLastmod: false,\n      defaultLocale: 'en',\n      normalisedLocales: normalizeLocales({ locales: [\n        { code: 'en', iso: 'en_EN' },\n        { code: 'de', iso: 'de_DE' },\n        { code: 'ru', iso: 'ru_RU' },\n      ] }),\n      strategy: 'prefix_except_default',\n      isI18nMicro: true,\n      autoI18n: true,\n    })).toMatchInlineSnapshot(`\n      [\n        {\n          \"_sitemap\": \"de_DE\",\n          \"alternatives\": [\n            {\n              \"href\": \"/de/page\",\n              \"hreflang\": \"de_DE\",\n            },\n            {\n              \"href\": \"/ja/page\",\n              \"hreflang\": undefined,\n            },\n          ],\n          \"loc\": \"/de/page\",\n        },\n        {\n          \"_sitemap\": \"index\",\n          \"alternatives\": [\n            {\n              \"href\": \"/de/page\",\n              \"hreflang\": \"de_DE\",\n            },\n            {\n              \"href\": \"/ja/page\",\n              \"hreflang\": undefined,\n            },\n          ],\n          \"loc\": \"/ja/page\",\n        },\n      ]\n    `)\n  })\n\n  it('autoI18n false disables hreflang alternatives', () => {\n    const result = convertNuxtPagesToSitemapEntries(payload, {\n      filter: {\n        include: [],\n        exclude: [],\n      },\n      isI18nMapped: false,\n      autoLastmod: false,\n      defaultLocale: 'en',\n      normalisedLocales: normalizeLocales({ locales: [{ code: 'en' }, { code: 'fr' }] }),\n      strategy: 'no_prefix',\n      isI18nMicro: false,\n      autoI18n: false,\n    })\n    // no entry should have alternatives when autoI18n is false\n    for (const entry of result) {\n      if (typeof entry === 'string')\n        continue\n      expect(entry).not.toHaveProperty('alternatives')\n    }\n  })\n})\n"
  },
  {
    "path": "test/unit/parseSitemapXml.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { parseSitemapXml } from '../../src/utils/parseSitemapXml'\n\ndescribe('parseSitemapXml', () => {\n  it('should extract loc, lastmod, changefreq, priority, images, videos, alternatives, and news from XML', async () => {\n    const xml = `\n      <urlset>\n        <url>\n          <loc>http://example.com/</loc>\n          <lastmod>2023-01-01</lastmod>\n          <changefreq>daily</changefreq>\n          <priority>0.8</priority>\n          <image:image>\n            <image:loc>http://example.com/image1.jpg</image:loc>\n          </image:image>\n          <video:video>\n            <video:title>Example Video</video:title>\n            <video:thumbnail_loc>http://example.com/thumbnail.jpg</video:thumbnail_loc>\n            <video:description>Example Description</video:description>\n            <video:content_loc>http://example.com/video1.mp4</video:content_loc>\n            <video:duration>600</video:duration>\n          </video:video>\n          <xhtml:link rel=\"alternate\" hreflang=\"en\" href=\"http://example.com/en\"/>\n          <news:news>\n            <news:title>Example News</news:title>\n            <news:publication_date>2023-01-01</news:publication_date>\n            <news:publication>\n              <news:name>Example Publication</news:name>\n              <news:language>en</news:language>\n            </news:publication>\n          </news:news>\n        </url>\n      </urlset>\n    `\n    const result = await parseSitemapXml(xml)\n    expect(result.urls).toMatchInlineSnapshot(`\n      [\n        {\n          \"alternatives\": [\n            {\n              \"href\": \"http://example.com/en\",\n              \"hreflang\": \"en\",\n            },\n          ],\n          \"changefreq\": \"daily\",\n          \"images\": [\n            {\n              \"loc\": \"http://example.com/image1.jpg\",\n            },\n          ],\n          \"lastmod\": \"2023-01-01\",\n          \"loc\": \"http://example.com/\",\n          \"news\": {\n            \"publication\": {\n              \"language\": \"en\",\n              \"name\": \"Example Publication\",\n            },\n            \"publication_date\": \"2023-01-01\",\n            \"title\": \"Example News\",\n          },\n          \"priority\": 0.8,\n          \"videos\": [\n            {\n              \"content_loc\": \"http://example.com/video1.mp4\",\n              \"description\": \"Example Description\",\n              \"duration\": 600,\n              \"thumbnail_loc\": \"http://example.com/thumbnail.jpg\",\n              \"title\": \"Example Video\",\n            },\n          ],\n        },\n      ]\n    `)\n  })\n\n  it('should handle missing optional fields', async () => {\n    const xml = `\n      <urlset>\n        <url>\n          <loc>http://example.com/</loc>\n          <lastmod>2023-01-01</lastmod>\n          <changefreq>daily</changefreq>\n        </url>\n      </urlset>\n    `\n    const result = await parseSitemapXml(xml)\n    expect(result.urls).toMatchInlineSnapshot(`\n      [\n        {\n          \"changefreq\": \"daily\",\n          \"lastmod\": \"2023-01-01\",\n          \"loc\": \"http://example.com/\",\n        },\n      ]\n    `)\n  })\n\n  it('should handle multiple images and videos', async () => {\n    const xml = `\n      <urlset>\n        <url>\n          <loc>http://example.com/</loc>\n          <image:image>\n            <image:loc>http://example.com/image1.jpg</image:loc>\n          </image:image>\n          <image:image>\n            <image:loc>http://example.com/image2.jpg</image:loc>\n          </image:image>\n          <video:video>\n            <video:title>Example Video 1</video:title>\n            <video:thumbnail_loc>http://example.com/thumbnail1.jpg</video:thumbnail_loc>\n            <video:description>Example Description 1</video:description>\n            <video:content_loc>http://example.com/video1.mp4</video:content_loc>\n          </video:video>\n          <video:video>\n            <video:title>Example Video 2</video:title>\n            <video:thumbnail_loc>http://example.com/thumbnail2.jpg</video:thumbnail_loc>\n            <video:description>Example Description 2</video:description>\n            <video:content_loc>http://example.com/video2.mp4</video:content_loc>\n          </video:video>\n        </url>\n      </urlset>\n    `\n    const result = await parseSitemapXml(xml)\n    expect(result.urls).toMatchInlineSnapshot(`\n      [\n        {\n          \"images\": [\n            {\n              \"loc\": \"http://example.com/image1.jpg\",\n            },\n            {\n              \"loc\": \"http://example.com/image2.jpg\",\n            },\n          ],\n          \"loc\": \"http://example.com/\",\n          \"videos\": [\n            {\n              \"content_loc\": \"http://example.com/video1.mp4\",\n              \"description\": \"Example Description 1\",\n              \"thumbnail_loc\": \"http://example.com/thumbnail1.jpg\",\n              \"title\": \"Example Video 1\",\n            },\n            {\n              \"content_loc\": \"http://example.com/video2.mp4\",\n              \"description\": \"Example Description 2\",\n              \"thumbnail_loc\": \"http://example.com/thumbnail2.jpg\",\n              \"title\": \"Example Video 2\",\n            },\n          ],\n        },\n      ]\n    `)\n  })\n\n  it('should handle missing loc, lastmod, and changefreq', async () => {\n    const xml = `\n      <urlset>\n        <url>\n          <image:image>\n            <image:loc>http://example.com/image1.jpg</image:loc>\n          </image:image>\n        </url>\n      </urlset>\n    `\n    const result = await parseSitemapXml(xml)\n    expect(result.urls).toMatchInlineSnapshot(`[]`)\n  })\n\n  it('should throw error if no URLs are found', async () => {\n    const xml = '<urlset></urlset>'\n    await expect(() => parseSitemapXml(xml)).rejects.toThrow('XML does not contain a valid urlset element')\n  })\n\n  it('should handle malformed XML', async () => {\n    const xml = `\n      <urlset>\n        <url>\n          <ldoc>http://example.com/\n          <lastmod>2023-01-01</lastmod>\n          <changefreq>daily</changefreq>\n        </url>\n      </urlset>\n    `\n    const result = await parseSitemapXml(xml)\n    expect(result.urls).toMatchInlineSnapshot(`[]`)\n  })\n\n  it('should handle XML with unexpected tags', async () => {\n    const xml = `\n      <urlset>\n        <url>\n          <loc>http://example.com/</loc>\n          <unexpectedTag>unexpectedValue</unexpectedTag>\n        </url>\n      </urlset>\n    `\n    const result = await parseSitemapXml(xml)\n    expect(result.urls).toMatchInlineSnapshot(`\n      [\n        {\n          \"loc\": \"http://example.com/\",\n        },\n      ]\n    `)\n  })\n\n  describe('malformed XML and edge cases', () => {\n    it('should throw error for completely invalid XML', async () => {\n      const xml = 'not xml at all'\n      await expect(() => parseSitemapXml(xml)).rejects.toThrow('XML does not contain a valid urlset element')\n    })\n\n    it('should throw error for XML with invalid structure', async () => {\n      const xml = '<invalid><structure>'\n      await expect(() => parseSitemapXml(xml)).rejects.toThrow('XML does not contain a valid urlset element')\n    })\n\n    it('should throw error for XML without urlset', async () => {\n      const xml = '<root><other>content</other></root>'\n      await expect(() => parseSitemapXml(xml)).rejects.toThrow('XML does not contain a valid urlset element')\n    })\n\n    it('should throw error for empty XML', async () => {\n      const xml = ''\n      await expect(() => parseSitemapXml(xml)).rejects.toThrow('Empty XML input provided')\n    })\n\n    it('should handle XML with unclosed tags', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/\n            <lastmod>2023-01-01</lastmod>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toEqual([])\n    })\n\n    it('should handle XML with CDATA sections', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc><![CDATA[http://example.com/special&chars]]></loc>\n            <lastmod>2023-01-01</lastmod>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toMatchInlineSnapshot(`\n        [\n          {\n            \"lastmod\": \"2023-01-01\",\n            \"loc\": \"http://example.com/special&chars\",\n          },\n        ]\n      `)\n    })\n\n    it('should handle XML with HTML entities', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/path?param1=value&amp;param2=value</loc>\n            <lastmod>2023-01-01</lastmod>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toMatchInlineSnapshot(`\n        [\n          {\n            \"lastmod\": \"2023-01-01\",\n            \"loc\": \"http://example.com/path?param1=value&param2=value\",\n          },\n        ]\n      `)\n    })\n  })\n\n  describe('xML namespace handling', () => {\n    it('should handle mixed namespace prefixes', async () => {\n      const xml = `\n        <urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n                xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\n                xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\">\n          <url>\n            <loc>http://example.com/</loc>\n            <image:image>\n              <image:loc>http://example.com/image.jpg</image:loc>\n            </image:image>\n            <video:video>\n              <video:title>Test Video</video:title>\n              <video:thumbnail_loc>http://example.com/thumb.jpg</video:thumbnail_loc>\n              <video:description>Test Description</video:description>\n              <video:content_loc>http://example.com/video.mp4</video:content_loc>\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0]).toMatchObject({\n        loc: 'http://example.com/',\n        images: [{ loc: 'http://example.com/image.jpg' }],\n        videos: [{\n          title: 'Test Video',\n          thumbnail_loc: 'http://example.com/thumb.jpg',\n          description: 'Test Description',\n          content_loc: 'http://example.com/video.mp4',\n        }],\n      })\n    })\n\n    it('should handle different namespace prefixes', async () => {\n      const xml = `\n        <sitemap:urlset xmlns:sitemap=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n                        xmlns:img=\"http://www.google.com/schemas/sitemap-image/1.1\">\n          <sitemap:url>\n            <sitemap:loc>http://example.com/</sitemap:loc>\n            <img:image>\n              <img:loc>http://example.com/image.jpg</img:loc>\n            </img:image>\n          </sitemap:url>\n        </sitemap:urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toMatchInlineSnapshot(`\n        [\n          {\n            \"images\": [\n              {\n                \"loc\": \"http://example.com/image.jpg\",\n              },\n            ],\n            \"loc\": \"http://example.com/\",\n          },\n        ]\n      `)\n    })\n  })\n\n  describe('complex video attributes and nested elements', () => {\n    it('should handle video with all optional attributes', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <video:video>\n              <video:title>Complete Video</video:title>\n              <video:thumbnail_loc>http://example.com/thumb.jpg</video:thumbnail_loc>\n              <video:description>Complete description</video:description>\n              <video:content_loc>http://example.com/video.mp4</video:content_loc>\n              <video:player_loc>http://example.com/player</video:player_loc>\n              <video:duration>3600</video:duration>\n              <video:expiration_date>2024-12-31</video:expiration_date>\n              <video:rating>4.5</video:rating>\n              <video:view_count>10000</video:view_count>\n              <video:publication_date>2023-01-01</video:publication_date>\n              <video:family_friendly>yes</video:family_friendly>\n              <video:requires_subscription>no</video:requires_subscription>\n              <video:live>no</video:live>\n              <video:tag>action</video:tag>\n              <video:tag>adventure</video:tag>\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].videos[0]).toMatchObject({\n        title: 'Complete Video',\n        thumbnail_loc: 'http://example.com/thumb.jpg',\n        description: 'Complete description',\n        content_loc: 'http://example.com/video.mp4',\n        player_loc: 'http://example.com/player',\n        duration: 3600,\n        expiration_date: '2024-12-31',\n        rating: 4.5,\n        view_count: 10000,\n        publication_date: '2023-01-01',\n        family_friendly: 'yes',\n        requires_subscription: 'no',\n        live: 'no',\n        tag: ['action', 'adventure'],\n      })\n    })\n\n    it('should handle video with restrictions and platform attributes', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <video:video>\n              <video:title>Restricted Video</video:title>\n              <video:thumbnail_loc>http://example.com/thumb.jpg</video:thumbnail_loc>\n              <video:description>Restricted content</video:description>\n              <video:content_loc>http://example.com/video.mp4</video:content_loc>\n              <video:restriction>\n                <video:relationship>allow</video:relationship>\n                US CA\n              </video:restriction>\n              <video:platform>\n                <video:relationship>deny</video:relationship>\n                mobile\n              </video:platform>\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].videos[0]).toMatchObject({\n        title: 'Restricted Video',\n        restriction: {\n          relationship: 'allow',\n          restriction: 'US CA',\n        },\n        platform: {\n          relationship: 'deny',\n          platform: 'mobile',\n        },\n      })\n    })\n\n    it('should handle video with price information', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <video:video>\n              <video:title>Paid Video</video:title>\n              <video:thumbnail_loc>http://example.com/thumb.jpg</video:thumbnail_loc>\n              <video:description>Premium content</video:description>\n              <video:content_loc>http://example.com/video.mp4</video:content_loc>\n              <video:price>\n                <video:currency>USD</video:currency>\n                <video:type>rent</video:type>\n                3.99\n              </video:price>\n              <video:price>\n                <video:currency>USD</video:currency>\n                <video:type>purchase</video:type>\n                9.99\n              </video:price>\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].videos[0].price).toEqual([\n        { price: '3.99', currency: 'USD', type: 'rent' },\n        { price: '9.99', currency: 'USD', type: 'purchase' },\n      ])\n    })\n\n    it('should handle video with uploader information', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <video:video>\n              <video:title>User Video</video:title>\n              <video:thumbnail_loc>http://example.com/thumb.jpg</video:thumbnail_loc>\n              <video:description>User generated content</video:description>\n              <video:content_loc>http://example.com/video.mp4</video:content_loc>\n              <video:uploader>\n                <video:info>http://example.com/user</video:info>\n                John Doe\n              </video:uploader>\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].videos[0].uploader).toEqual({\n        uploader: 'John Doe',\n        info: 'http://example.com/user',\n      })\n    })\n\n    it('should filter out invalid videos missing required fields', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <video:video>\n              <video:title>Incomplete Video 1</video:title>\n              <!-- Missing thumbnail_loc, description, and content_loc -->\n            </video:video>\n            <video:video>\n              <video:title>Complete Video</video:title>\n              <video:thumbnail_loc>http://example.com/thumb.jpg</video:thumbnail_loc>\n              <video:description>Complete description</video:description>\n              <video:content_loc>http://example.com/video.mp4</video:content_loc>\n            </video:video>\n            <video:video>\n              <video:thumbnail_loc>http://example.com/thumb2.jpg</video:thumbnail_loc>\n              <video:description>Missing title</video:description>\n              <video:content_loc>http://example.com/video2.mp4</video:content_loc>\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].videos).toHaveLength(1)\n      expect(result.urls[0].videos[0].title).toBe('Complete Video')\n    })\n  })\n\n  describe('empty and null values', () => {\n    it('should handle empty values gracefully', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <lastmod></lastmod>\n            <changefreq></changefreq>\n            <priority></priority>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toMatchInlineSnapshot(`\n        [\n          {\n            \"loc\": \"http://example.com/\",\n          },\n        ]\n      `)\n    })\n\n    it('should handle whitespace-only values', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>   http://example.com/   </loc>\n            <lastmod>  </lastmod>\n            <changefreq>   </changefreq>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].loc).toBe('http://example.com/') // trimValues is true so whitespace is trimmed\n      expect(result.urls[0]).not.toHaveProperty('lastmod')\n      expect(result.urls[0]).not.toHaveProperty('changefreq')\n    })\n\n    it('should handle multiple URLs with mixed validity', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/1</loc>\n            <lastmod>2023-01-01</lastmod>\n          </url>\n          <url>\n            <!-- Missing loc -->\n            <lastmod>2023-01-02</lastmod>\n          </url>\n          <url>\n            <loc>http://example.com/3</loc>\n            <priority>0.5</priority>\n          </url>\n          <url>\n            <loc></loc>\n            <lastmod>2023-01-04</lastmod>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(2)\n      expect(result.urls[0].loc).toBe('http://example.com/1')\n      expect(result.urls[1].loc).toBe('http://example.com/3')\n    })\n\n    it('should handle special characters in URLs', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/path with spaces</loc>\n            <lastmod>2023-01-01</lastmod>\n          </url>\n          <url>\n            <loc>http://example.com/中文路径</loc>\n            <lastmod>2023-01-02</lastmod>\n          </url>\n          <url>\n            <loc>http://example.com/émoji-🚀-path</loc>\n            <lastmod>2023-01-03</lastmod>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(3)\n      expect(result.urls[0].loc).toBe('http://example.com/path with spaces')\n      expect(result.urls[1].loc).toBe('http://example.com/中文路径')\n      expect(result.urls[2].loc).toBe('http://example.com/émoji-🚀-path')\n    })\n\n    it('should handle images without loc', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <image:image>\n              <image:loc>http://example.com/valid.jpg</image:loc>\n            </image:image>\n            <image:image>\n              <!-- Missing image:loc -->\n            </image:image>\n            <image:image>\n              <image:loc></image:loc>\n            </image:image>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].images).toHaveLength(1)\n      expect(result.urls[0].images[0].loc).toBe('http://example.com/valid.jpg')\n    })\n\n    it('should handle news with missing required fields', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <news:news>\n              <news:title>Valid News</news:title>\n              <news:publication_date>2023-01-01</news:publication_date>\n              <news:publication>\n                <news:name>Example News</news:name>\n                <news:language>en</news:language>\n              </news:publication>\n            </news:news>\n          </url>\n          <url>\n            <loc>http://example.com/2</loc>\n            <news:news>\n              <!-- Missing required fields -->\n              <news:title>Incomplete News</news:title>\n            </news:news>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0]).toHaveProperty('news')\n      expect(result.urls[1]).not.toHaveProperty('news')\n    })\n  })\n\n  describe('priority and changefreq edge cases', () => {\n    it('should handle various priority formats', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/1</loc>\n            <priority>0.0</priority>\n          </url>\n          <url>\n            <loc>http://example.com/2</loc>\n            <priority>1.0</priority>\n          </url>\n          <url>\n            <loc>http://example.com/3</loc>\n            <priority>0.5</priority>\n          </url>\n          <url>\n            <loc>http://example.com/4</loc>\n            <priority>invalid</priority>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].priority).toBe(0.0)\n      expect(result.urls[1].priority).toBe(1.0)\n      expect(result.urls[2].priority).toBe(0.5)\n      expect(result.urls[3]).not.toHaveProperty('priority') // Invalid priority is filtered out\n    })\n\n    it('should handle all valid changefreq values', async () => {\n      const frequencies = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never']\n      const xml = `\n        <urlset>\n          ${frequencies.map((freq, i) => `\n            <url>\n              <loc>http://example.com/${i}</loc>\n              <changefreq>${freq}</changefreq>\n            </url>\n          `).join('')}\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      frequencies.forEach((freq, i) => {\n        expect(result.urls[i].changefreq).toBe(freq)\n      })\n    })\n  })\n\n  describe('alternatives handling', () => {\n    it('should handle multiple alternatives with different hreflang values', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <xhtml:link>\n              <xhtml:rel>alternate</xhtml:rel>\n              <xhtml:hreflang>en</xhtml:hreflang>\n              <xhtml:href>http://example.com/en</xhtml:href>\n            </xhtml:link>\n            <xhtml:link>\n              <xhtml:rel>alternate</xhtml:rel>\n              <xhtml:hreflang>fr</xhtml:hreflang>\n              <xhtml:href>http://example.com/fr</xhtml:href>\n            </xhtml:link>\n            <xhtml:link>\n              <xhtml:rel>alternate</xhtml:rel>\n              <xhtml:hreflang>es</xhtml:hreflang>\n              <xhtml:href>http://example.com/es</xhtml:href>\n            </xhtml:link>\n            <xhtml:link>\n              <xhtml:rel>alternate</xhtml:rel>\n              <xhtml:hreflang>x-default</xhtml:hreflang>\n              <xhtml:href>http://example.com/</xhtml:href>\n            </xhtml:link>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].alternatives).toHaveLength(4)\n      expect(result.urls[0].alternatives).toEqual([\n        { hreflang: 'en', href: 'http://example.com/en' },\n        { hreflang: 'fr', href: 'http://example.com/fr' },\n        { hreflang: 'es', href: 'http://example.com/es' },\n        { hreflang: 'x-default', href: 'http://example.com/' },\n      ])\n    })\n\n    it('should filter out invalid alternative links', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <xhtml:link rel=\"alternate\" hreflang=\"en\" href=\"http://example.com/en\" />\n            <xhtml:link rel=\"canonical\" href=\"http://example.com/\" />\n            <xhtml:link rel=\"alternate\" href=\"http://example.com/missing-hreflang\" />\n            <xhtml:link rel=\"alternate\" hreflang=\"fr\" />\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls[0].alternatives).toHaveLength(1)\n      expect(result.urls[0].alternatives[0]).toEqual({ hreflang: 'en', href: 'http://example.com/en' })\n    })\n  })\n\n  describe('parseSitemapXml with warnings', () => {\n    it('should throw error for invalid XML', async () => {\n      const xml = 'not xml at all'\n      await expect(() => parseSitemapXml(xml)).rejects.toThrow('XML does not contain a valid urlset element')\n    })\n\n    it('should throw error for empty XML', async () => {\n      const xml = ''\n      await expect(() => parseSitemapXml(xml)).rejects.toThrow('Empty XML input provided')\n    })\n\n    it('should throw error for XML without urlset', async () => {\n      const xml = '<root><other>content</other></root>'\n      await expect(() => parseSitemapXml(xml)).rejects.toThrow('XML does not contain a valid urlset element')\n    })\n\n    it('should throw error for sitemap with no URL entries', async () => {\n      const xml = '<urlset></urlset>'\n      await expect(() => parseSitemapXml(xml)).rejects.toThrow('XML does not contain a valid urlset element')\n    })\n\n    it('should return warnings for URLs missing loc', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <lastmod>2023-01-01</lastmod>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toEqual([])\n      expect(result.warnings).toHaveLength(2)\n      expect(result.warnings[0].type).toBe('validation')\n      expect(result.warnings[0].message).toBe('URL entry missing required loc element')\n      expect(result.warnings[1].message).toBe('No valid URLs found in sitemap after validation')\n    })\n\n    it('should return warnings for invalid changefreq values', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <changefreq>invalid</changefreq>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(1)\n      expect(result.warnings).toHaveLength(1)\n      expect(result.warnings[0].type).toBe('validation')\n      expect(result.warnings[0].message).toBe('Invalid changefreq value')\n      expect(result.warnings[0].context?.field).toBe('changefreq')\n    })\n\n    it('should return warnings for out-of-range priority values', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <priority>1.5</priority>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(1)\n      expect(result.urls[0].priority).toBe(1.0) // clamped\n      expect(result.warnings).toHaveLength(1)\n      expect(result.warnings[0].type).toBe('validation')\n      expect(result.warnings[0].message).toBe('Priority value should be between 0.0 and 1.0, clamping to valid range')\n    })\n\n    it('should return warnings for videos missing required fields', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <video:video>\n              <video:title>Incomplete Video</video:title>\n              <!-- Missing required fields -->\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(1)\n      expect(result.urls[0]).not.toHaveProperty('videos')\n      expect(result.warnings).toHaveLength(1)\n      expect(result.warnings[0].type).toBe('validation')\n      expect(result.warnings[0].message).toContain('Video missing required fields')\n    })\n\n    it('should return warnings for invalid video rating values', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <video:video>\n              <video:title>Test Video</video:title>\n              <video:thumbnail_loc>http://example.com/thumb.jpg</video:thumbnail_loc>\n              <video:description>Test Description</video:description>\n              <video:content_loc>http://example.com/video.mp4</video:content_loc>\n              <video:rating>10</video:rating>\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(1)\n      expect(result.warnings).toHaveLength(1)\n      expect(result.warnings[0].type).toBe('validation')\n      expect(result.warnings[0].message).toBe('Video rating should be between 0.0 and 5.0')\n    })\n\n    it('should return warnings for invalid video family_friendly values', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <video:video>\n              <video:title>Test Video</video:title>\n              <video:thumbnail_loc>http://example.com/thumb.jpg</video:thumbnail_loc>\n              <video:description>Test Description</video:description>\n              <video:content_loc>http://example.com/video.mp4</video:content_loc>\n              <video:family_friendly>maybe</video:family_friendly>\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(1)\n      expect(result.warnings).toHaveLength(1)\n      expect(result.warnings[0].type).toBe('validation')\n      expect(result.warnings[0].message).toBe('Invalid video family_friendly value, should be \"yes\" or \"no\"')\n    })\n\n    it('should return warnings for news entries missing required fields', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/</loc>\n            <news:news>\n              <news:title>Incomplete News</news:title>\n              <!-- Missing publication_date and publication -->\n            </news:news>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(1)\n      expect(result.urls[0]).not.toHaveProperty('news')\n      expect(result.warnings).toHaveLength(1)\n      expect(result.warnings[0].type).toBe('validation')\n      expect(result.warnings[0].message).toContain('News entry missing required fields')\n    })\n\n    it('should collect multiple warnings for different issues', async () => {\n      const xml = `\n        <urlset>\n          <url>\n            <loc>http://example.com/1</loc>\n            <changefreq>invalid</changefreq>\n            <priority>2.0</priority>\n          </url>\n          <url>\n            <!-- Missing loc -->\n            <lastmod>2023-01-01</lastmod>\n          </url>\n          <url>\n            <loc>http://example.com/3</loc>\n            <video:video>\n              <video:title>Incomplete Video</video:title>\n              <!-- Missing required video fields -->\n            </video:video>\n          </url>\n        </urlset>\n      `\n      const result = await parseSitemapXml(xml)\n      expect(result.urls).toHaveLength(2) // Only valid URLs\n      expect(result.warnings.length).toBeGreaterThan(3) // Multiple warnings\n\n      const warningTypes = result.warnings.map(w => w.type)\n      expect(warningTypes).toContain('validation')\n    })\n  })\n})\n"
  },
  {
    "path": "test/unit/sitemapIndex.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { isSitemapIndex, parseSitemapIndex } from '../../src/utils'\n\ndescribe('isSitemapIndex', () => {\n  it('detects sitemap index with opening tag', () => {\n    expect(isSitemapIndex('<sitemapindex>')).toBe(true)\n  })\n\n  it('detects sitemap index with namespace', () => {\n    expect(isSitemapIndex('<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">')).toBe(true)\n  })\n\n  it('detects sitemap index with closing tag only', () => {\n    expect(isSitemapIndex('</sitemapindex>')).toBe(true)\n  })\n\n  it('returns false for urlset sitemap', () => {\n    expect(isSitemapIndex('<urlset><url><loc>https://example.com</loc></url></urlset>')).toBe(false)\n  })\n\n  it('returns false for empty string', () => {\n    expect(isSitemapIndex('')).toBe(false)\n  })\n})\n\ndescribe('parseSitemapIndex', () => {\n  it('parses basic sitemap index', async () => {\n    const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <sitemap>\n    <loc>https://example.com/sitemap-1.xml</loc>\n  </sitemap>\n  <sitemap>\n    <loc>https://example.com/sitemap-2.xml</loc>\n  </sitemap>\n</sitemapindex>`\n\n    const { entries, warnings } = await parseSitemapIndex(xml)\n    expect(entries).toEqual([\n      { loc: 'https://example.com/sitemap-1.xml' },\n      { loc: 'https://example.com/sitemap-2.xml' },\n    ])\n    expect(warnings).toEqual([])\n  })\n\n  it('parses sitemap index with lastmod', async () => {\n    const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <sitemap>\n    <loc>https://example.com/sitemap-1.xml</loc>\n    <lastmod>2024-01-15</lastmod>\n  </sitemap>\n</sitemapindex>`\n\n    const { entries, warnings } = await parseSitemapIndex(xml)\n    expect(entries).toEqual([\n      { loc: 'https://example.com/sitemap-1.xml', lastmod: '2024-01-15' },\n    ])\n    expect(warnings).toEqual([])\n  })\n\n  it('handles single sitemap entry', async () => {\n    const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <sitemap>\n    <loc>https://example.com/sitemap.xml</loc>\n  </sitemap>\n</sitemapindex>`\n\n    const { entries } = await parseSitemapIndex(xml)\n    expect(entries).toHaveLength(1)\n    expect(entries[0].loc).toBe('https://example.com/sitemap.xml')\n  })\n\n  it('returns empty array for empty sitemapindex', async () => {\n    const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n</sitemapindex>`\n\n    const { entries, warnings } = await parseSitemapIndex(xml)\n    expect(entries).toEqual([])\n    expect(warnings).toEqual([])\n  })\n\n  it('warns on entries without loc', async () => {\n    const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <sitemap>\n    <lastmod>2024-01-15</lastmod>\n  </sitemap>\n  <sitemap>\n    <loc>https://example.com/valid.xml</loc>\n  </sitemap>\n</sitemapindex>`\n\n    const { entries, warnings } = await parseSitemapIndex(xml)\n    expect(entries).toEqual([\n      { loc: 'https://example.com/valid.xml' },\n    ])\n    expect(warnings).toHaveLength(1)\n    expect(warnings[0].message).toBe('Sitemap entry missing required loc element')\n  })\n\n  it('warns on invalid URLs', async () => {\n    const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <sitemap>\n    <loc>not-a-url</loc>\n  </sitemap>\n  <sitemap>\n    <loc>https://example.com/valid.xml</loc>\n  </sitemap>\n</sitemapindex>`\n\n    const { entries, warnings } = await parseSitemapIndex(xml)\n    expect(entries).toEqual([\n      { loc: 'https://example.com/valid.xml' },\n    ])\n    expect(warnings).toHaveLength(1)\n    expect(warnings[0].message).toBe('Sitemap entry has invalid URL')\n    expect(warnings[0].context?.url).toBe('not-a-url')\n  })\n\n  it('trims whitespace from values', async () => {\n    const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <sitemap>\n    <loc>  https://example.com/sitemap.xml  </loc>\n    <lastmod>  2024-01-15  </lastmod>\n  </sitemap>\n</sitemapindex>`\n\n    const { entries } = await parseSitemapIndex(xml)\n    expect(entries[0].loc).toBe('https://example.com/sitemap.xml')\n    expect(entries[0].lastmod).toBe('2024-01-15')\n  })\n\n  it('throws on empty input', async () => {\n    await expect(parseSitemapIndex('')).rejects.toThrow('Empty XML input provided')\n  })\n\n  it('throws on non-sitemapindex XML', async () => {\n    const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <url><loc>https://example.com</loc></url>\n</urlset>`\n\n    await expect(parseSitemapIndex(xml)).rejects.toThrow('XML does not contain a valid sitemapindex element')\n  })\n})\n"
  },
  {
    "path": "test/unit/sorting.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { sortInPlace } from '../../src/runtime/server/sitemap/urlset/sort'\n\ndescribe('sorting', () => {\n  it('default', async () => {\n    const data = sortInPlace([\n      { loc: '/a' },\n      { loc: '/b' },\n      { loc: '/c' },\n      { loc: '/1' },\n      { loc: '/2' },\n      { loc: '/10' },\n    ])\n    expect(data).toMatchInlineSnapshot(`\n      [\n        {\n          \"loc\": \"/1\",\n        },\n        {\n          \"loc\": \"/2\",\n        },\n        {\n          \"loc\": \"/10\",\n        },\n        {\n          \"loc\": \"/a\",\n        },\n        {\n          \"loc\": \"/b\",\n        },\n        {\n          \"loc\": \"/c\",\n        },\n      ]\n    `)\n  })\n})\n"
  },
  {
    "path": "test/unit/sourcesHook.test.ts",
    "content": "import type { H3Event } from 'h3'\nimport type { SitemapSourcesHookCtx } from '../../src/runtime/types'\nimport { describe, expect, it } from 'vitest'\n\ndescribe('sitemap:sources hook', () => {\n  it('hook context is correctly typed', () => {\n    // This is a type test to ensure our hook context is properly structured\n    const mockEvent: Partial<H3Event> = {\n      node: {\n        req: {\n          headers: {\n            authorization: 'Bearer test-token',\n          },\n        } as any,\n      } as any,\n    }\n\n    const ctx: SitemapSourcesHookCtx = {\n      event: mockEvent as H3Event,\n      sitemapName: 'test-sitemap',\n      sources: [\n        '/api/test1',\n        ['/api/test2', { headers: { 'X-Original': 'original' } }],\n      ],\n    }\n\n    // Type checks - ensuring the structure is correct\n    expect(ctx.event).toBeDefined()\n    expect(ctx.sitemapName).toBe('test-sitemap')\n    expect(ctx.sources).toBeDefined()\n    expect(ctx.sources).toHaveLength(2)\n  })\n\n  it('hook can add new sources', () => {\n    const mockEvent: Partial<H3Event> = {\n      node: {\n        req: {\n          headers: {\n            authorization: 'Bearer test-token',\n          },\n        } as any,\n      } as any,\n    }\n\n    const ctx: SitemapSourcesHookCtx = {\n      event: mockEvent as H3Event,\n      sitemapName: 'test-sitemap',\n      sources: ['/api/existing'],\n    }\n\n    // Simulate adding a new source\n    ctx.sources.push('/api/new-source')\n\n    expect(ctx.sources).toHaveLength(2)\n    expect(ctx.sources).toContain('/api/new-source')\n  })\n\n  it('hook can modify source headers', () => {\n    const mockEvent: Partial<H3Event> = {\n      node: {\n        req: {\n          headers: {\n            authorization: 'Bearer test-token',\n          },\n        } as any,\n      } as any,\n    }\n\n    const ctx: SitemapSourcesHookCtx = {\n      event: mockEvent as H3Event,\n      sitemapName: 'test-sitemap',\n      sources: [\n        { fetch: ['/api/test', { headers: { 'X-Original': 'original' } }] } as any,\n      ],\n    }\n\n    // Simulate what a hook would do\n    ctx.sources = ctx.sources.map((source) => {\n      if (typeof source === 'object' && source.fetch) {\n        const [url, options = {}] = Array.isArray(source.fetch) ? source.fetch : [source.fetch, {}]\n\n        options.headers = options.headers || {}\n        options.headers['X-Custom'] = 'custom-value'\n\n        const authHeader = ctx.event.node?.req?.headers?.authorization\n        if (authHeader) {\n          options.headers.Authorization = authHeader\n        }\n\n        return { ...source, fetch: [url, options] }\n      }\n      return source\n    })\n\n    // Verify the modifications\n    const modifiedSource = ctx.sources[0] as any\n    const headers = modifiedSource.fetch[1].headers\n    expect(headers['X-Original']).toBe('original')\n    expect(headers['X-Custom']).toBe('custom-value')\n    expect(headers.Authorization).toBe('Bearer test-token')\n  })\n\n  it('hook can filter sources', () => {\n    const mockEvent: Partial<H3Event> = {\n      node: {\n        req: {\n          headers: {},\n        } as any,\n      } as any,\n    }\n\n    const ctx: SitemapSourcesHookCtx = {\n      event: mockEvent as H3Event,\n      sitemapName: 'test-sitemap',\n      sources: [\n        '/api/keep-this',\n        '/api/skip-this',\n        '/api/also-keep',\n      ],\n    }\n\n    // Simulate filtering sources\n    ctx.sources = ctx.sources.filter((source) => {\n      if (typeof source === 'string') {\n        return !source.includes('skip-this')\n      }\n      return true\n    })\n\n    expect(ctx.sources).toHaveLength(2)\n    expect(ctx.sources).not.toContain('/api/skip-this')\n    expect(ctx.sources).toContain('/api/keep-this')\n    expect(ctx.sources).toContain('/api/also-keep')\n  })\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"./.nuxt/tsconfig.json\",\n  \"exclude\": [\n    \"dist\",\n    \"test/**\",\n    \"playground\",\n    \"examples\",\n    \"benchmark\",\n    \"src/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.ts\"\n  ]\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig, defineProject } from 'vitest/config'\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    reporters: 'dot',\n    projects: [\n      // utils folders as *.test.ts in either test/unit or in src/**/*.test.ts\n      defineProject({\n        test: {\n          name: 'unit',\n          environment: 'node',\n          include: [\n            './test/unit/**/*.test.ts',\n            './src/**/*.test.ts',\n          ],\n          exclude: [\n            '**/node_modules/**',\n          ],\n        },\n      }),\n      // type-level tests via vitest typecheck\n      defineProject({\n        test: {\n          name: 'typecheck',\n          typecheck: {\n            enabled: true,\n            tsconfig: './test/types/tsconfig.json',\n          },\n          include: [\n            './test/types/**/*.test-d.ts',\n          ],\n          exclude: [\n            '**/node_modules/**',\n            '**/.claude/**',\n          ],\n        },\n      }),\n      // e2e tests in test/e2e\n      defineProject({\n        test: {\n          name: 'e2e',\n          environment: 'node',\n          include: [\n            './test/e2e/**/*.test.ts',\n          ],\n          exclude: [\n            '**/node_modules/**',\n          ],\n          globalSetup: './test/e2e/global-setup.ts',\n        },\n      }),\n    ],\n  },\n})\n"
  }
]