nuxt-ai-ready 1.4.0 → 1.5.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.
package/README.md CHANGED
@@ -41,6 +41,8 @@ npx nuxi@latest module add nuxt-ai-ready
41
41
  > npx skilld add nuxt-ai-ready
42
42
  > ```
43
43
 
44
+ 💡 Made your site AI-ready? Preview how a page converts to LLM-readable markdown with the free [HTML→Markdown tool](https://nuxtseo.com/tools/html-to-markdown), or track how AI engines index, rank and cite your site with [Nuxt SEO Pro](https://nuxtseo.com/pro).
45
+
44
46
  ## Documentation
45
47
 
46
48
  [📖 Read the full documentation](https://nuxtseo.com/ai-ready) for more information.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "1.4.0",
7
+ "version": "1.5.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -675,11 +675,13 @@ const module$1 = defineNuxtModule({
675
675
  nuxt.options.robots = robotsOpts;
676
676
  const groups = robotsOpts.groups || [];
677
677
  robotsOpts.groups = groups;
678
- groups.push({
678
+ const group = {
679
679
  userAgent: "*",
680
- contentUsage: [`train-ai=${config.contentSignal.aiTrain ? "y" : "n"}`],
681
680
  contentSignal: [`ai-train=${config.contentSignal.aiTrain ? "yes" : "no"}`, `search=${config.contentSignal.search ? "yes" : "no"}`, `ai-input=${config.contentSignal.aiInput ? "yes" : "no"}`]
682
- });
681
+ };
682
+ if (config.contentSignal.contentUsage !== false)
683
+ group.contentUsage = [`train-ai=${config.contentSignal.aiTrain ? "y" : "n"}`];
684
+ groups.push(group);
683
685
  }
684
686
  registerTypeTemplates();
685
687
  const defaultLlmsTxtSections = [];
@@ -4,9 +4,14 @@ import { useRuntimeConfig } from "nitropack/runtime";
4
4
  import * as schema from "#ai-ready-virtual/db-schema.mjs";
5
5
  import { logger } from "../../../logger.js";
6
6
  import { registerDriver } from "../raw.js";
7
+ import { resolveWritableDbPath } from "./dbPath.js";
7
8
  export async function createClient(event) {
8
9
  const config = useRuntimeConfig(event)["nuxt-ai-ready"];
9
- const dbUrl = config.database.url || `file:${config.database.filename || ".data/ai-ready/pages.db"}`;
10
+ let dbUrl = config.database.url || `file:${config.database.filename || ".data/ai-ready/pages.db"}`;
11
+ if (dbUrl.startsWith("file:") && !dbUrl.startsWith("file://")) {
12
+ const resolved = await resolveWritableDbPath(dbUrl.slice("file:".length));
13
+ dbUrl = `file:${resolved}`;
14
+ }
10
15
  logger.debug(`[drizzle] Connecting to LibSQL: ${dbUrl}`);
11
16
  const client = createLibSQLClient({ url: dbUrl, authToken: config.database.authToken });
12
17
  const db = drizzle(client, { schema });
@@ -9,14 +9,14 @@ import { buildFrontmatter } from "../utils/frontmatter.js";
9
9
  import { computeLocaleAlternates, resolveLocaleFromRoute } from "../utils/i18n.js";
10
10
  import { buildLinkHeader } from "../utils/link-header.js";
11
11
  const INTERNAL_HEADER = "x-ai-ready-internal";
12
- function setNegotiationHeaders(event, path, config) {
12
+ function setNegotiationHeaders(event, path, config, resolveUrl) {
13
13
  setHeader(event, "vary", "Accept, Sec-Fetch-Dest");
14
- setHeader(event, "link", buildLinkHeader(path, "html", config));
14
+ setHeader(event, "link", buildLinkHeader(path, "html", config, resolveUrl));
15
15
  }
16
- function setMarkdownHeaders(event, path, config) {
16
+ function setMarkdownHeaders(event, path, config, resolveUrl) {
17
17
  setHeader(event, "content-type", "text/markdown; charset=utf-8");
18
18
  setHeader(event, "vary", "Accept, Sec-Fetch-Dest");
19
- setHeader(event, "link", buildLinkHeader(path, "markdown", config));
19
+ setHeader(event, "link", buildLinkHeader(path, "markdown", config, resolveUrl));
20
20
  if (config.markdownCacheHeaders) {
21
21
  const { maxAge, swr } = config.markdownCacheHeaders;
22
22
  const cacheControl = swr ? `public, max-age=${maxAge}, stale-while-revalidate=${maxAge}` : `public, max-age=${maxAge}`;
@@ -65,9 +65,10 @@ export default defineEventHandler(async (event) => {
65
65
  }
66
66
  const { path, isExplicit, negotiation } = renderInfo;
67
67
  const config = useRuntimeConfig(event)["nuxt-ai-ready"];
68
- const canonicalUrl = withSiteUrl(event, path);
68
+ const resolveUrl = (path2) => withSiteUrl(event, path2);
69
+ const canonicalUrl = resolveUrl(path);
69
70
  if (negotiation === "html") {
70
- setNegotiationHeaders(event, path, config);
71
+ setNegotiationHeaders(event, path, config, resolveUrl);
71
72
  return;
72
73
  }
73
74
  if (!isExplicit) {
@@ -85,7 +86,7 @@ export default defineEventHandler(async (event) => {
85
86
  canonical_url: canonicalUrl,
86
87
  last_updated: contentPage.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
87
88
  });
88
- setMarkdownHeaders(event, path, config);
89
+ setMarkdownHeaders(event, path, config, resolveUrl);
89
90
  return `${frontmatter}
