From 7d87776fdf2fca309a37476c59ac72fa7a3d327c Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 19 Aug 2026 01:25:07 +1000 Subject: [PATCH 1/2] feat: first-party comark-content support resolveNuxtContentVersion() only answers for @nuxt/content, so an app running comark-content never reached the robots frontmatter mapping. Switch to resolveContentProvider() and run the same content:file:afterParse handler for both providers. comark reads Markdown into Nitro server assets rather than a database, so unlike Nuxt Content v2 it needs no Cloudflare opt out. --- docs/content/3.advanced/2.content.md | 28 +++ package.json | 1 + pnpm-lock.yaml | 162 ++++++++++++++++++ pnpm-workspace.yaml | 2 + src/kit.ts | 3 +- src/module.ts | 20 ++- test/e2e/comark-content.test.ts | 29 ++++ test/fixtures/comark-content/.nuxtrc | 2 + test/fixtures/comark-content/app.vue | 3 + .../fixtures/comark-content/content.config.ts | 16 ++ test/fixtures/comark-content/content/bar.md | 6 + test/fixtures/comark-content/content/foo.md | 7 + test/fixtures/comark-content/nuxt.config.ts | 14 ++ .../comark-content/pages/[...slug].vue | 18 ++ 14 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 test/e2e/comark-content.test.ts create mode 100644 test/fixtures/comark-content/.nuxtrc create mode 100644 test/fixtures/comark-content/app.vue create mode 100644 test/fixtures/comark-content/content.config.ts create mode 100644 test/fixtures/comark-content/content/bar.md create mode 100644 test/fixtures/comark-content/content/foo.md create mode 100644 test/fixtures/comark-content/nuxt.config.ts create mode 100644 test/fixtures/comark-content/pages/[...slug].vue diff --git a/docs/content/3.advanced/2.content.md b/docs/content/3.advanced/2.content.md index 99ba9fbf..60de3ace 100644 --- a/docs/content/3.advanced/2.content.md +++ b/docs/content/3.advanced/2.content.md @@ -60,6 +60,34 @@ export default defineNuxtConfig({ }) ``` +## Setup comark-content + +[comark-content](https://github.com/harlan-zw/harlan-nuxt/tree/main/packages/comark-content) is a Markdown-only content module. Nuxt Robots supports it first-party. + +No schema opt in is needed. Write the `robots` key in frontmatter and the module maps it onto the page's `seo.robots`. + +```md [content/foo.md] +--- +robots: false +--- + +# Foo +``` + +Render it with `useSeoMeta()`{lang="ts"} in your catch-all page, the same as with Nuxt Content v3. + +```vue [pages/[...slug\].vue] + +``` + +See [Usage](#usage) for the values the `robots` key accepts. + ## Setup Nuxt Content v2 In Nuxt Content v2 markdown files require either [Document Driven Mode](https://content.nuxt.com/document-driven/introduction) or a `path` key to be set diff --git a/package.json b/package.json index b73a702c..b5ec2475 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "@iconify-json/logos": "catalog:dev", "@iconify-json/ri": "catalog:dev", "@iconify-json/tabler": "catalog:", + "@harlan-zw/comark-content": "catalog:dev", "@nuxt/content": "catalog:dev", "@nuxt/devtools-kit": "catalog:", "@nuxt/devtools-ui-kit": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70d7e602..2713f733 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,6 +124,9 @@ catalogs: '@arethetypeswrong/cli': specifier: ^0.18.5 version: 0.18.5 + '@harlan-zw/comark-content': + specifier: ^0.1.3 + version: 0.1.3 '@headlessui/vue': specifier: ^1.7.23 version: 1.7.23 @@ -202,6 +205,9 @@ importers: '@arethetypeswrong/cli': specifier: catalog:dev version: 0.18.5 + '@harlan-zw/comark-content': + specifier: catalog:dev + version: 0.1.3(magic-string@1.1.0)(magicast@0.5.4)(nuxt@4.5.2(c208e49f62ede97a435f751ca2ecda2c))(oxc-parser@0.131.0)(rolldown@1.2.1)(shiki@4.4.1)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.4)(vite@8.2.1(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(webpack@5.109.2(cssnano@8.0.2(postcss@8.5.25))(csso@5.0.5)(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)))(vue@3.5.41(typescript@6.0.3)) '@headlessui/vue': specifier: catalog:dev version: 1.7.23(vue@3.5.41(typescript@6.0.3)) @@ -1280,6 +1286,13 @@ packages: engines: {node: '>=6'} hasBin: true + '@harlan-zw/comark-content@0.1.3': + resolution: {integrity: sha512-nmGSQjp8lqLHFenlKXg/7mB8p82Gw0spp8Jr3aSvaHpyxQwqS3enDE+IxrnouH15bCjRQeZab+v7uWDWPWpNGA==} + engines: {node: '>=22.12.0'} + peerDependencies: + nuxt: '>=4.5.0 <5.0.0' + vue: '>=3.5.0 <4.0.0' + '@headlessui/vue@1.7.23': resolution: {integrity: sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==} engines: {node: '>=10'} @@ -3292,9 +3305,15 @@ packages: '@types/katex@0.16.8': resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -4318,6 +4337,23 @@ packages: colortranslator@5.0.0: resolution: {integrity: sha512-Z3UPUKasUVDFCDYAjP2fmlVRf1jFHJv1izAmPjiOa0OCIw1W7iC8PZ2GsoDa8uZv+mKyWopxxStT9q05+27h7w==} + comark@0.6.2: + resolution: {integrity: sha512-hlWa7KlGPfRxovQavnHZlLlTUKrfwE7/o4ewCY0FzqFtRvVT8VdslrumQwELIiK9utsurpoSWCBFj1yVMtDOtw==} + peerDependencies: + beautiful-mermaid: ^1.1.3 + katex: ^0.17.0 + rangi: ^2.2.0 + shiki: ^4.3.1 + peerDependenciesMeta: + beautiful-mermaid: + optional: true + katex: + optional: true + rangi: + optional: true + shiki: + optional: true + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -4657,16 +4693,32 @@ packages: dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-serializer@3.1.1: + resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==} + engines: {node: '>=20.19.0'} + domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + domelementtype@3.0.0: + resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==} + engines: {node: '>=20.19.0'} + domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + domhandler@6.0.1: + resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==} + engines: {node: '>=20.19.0'} + domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + domutils@4.0.2: + resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==} + engines: {node: '>=20.19.0'} + dot-prop@10.2.0: resolution: {integrity: sha512-BTJ9aZYL3vCfZlZOBLy9v8TUqWGQ0pzFnygKwFZt5udj6viBoFIBviKPUoZLDCPn1FoXffv6McQFDenrm5Krfw==} engines: {node: '>=20'} @@ -5624,6 +5676,10 @@ packages: html-whitespace-sensitive-tag-names@3.0.1: resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==} + htmlparser2@12.0.0: + resolution: {integrity: sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==} + engines: {node: '>=20.19.0'} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -5881,6 +5937,10 @@ packages: resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true + js-yaml@5.3.0: + resolution: {integrity: sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q==} + hasBin: true + jsdoc-type-pratt-parser@7.3.0: resolution: {integrity: sha512-DoyJXo7x/n48M3NsGOs9QnEws0ft0tV3YsSgvWMNxz2hZtz+Q6fpqUD96lVYX4a+jcEkzHeFNDJOn68TzXfbdA==} engines: {node: '>=20.0.0'} @@ -6135,6 +6195,9 @@ packages: limiter@1.1.5: resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + linkify-it@5.0.2: + resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==} + linkifyjs@4.3.3: resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} @@ -6237,6 +6300,9 @@ packages: resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} engines: {node: '>=18'} + markdown-exit@1.1.0-beta.2: + resolution: {integrity: sha512-8CzMGVlFZ4DEfnc8KU+4ycUW2SIOuiXqCHD7z51ecVEi/weyc0f2ylQbCm4KoKuVlTZSuMUMnWT0hTyquZ7anQ==} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -6311,6 +6377,9 @@ packages: mdn-data@2.29.0: resolution: {integrity: sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==} + mdurl@2.1.0: + resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} + media-typer@1.1.1: resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} engines: {node: '>= 0.8'} @@ -7425,6 +7494,10 @@ packages: engines: {node: '>=18'} hasBin: true + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -7453,6 +7526,10 @@ packages: resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} engines: {node: '>= 0.6'} + rangi@2.2.0: + resolution: {integrity: sha512-zMXLNs+3ejB2dg5kjJdNbllNq6FrdtEbHA9f3POxbSIHb0CyslALbY8qSDCE2r+/6Ku7xaVHKtnC5qHbF2SgNg==} + hasBin: true + raw-body@3.0.2: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} @@ -8176,6 +8253,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} @@ -9926,6 +10006,23 @@ snapshots: yargs: 17.7.3 optional: true + '@harlan-zw/comark-content@0.1.3(magic-string@1.1.0)(magicast@0.5.4)(nuxt@4.5.2(c208e49f62ede97a435f751ca2ecda2c))(oxc-parser@0.131.0)(rolldown@1.2.1)(shiki@4.4.1)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.4)(vite@8.2.1(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(webpack@5.109.2(cssnano@8.0.2(postcss@8.5.25))(csso@5.0.5)(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)))(vue@3.5.41(typescript@6.0.3))': + dependencies: + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.131.0)(rolldown@1.2.1)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.4)(vite@8.2.1(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(webpack@5.109.2(cssnano@8.0.2(postcss@8.5.25))(csso@5.0.5)(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25))) + comark: 0.6.2(rangi@2.2.0)(shiki@4.4.1) + nuxt: 4.5.2(c208e49f62ede97a435f751ca2ecda2c) + rangi: 2.2.0 + vue: 3.5.41(typescript@6.0.3) + transitivePeerDependencies: + - beautiful-mermaid + - katex + - magic-string + - magicast + - oxc-parser + - rolldown + - shiki + - unplugin + '@headlessui/vue@1.7.23(vue@3.5.41(typescript@6.0.3))': dependencies: '@tanstack/vue-virtual': 3.13.35(vue@3.5.41(typescript@6.0.3)) @@ -12406,10 +12503,14 @@ snapshots: '@types/katex@0.16.8': {} + '@types/linkify-it@5.0.0': {} + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/mdurl@2.0.0': {} + '@types/ms@2.1.0': {} '@types/node@26.1.2': @@ -13875,6 +13976,16 @@ snapshots: colortranslator@5.0.0: {} + comark@0.6.2(rangi@2.2.0)(shiki@4.4.1): + dependencies: + entities: 8.0.0 + htmlparser2: 12.0.0 + js-yaml: 5.3.0 + markdown-exit: 1.1.0-beta.2 + optionalDependencies: + rangi: 2.2.0 + shiki: 4.4.1 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -14211,18 +14322,36 @@ snapshots: domhandler: 5.0.3 entities: 4.5.0 + dom-serializer@3.1.1: + dependencies: + domelementtype: 3.0.0 + domhandler: 6.0.1 + entities: 8.0.0 + domelementtype@2.3.0: {} + domelementtype@3.0.0: {} + domhandler@5.0.3: dependencies: domelementtype: 2.3.0 + domhandler@6.0.1: + dependencies: + domelementtype: 3.0.0 + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 + domutils@4.0.2: + dependencies: + dom-serializer: 3.1.1 + domelementtype: 3.0.0 + domhandler: 6.0.1 + dot-prop@10.2.0: dependencies: type-fest: 5.8.0 @@ -15555,6 +15684,13 @@ snapshots: html-whitespace-sensitive-tag-names@3.0.1: {} + htmlparser2@12.0.0: + dependencies: + domelementtype: 3.0.0 + domhandler: 6.0.1 + domutils: 4.0.2 + entities: 8.0.0 + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -15803,6 +15939,10 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@5.3.0: + dependencies: + argparse: 2.0.1 + jsdoc-type-pratt-parser@7.3.0: {} jsdoc-type-pratt-parser@8.0.0: {} @@ -16016,6 +16156,10 @@ snapshots: limiter@1.1.5: {} + linkify-it@5.0.2: + dependencies: + uc.micro: 2.1.0 + linkifyjs@4.3.3: {} listhen@1.10.1(srvx@0.11.22): @@ -16144,6 +16288,16 @@ snapshots: type-fest: 4.41.0 web-worker: 1.5.0 + markdown-exit@1.1.0-beta.2: + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + entities: 7.0.1 + linkify-it: 5.0.2 + mdurl: 2.1.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + markdown-table@3.0.4: {} marked-terminal@7.3.0(marked@9.1.6): @@ -16306,6 +16460,8 @@ snapshots: mdn-data@2.29.0: {} + mdurl@2.1.0: {} + media-typer@1.1.1: {} merge-descriptors@2.0.0: {} @@ -17997,6 +18153,8 @@ snapshots: picocolors: 1.1.1 sade: 1.8.1 + punycode.js@2.3.1: {} + punycode@2.3.1: {} qs@6.15.3: @@ -18016,6 +18174,8 @@ snapshots: range-parser@1.3.0: {} + rangi@2.2.0: {} + raw-body@3.0.2: dependencies: bytes: 3.1.2 @@ -18910,6 +19070,8 @@ snapshots: typescript@6.0.3: {} + uc.micro@2.1.0: {} + ufo@1.6.4: {} ultrahtml@1.7.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fe5fb4db..e9ff22d0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,6 +22,7 @@ minimumReleaseAgeExclude: - '@nuxt/schema@4.5.0' - '@nuxt/vite-builder@4.5.0' - nuxt@4.5.0 + - '@harlan-zw/comark-content@0.1.3' shellEmulator: true trustPolicy: no-downgrade trustPolicyExclude: @@ -81,6 +82,7 @@ catalogs: '@headlessui/vue': ^1.7.23 '@iconify-json/logos': ^1.2.12 '@iconify-json/ri': ^1.2.10 + '@harlan-zw/comark-content': ^0.1.3 '@nuxt/content': ^3.15.2 '@nuxt/module-builder': ^1.0.3 '@nuxt/test-utils': ^4.1.0 diff --git a/src/kit.ts b/src/kit.ts index 6cd53998..3b5dc8d4 100644 --- a/src/kit.ts +++ b/src/kit.ts @@ -1 +1,2 @@ -export { isNuxtGenerate, resolveNitroPreset, resolveNuxtContentVersion } from 'nuxtseo-shared/kit' +export type { ContentProvider } from 'nuxtseo-shared/kit' +export { isNuxtGenerate, resolveContentProvider, resolveNitroPreset } from 'nuxtseo-shared/kit' diff --git a/src/module.ts b/src/module.ts index 41cc35b2..296af252 100644 --- a/src/module.ts +++ b/src/module.ts @@ -22,7 +22,7 @@ import { withoutTrailingSlash, withTrailingSlash } from 'ufo' import { AiBots, NonHelpfulBots } from './const' import { setupDevToolsUI } from './devtools' import { mapPathForI18nPages, resolveI18nConfig, splitPathForI18nLocales } from './i18n' -import { isNuxtGenerate, resolveNitroPreset, resolveNuxtContentVersion } from './kit' +import { isNuxtGenerate, resolveContentProvider, resolveNitroPreset } from './kit' import { registerTypeTemplates } from './templates' import { asArray, @@ -215,6 +215,10 @@ export default defineNuxtModule({ version: '>=2', optional: true, }, + '@harlan-zw/comark-content': { + version: '>=0.1.2', + optional: true, + }, }, defaults: { enabled: true, @@ -384,11 +388,15 @@ export default defineNuxtModule({ } const nitroPreset = resolveNitroPreset(nuxt.options.nitro) - const contentVersion = await resolveNuxtContentVersion() - const isNuxtContentV3 = contentVersion && contentVersion.version === 3 - let isNuxtContentV2 = contentVersion && contentVersion.version === 2 - if (isNuxtContentV3) { - if (hasNuxtModule('Content', nuxt)) { + const contentProvider = await resolveContentProvider(nuxt) + const isNuxtContentV3 = contentProvider._tag === 'NuxtContent' && contentProvider.version === 3 + let isNuxtContentV2 = contentProvider._tag === 'NuxtContent' && contentProvider.version === 2 + const isComarkContent = contentProvider._tag === 'Comark' + // comark-content fires the same build hook with the same context shape, so the + // frontmatter mapping is identical. It reads Markdown into Nitro server assets + // rather than a database, so unlike Nuxt Content v2 it needs no Cloudflare opt out. + if (isNuxtContentV3 || isComarkContent) { + if (isNuxtContentV3 && hasNuxtModule('Content', nuxt)) { logger.warn('You have loaded `@nuxt/content` before `@nuxtjs/robots`, this may cause issues with the integration. Please ensure `@nuxtjs/robots` is loaded first.') } nuxt.hooks.hook('content:file:afterParse' as any, (ctx: FileAfterParseHook) => { diff --git a/test/e2e/comark-content.test.ts b/test/e2e/comark-content.test.ts new file mode 100644 index 00000000..57e3f12d --- /dev/null +++ b/test/e2e/comark-content.test.ts @@ -0,0 +1,29 @@ +import { createResolver } from '@nuxt/kit' +import { $fetch, setup } from '@nuxt/test-utils' +import { describe, expect, it } from 'vitest' + +const { resolve } = createResolver(import.meta.url) + +await setup({ + rootDir: resolve('../fixtures/comark-content'), +}) + +describe('comark-content', () => { + it('maps the robots frontmatter onto the page seo meta', async () => { + const barHtml = await $fetch('/bar') + expect(String(barHtml).match(//)).toMatchInlineSnapshot(` + [ + "", + "test", + ] + `) + + const fooHtml = await $fetch('/foo') + expect(String(fooHtml).match(//)).toMatchInlineSnapshot(` + [ + "", + "noindex, nofollow", + ] + `) + }, 60000) +}) diff --git a/test/fixtures/comark-content/.nuxtrc b/test/fixtures/comark-content/.nuxtrc new file mode 100644 index 00000000..167e6c78 --- /dev/null +++ b/test/fixtures/comark-content/.nuxtrc @@ -0,0 +1,2 @@ +imports.autoImport=true +typescript.includeWorkspace=true diff --git a/test/fixtures/comark-content/app.vue b/test/fixtures/comark-content/app.vue new file mode 100644 index 00000000..8f62b8bf --- /dev/null +++ b/test/fixtures/comark-content/app.vue @@ -0,0 +1,3 @@ + diff --git a/test/fixtures/comark-content/content.config.ts b/test/fixtures/comark-content/content.config.ts new file mode 100644 index 00000000..ce214828 --- /dev/null +++ b/test/fixtures/comark-content/content.config.ts @@ -0,0 +1,16 @@ +import { dirname, resolve } from 'node:path' +import { defineCollection, defineContentConfig } from '@harlan-zw/comark-content' + +const dirName = dirname(import.meta.url.replace('file://', '')) + +export default defineContentConfig({ + collections: { + content: defineCollection({ + type: 'page', + source: { + include: '**/*.md', + cwd: resolve(dirName, 'content'), + }, + }), + }, +}) diff --git a/test/fixtures/comark-content/content/bar.md b/test/fixtures/comark-content/content/bar.md new file mode 100644 index 00000000..2f082a81 --- /dev/null +++ b/test/fixtures/comark-content/content/bar.md @@ -0,0 +1,6 @@ +--- +path: /bar +robots: "test" +--- + +# bar diff --git a/test/fixtures/comark-content/content/foo.md b/test/fixtures/comark-content/content/foo.md new file mode 100644 index 00000000..91f2dc51 --- /dev/null +++ b/test/fixtures/comark-content/content/foo.md @@ -0,0 +1,7 @@ +--- +path: '/foo' +robots: false +date: 2026-10-10 +--- + +# foo diff --git a/test/fixtures/comark-content/nuxt.config.ts b/test/fixtures/comark-content/nuxt.config.ts new file mode 100644 index 00000000..647f1479 --- /dev/null +++ b/test/fixtures/comark-content/nuxt.config.ts @@ -0,0 +1,14 @@ +import NuxtRobots from '../../../src/module' + +export default defineNuxtConfig({ + modules: [ + NuxtRobots, + '@harlan-zw/comark-content', + ], + + site: { + url: 'https://nuxtseo.com', + }, + + compatibilityDate: '2024-12-06', +}) diff --git a/test/fixtures/comark-content/pages/[...slug].vue b/test/fixtures/comark-content/pages/[...slug].vue new file mode 100644 index 00000000..ae860280 --- /dev/null +++ b/test/fixtures/comark-content/pages/[...slug].vue @@ -0,0 +1,18 @@ + + + From 45b882a6746870ba902484d84df468c3792b6394 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 19 Aug 2026 03:37:55 +1000 Subject: [PATCH 2/2] chore: sort the comark catalog entry --- package.json | 2 +- pnpm-workspace.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b5ec2475..cc3ac9bf 100644 --- a/package.json +++ b/package.json @@ -83,11 +83,11 @@ "devDependencies": { "@antfu/eslint-config": "catalog:", "@arethetypeswrong/cli": "catalog:dev", + "@harlan-zw/comark-content": "catalog:dev", "@headlessui/vue": "catalog:dev", "@iconify-json/logos": "catalog:dev", "@iconify-json/ri": "catalog:dev", "@iconify-json/tabler": "catalog:", - "@harlan-zw/comark-content": "catalog:dev", "@nuxt/content": "catalog:dev", "@nuxt/devtools-kit": "catalog:", "@nuxt/devtools-ui-kit": "catalog:", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e9ff22d0..669ec03e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -79,10 +79,10 @@ catalog: catalogs: dev: '@arethetypeswrong/cli': ^0.18.5 + '@harlan-zw/comark-content': ^0.1.3 '@headlessui/vue': ^1.7.23 '@iconify-json/logos': ^1.2.12 '@iconify-json/ri': ^1.2.10 - '@harlan-zw/comark-content': ^0.1.3 '@nuxt/content': ^3.15.2 '@nuxt/module-builder': ^1.0.3 '@nuxt/test-utils': ^4.1.0