nuxt-ai-ready 1.7.14 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { realpath, readFile } from 'node:fs/promises';
3
3
  import { resolve, isAbsolute, relative, sep } from 'node:path';
4
4
  import { parseDocument } from 'yaml';
5
- import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.DQXoppXw.mjs';
5
+ import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.B6l-68o5.mjs';
6
6
  import 'node:module';
7
7
  import '@nuxt/kit';
8
8
  import 'defu';
@@ -5,7 +5,7 @@ import { colorize } from 'consola/utils';
5
5
  import { resolveLocaleFromRoute } from 'nuxtseo-shared/i18n-runtime';
6
6
  import { collectSitemap } from 'sitemapd/parse';
7
7
  import { withLeadingSlash, withBase, joinURL } from 'ufo';
8
- import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.DQXoppXw.mjs';
8
+ import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.B6l-68o5.mjs';
9
9
  import { normalizePagePath, toMarkdownPath } from '../../dist/runtime/markdown-path.js';
10
10
  import { toLogicalRoute, toDeployedRoute } from '../../dist/runtime/route-path.js';
11
11
  import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../../dist/runtime/server/db/shared.js';
@@ -238,6 +238,7 @@ async function processSitemapEntry(state, nuxt, nitro, entry) {
238
238
  logger.debug(`Fetching markdown for ${route} \u2192 ${mdUrl}`);
239
239
  const res = await globalThis.$fetch(mdUrl, {
240
240
  headers: { "x-nitro-prerender": mdRoute },
241
+ retry: 0,
241
242
  signal: AbortSignal.timeout(PRERENDER_PAGE_TIMEOUT)
242
243
  }).catch((err) => {
243
244
  if (err?.name === "TimeoutError" || err?.name === "AbortError")
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "1.7.14",
7
+ "version": "1.8.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -7,7 +7,7 @@ import 'defu';
7
7
  import 'nuxt-site-config/kit';
8
8
  import 'nuxtseo-shared/kit';
9
9
  import 'pkg-types';
10
- export { m as default } from './shared/nuxt-ai-ready.DQXoppXw.mjs';
10
+ export { m as default } from './shared/nuxt-ai-ready.B6l-68o5.mjs';
11
11
  import '../dist/runtime/server/utils/discovery-response.js';
12
12
  import 'node:url';
13
13
  import 'pathe';
@@ -113,6 +113,19 @@ export interface ModuleOptions {
113
113
  * @default undefined
114
114
  */
115
115
  apiCatalog?: false | ApiCatalogConfig;
116
+ /**
117
+ * Read a page's Markdown from the installed content module instead of
118
+ * rendering the page and converting its HTML.
119
+ *
120
+ * Supports @nuxt/content v3 and @harlan-zw/comark-content. When a content
121
+ * module is present this skips one SSR render per route and keeps the source
122
+ * Markdown intact, so MDC blocks, tables and code fences survive.
123
+ *
124
+ * Set to false to convert the rendered HTML instead. Do that when a page's
125
+ * meaning depends on what its components render rather than on its source.
126
+ * @default true
127
+ */
128
+ contentSource?: boolean;
116
129
  /**
117
130
  * Options to pass to mdream htmlToMarkdown function
118
131
  */
@@ -2,11 +2,11 @@ import { createHash, randomBytes } from 'node:crypto';
2
2
  import { readFile, mkdir, writeFile, access } from 'node:fs/promises';
3
3
  import { createRequire } from 'node:module';
4
4
  import { dirname, basename, join } from 'node:path';
5
- import { useLogger, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, addServerImports, hasNuxtModule, addServerPlugin, addServerHandler, extendRouteRules, addPlugin, addImports } from '@nuxt/kit';
5
+ import { hasNuxtModule, useNuxt, useLogger, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, addServerImports, addServerPlugin, addServerHandler, extendRouteRules, addPlugin, addImports } from '@nuxt/kit';
6
6
  import defu from 'defu';
7
7
  import { installNuxtSiteConfig, useSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
8
- import { renderNitroTypeAugmentations, setupNitroRuntimeCompatibility, resolveNuxtContentVersion } from 'nuxtseo-shared/kit';
9
- import { resolvePackageJSON, readPackageJSON } from 'pkg-types';
8
+ import { resolveNuxtContentVersion, renderNitroTypeAugmentations, setupNitroRuntimeCompatibility } from 'nuxtseo-shared/kit';
9
+ import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
10
10
  import { fileURLToPath, pathToFileURL } from 'node:url';
11
11
  import { MCP_SERVER_CARD_MEDIA_TYPE, AI_CATALOG_MEDIA_TYPE } from '../../dist/runtime/server/utils/discovery-response.js';
12
12
  import { isAbsolute, join as join$1 } from 'pathe';
@@ -77,6 +77,93 @@ export async function readPageDataFromFilesystem() {
77
77
  `;
78
78
  }
79
79
 
80
+ const COMARK_MODULE = "@harlan-zw/comark-content";
81
+ const COMARK_MINIMUM = [0, 1, 2];
82
+ async function comarkSatisfiesMinimum() {
83
+ const url = useNuxt().options.rootDir;
84
+ const pkg = await readPackageJSON(COMARK_MODULE, { url }).catch(() => {
85
+ return null;
86
+ });
87
+ const parts = pkg?.version?.split(".").map((part) => Number.parseInt(part, 10));
88
+ if (!parts || parts.length < 3 || parts.some((part) => !Number.isFinite(part)))
89
+ return false;
90
+ for (const [index, minimum] of COMARK_MINIMUM.entries()) {
91
+ if (parts[index] !== minimum)
92
+ return parts[index] > minimum;
93
+ }
94
+ return true;
95
+ }
96
+ async function resolveContentSource(enabled) {
97
+ if (!enabled)
98
+ return { _tag: "None" };
99
+ const contentVersion = await resolveNuxtContentVersion();
100
+ if (contentVersion && contentVersion.version === 3)
101
+ return { _tag: "NuxtContentV3" };
102
+ if (hasNuxtModule(COMARK_MODULE) && await comarkSatisfiesMinimum())
103
+ return { _tag: "ComarkContent" };
104
+ return { _tag: "None" };
105
+ }
106
+ const NUXT_CONTENT_V3_LOOKUP = `
107
+ import { queryCollection } from '@nuxt/content/server'
108
+ import manifest from '#content/manifest'
109
+ import { stringify } from 'minimark/stringify'
110
+
111
+ const pageCollections = Object.entries(manifest)
112
+ .filter(([, info]) => info.type === 'page')
113
+ .map(([name]) => name)
114
+
115
+ export async function lookupContentPage(event, path) {
116
+ if (!pageCollections.length) return null
117
+ const candidates = path === '/' ? ['/'] : [path, path.replace(/\\/$/, '')]
118
+ for (const collection of pageCollections) {
119
+ for (const candidate of candidates) {
120
+ const page = await queryCollection(event, collection).path(candidate).first().catch(() => null)
121
+ if (!page) continue
122
+ const markdown = stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
123
+ return {
124
+ markdown,
125
+ title: page.title,
126
+ description: page.description,
127
+ updatedAt: page.seo?.articleModifiedTime || page.updatedAt,
128
+ }
129
+ }
130
+ }
131
+ return null
132
+ }
133
+ `;
134
+ const COMARK_LOOKUP = `
135
+ import { queryCollection, queryCollectionManifest, renderPageMarkdown } from '@harlan-zw/comark-content/server'
136
+
137
+ let manifestPromise
138
+
139
+ export async function lookupContentPage(event, path) {
140
+ manifestPromise ??= queryCollectionManifest(event)
141
+ const manifest = await manifestPromise
142
+ for (const { name } of manifest) {
143
+ const page = await queryCollection(event, name).path(path).first()
144
+ if (!page) continue
145
+ return {
146
+ markdown: await renderPageMarkdown(page.body),
147
+ title: page.title,
148
+ description: page.description,
149
+ updatedAt: page.seo?.articleModifiedTime || page.updatedAt,
150
+ }
151
+ }
152
+ return null
153
+ }
154
+ `;
155
+ const NO_CONTENT_LOOKUP = `export async function lookupContentPage() { return null }`;
156
+ function contentLookupModule(source) {
157
+ switch (source._tag) {
158
+ case "NuxtContentV3":
159
+ return NUXT_CONTENT_V3_LOOKUP;
160
+ case "ComarkContent":
161
+ return COMARK_LOOKUP;
162
+ case "None":
163
+ return NO_CONTENT_LOOKUP;
164
+ }
165
+ }
166
+
80
167
  const logger = useLogger("nuxt-ai-ready");
81
168
 
82
169
  function resolveModuleEntryUrl(url) {
@@ -1043,8 +1130,7 @@ ${details}`);
1043
1130
  if (mcpConfig.resources !== false)
1044
1131
  (paths.resources ||= []).push(`${mcpRuntimeDir}/resources`);
1045
1132
  });
1046
- const contentVersion = await resolveNuxtContentVersion();
1047
- const hasNuxtContentV3 = !!(contentVersion && contentVersion.version === 3);
1133
+ const contentSource = await resolveContentSource(config.contentSource !== false);
1048
1134
  if (typeof config.contentSignal === "object") {
1049
1135
  const robotsOpts = nuxt.options.robots || {};
1050
1136
  nuxt.options.robots = robotsOpts;
@@ -1423,34 +1509,7 @@ export const logger = createModuleLogger('nuxt-ai-ready', ${!!config.debug})
1423
1509
  mcp: { enabled: mcpAvailable, tools: mcpAvailable && config.mcp?.tools !== false && hasMcpSiteTools, resources: mcpAvailable && config.mcp?.resources !== false },
1424
1510
  cron: !!config.cron
1425
1511
  })}`;
1426
- nitroConfig.virtual["#ai-ready-virtual/content-lookup.mjs"] = hasNuxtContentV3 ? `
1427
- import { queryCollection } from '@nuxt/content/server'
1428
- import manifest from '#content/manifest'
1429
- import { stringify } from 'minimark/stringify'
1430
-
1431
- const pageCollections = Object.entries(manifest)
1432
- .filter(([, info]) => info.type === 'page')
1433
- .map(([name]) => name)
1434
-
1435
- export async function lookupContentPage(event, path) {
1436
- if (!pageCollections.length) return null
1437
- const candidates = path === '/' ? ['/'] : [path, path.replace(/\\/$/, '')]
1438
- for (const collection of pageCollections) {
1439
- for (const candidate of candidates) {
1440
- const page = await queryCollection(event, collection).path(candidate).first().catch(() => null)
1441
- if (!page) continue
1442
- const markdown = stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
1443
- return {
1444
- markdown,
1445
- title: page.title,
1446
- description: page.description,
1447
- updatedAt: page.seo?.articleModifiedTime || page.updatedAt,
1448
- }
1449
- }
1450
- }
1451
- return null
1452
- }
1453
- ` : `export async function lookupContentPage() { return null }`;
1512
+ nitroConfig.virtual["#ai-ready-virtual/content-lookup.mjs"] = contentLookupModule(contentSource);
1454
1513
  });
1455
1514
  const database = refineDatabaseConfig(config.database || {}, nuxt.options.rootDir);
1456
1515
  nuxt.options.runtimeConfig["nuxt-ai-ready"] = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ai-ready",
3
3
  "type": "module",
4
- "version": "1.7.14",
4
+ "version": "1.8.0",
5
5
  "description": "Best practice AI & LLM discoverability for Nuxt sites.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -82,6 +82,7 @@
82
82
  "devDependencies": {
83
83
  "@antfu/eslint-config": "^9.3.0",
84
84
  "@arethetypeswrong/cli": "^0.18.5",
85
+ "@harlan-zw/comark-content": "0.1.2",
85
86
  "@libsql/client": "^0.17.4",
86
87
  "@nuxt/content": "^3.15.2",
87
88
  "@nuxt/module-builder": "^1.0.3",