90
91
  ${contentPage.markdown}`;
91
92
  }
@@ -98,7 +99,7 @@ ${contentPage.markdown}`;
98
99
  return null;
99
100
  });
100
101
  if (!response) {
101
- setMarkdownHeaders(event, path, config);
102
+ setMarkdownHeaders(event, path, config, resolveUrl);
102
103
  return notFoundMarkdown(canonicalUrl, path, config);
103
104
  }
104
105
  if (response.status >= 300 && response.status < 400) {
@@ -113,12 +114,12 @@ ${contentPage.markdown}`;
113
114
  }
114
115
  }
115
116
  if (!response.ok) {
116
- setMarkdownHeaders(event, path, config);
117
+ setMarkdownHeaders(event, path, config, resolveUrl);
117
118
  return notFoundMarkdown(canonicalUrl, path, config);
118
119
  }
119
120
  const contentType = response.headers.get("content-type") || "";
120
121
  if (!contentType.includes("text/html")) {
121
- setMarkdownHeaders(event, path, config);
122
+ setMarkdownHeaders(event, path, config, resolveUrl);
122
123
  return notFoundMarkdown(canonicalUrl, path, config);
123
124
  }
124
125
  const html = await response.text();
@@ -144,6 +145,6 @@ ${contentPage.markdown}`;
144
145
  additionalFrontmatter
145
146
  }
146
147
  );
147
- setMarkdownHeaders(event, path, config);
148
+ setMarkdownHeaders(event, path, config, resolveUrl);
148
149
  return result.markdown;
149
150
  });
@@ -7,7 +7,9 @@ import type { ModulePublicRuntimeConfig } from '../../../module.js';
7
7
  * `encodeURI` preserves `/` separators and other reserved URL characters.
8
8
  */
9
9
  export declare function encodePathForHeader(path: string): string;
10
+ type LinkUrlResolver = (path: string) => string;
10
11
  /**
11
12
  * Build a comma-joined Link header value with the standard alternates plus i18n hreflang variants.
12
13
  */
13
- export declare function buildLinkHeader(path: string, variant: 'html' | 'markdown', config: ModulePublicRuntimeConfig): string;
14
+ export declare function buildLinkHeader(path: string, variant: 'html' | 'markdown', config: ModulePublicRuntimeConfig, resolveUrl?: LinkUrlResolver): string;
15
+ export {};
@@ -7,7 +7,16 @@ function toMarkdownPath(path) {
7
7
  return `${path}index.md`;
8
8
  return `${path}.md`;
9
9
  }
10
- export function buildLinkHeader(path, variant, config) {
10
+ function resolveHeaderUrl(path, resolveUrl) {
11
+ if (!resolveUrl)
12
+ return path;
13
+ try {
14
+ return resolveUrl(path);
15
+ } catch {
16
+ return path;
17
+ }
18
+ }
19
+ export function buildLinkHeader(path, variant, config, resolveUrl) {
11
20
  const parts = [];
12
21
  if (variant === "html") {
13
22
  parts.push(`<${encodePathForHeader(toMarkdownPath(path))}>; rel="alternate"; type="text/markdown"`);
@@ -18,7 +27,7 @@ export function buildLinkHeader(path, variant, config) {
18
27
  const alternates = computeLocaleAlternates(path, config.i18n);
19
28
  for (const alt of alternates) {
20
29
  const href = variant === "markdown" ? toMarkdownPath(alt.path) : alt.path;
21
- parts.push(`<${encodePathForHeader(href)}>; rel="alternate"; hreflang="${alt.hreflang}"`);
30
+ parts.push(`<${encodePathForHeader(resolveHeaderUrl(href, resolveUrl))}>; rel="alternate"; hreflang="${alt.hreflang}"`);
22
31
  }
23
32
  }
24
33
  return parts.join(", ");
@@ -42,6 +42,12 @@ export interface ModuleOptions {
42
42
  * Allow Training or fine-tuning AI models.
43
43
  */
44
44
  aiTrain?: boolean;
45
+ /**
46
+ * Emit the draft Content-Usage robots.txt directive.
47
+ * Set to false to avoid robots.txt validators that do not support it yet.
48
+ * @default true
49
+ */
50
+ contentUsage?: boolean;
45
51
  /**
46
52
  * Allow building a search index and providing search results (e.g., returning hyperlinks and short excerpts from your website's contents).
47
53
  * Search does not include providing AI-generated search summaries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ai-ready",
3
3
  "type": "module",
4
- "version": "1.4.0",
4
+ "version": "1.5.0",
5
5
  "description": "Best practice AI & LLM discoverability for Nuxt sites.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -57,56 +57,56 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@mdream/js": "^1.3.0",
60
+ "@mdream/js": "^1.4.1",
61
61
  "@nuxt/kit": "^4.4.8",
62
62
  "citty": "^0.2.2",
63
63
  "consola": "^3.4.2",
64
64
  "defu": "^6.1.7",
65
65
  "drizzle-orm": "^0.45.2",
66
- "mdream": "^1.3.0",
66
+ "mdream": "^1.4.1",
67
67
  "nuxt-site-config": "^4.1.0",
68
- "nuxtseo-shared": "^5.2.6",
68
+ "nuxtseo-shared": "^5.3.0",
69
69
  "pathe": "^2.0.3",
70
70
  "pkg-types": "^2.3.1",
71
- "site-config-stack": "^4.0.8",
71
+ "site-config-stack": "^4.1.0",
72
72
  "ufo": "^1.6.4",
73
73
  "uncrypto": "^0.1.3"
74
74
  },
75
75
  "devDependencies": {
76
- "@antfu/eslint-config": "^9.0.0",
77
- "@arethetypeswrong/cli": "^0.18.3",
78
- "@libsql/client": "^0.17.3",
76
+ "@antfu/eslint-config": "^9.1.0",
77
+ "@arethetypeswrong/cli": "^0.18.4",
78
+ "@libsql/client": "^0.17.4",
79
79
  "@nuxt/content": "^3.14.0",
80
80
  "@nuxt/module-builder": "^1.0.2",
81
81
  "@nuxt/test-utils": "^4.0.3",
82
82
  "@nuxtjs/eslint-config-typescript": "^12.1.0",
83
83
  "@nuxtjs/mcp-toolkit": "^0.17.2",
84
- "@nuxtjs/robots": "^6.0.9",
85
- "@nuxtjs/sitemap": "^8.2.0",
84
+ "@nuxtjs/robots": "^6.1.1",
85
+ "@nuxtjs/sitemap": "^8.2.1",
86
86
  "@types/better-sqlite3": "^7.6.13",
87
- "@vitest/coverage-v8": "^4.1.8",
87
+ "@vitest/coverage-v8": "^4.1.9",
88
88
  "@vue/test-utils": "^2.4.11",
89
89
  "@vueuse/nuxt": "^14.3.0",
90
- "better-sqlite3": "^12.10.0",
90
+ "better-sqlite3": "^12.11.1",
91
91
  "bumpp": "^11.1.0",
92
- "eslint": "^10.4.1",
92
+ "eslint": "^10.5.0",
93
93
  "eslint-plugin-harlanzw": "^0.17.0",
94
94
  "execa": "^9.6.1",
95
- "happy-dom": "^20.10.2",
95
+ "happy-dom": "^20.10.6",
96
96
  "nitropack": "^2.13.4",
97
97
  "nuxt": "^4.4.8",
98
98
  "nuxt-site-config": "^4.1.0",
99
- "nuxtseo-layer-devtools": "^5.2.6",
100
- "playwright": "^1.60.0",
101
- "playwright-core": "^1.60.0",
99
+ "nuxtseo-layer-devtools": "^5.3.0",
100
+ "playwright": "^1.61.1",
101
+ "playwright-core": "^1.61.1",
102
102
  "tinyglobby": "^0.2.17",
103
103
  "typescript": "^6.0.3",
104
104
  "unbuild": "^3.6.1",
105
- "vitest": "^4.1.8",
106
- "vue": "^3.5.35",
105
+ "vitest": "^4.1.9",
106
+ "vue": "^3.5.38",
107
107
  "vue-router": "^5.1.0",
108
- "vue-tsc": "^3.3.4",
109
- "wrangler": "^4.99.0",
108
+ "vue-tsc": "^3.3.5",
109
+ "wrangler": "^4.104.0",
110
110
  "zod": "^4.4.3"
111
111
  },
112
112
  "scripts": {