nuxt-ai-ready 0.8.2 → 0.9.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/dist/module.d.mts CHANGED
@@ -46,7 +46,7 @@ interface ModulePublicRuntimeConfig {
46
46
  pruneTtl: number;
47
47
  };
48
48
  runtimeSyncSecret?: string;
49
- indexNowKey?: string;
49
+ indexNow?: string;
50
50
  }
51
51
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
52
52
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=4.0.0"
5
5
  },
6
6
  "configKey": "aiReady",
7
- "version": "0.8.2",
7
+ "version": "0.9.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { randomBytes } from 'node:crypto';
1
+ import { createHash, randomBytes } from 'node:crypto';
2
2
  import { mkdir, writeFile, appendFile, stat, access } from 'node:fs/promises';
3
3
  import { join, dirname } from 'node:path';
4
4
  import { useLogger, useNuxt, addTemplate, addTypeTemplate, defineNuxtModule, createResolver, hasNuxtModule, addServerHandler, addPlugin } from '@nuxt/kit';
@@ -79,7 +79,7 @@ function hookNuxtSeoProLicense() {
79
79
  }
80
80
  }
81
81
 
82
- async function fetchPreviousMeta(siteUrl, indexNowKey) {
82
+ async function fetchPreviousMeta(siteUrl, indexNow) {
83
83
  const metaUrl = `${siteUrl}/__ai-ready/pages.meta.json`;
84
84
  logger.debug(`[indexnow] Fetching previous meta from ${metaUrl}`);
85
85
  const prevMeta = await fetch(metaUrl).then((r) => r.ok ? r.json() : null).catch(() => null);
@@ -87,7 +87,7 @@ async function fetchPreviousMeta(siteUrl, indexNowKey) {
87
87
  logger.info("[indexnow] First deploy or no previous meta - skipping IndexNow");
88
88
  return null;
89
89
  }
90
- const keyUrl = `${siteUrl}/${indexNowKey}.txt`;
90
+ const keyUrl = `${siteUrl}/${indexNow}.txt`;
91
91
  const keyLive = await fetch(keyUrl).then((r) => r.ok).catch(() => false);
92
92
  if (!keyLive) {
93
93
  logger.info("[indexnow] Key file not live yet - skipping IndexNow");
@@ -95,7 +95,7 @@ async function fetchPreviousMeta(siteUrl, indexNowKey) {
95
95
  }
96
96
  return prevMeta;
97
97
  }
98
- async function handleStaticIndexNow(currentPages, siteUrl, indexNowKey, prevMeta) {
98
+ async function handleStaticIndexNow(currentPages, siteUrl, indexNow, prevMeta) {
99
99
  const { changed, added } = comparePageHashes(currentPages, prevMeta);
100
100
  const totalChanged = changed.length + added.length;
101
101
  if (totalChanged === 0) {
@@ -105,7 +105,7 @@ async function handleStaticIndexNow(currentPages, siteUrl, indexNowKey, prevMeta
105
105
  logger.info(`[indexnow] Submitting ${totalChanged} changed pages (${changed.length} modified, ${added.length} new)`);
106
106
  const result = await submitToIndexNowShared(
107
107
  [...changed, ...added],
108
- indexNowKey,
108
+ indexNow,
109
109
  siteUrl,
110
110
  { logger }
111
111
  );
@@ -115,7 +115,7 @@ async function handleStaticIndexNow(currentPages, siteUrl, indexNowKey, prevMeta
115
115
  logger.warn(`[indexnow] Failed to submit: ${result.error}`);
116
116
  }
117
117
  }
118
- function createCrawlerState(dbPath, llmsFullTxtPath, siteInfo, llmsTxtConfig, indexNowKey) {
118
+ function createCrawlerState(dbPath, llmsFullTxtPath, siteInfo, llmsTxtConfig, indexNow) {
119
119
  return {
120
120
  prerenderedRoutes: /* @__PURE__ */ new Set(),
121
121
  errorRoutes: /* @__PURE__ */ new Set(),
@@ -125,7 +125,7 @@ function createCrawlerState(dbPath, llmsFullTxtPath, siteInfo, llmsTxtConfig, in
125
125
  llmsFullTxtPath,
126
126
  siteInfo,
127
127
  llmsTxtConfig,
128
- indexNowKey
128
+ indexNow
129
129
  };
130
130
  }
131
131
  async function initCrawler(state) {
@@ -276,11 +276,11 @@ async function prerenderRoute(nitro, route) {
276
276
  nitro._prerenderedRoutes.push(_route);
277
277
  return stat(filePath);
278
278
  }
279
- function setupPrerenderHandler(dbPath, siteInfo, llmsTxtConfig, indexNowKey) {
279
+ function setupPrerenderHandler(dbPath, siteInfo, llmsTxtConfig, indexNow) {
280
280
  const nuxt = useNuxt();
281
281
  nuxt.hooks.hook("nitro:init", async (nitro) => {
282
282
  const llmsFullTxtPath = join(nitro.options.output.publicDir, "llms-full.txt");
283
- const state = createCrawlerState(dbPath, llmsFullTxtPath, siteInfo, llmsTxtConfig, indexNowKey);
283
+ const state = createCrawlerState(dbPath, llmsFullTxtPath, siteInfo, llmsTxtConfig, indexNow);
284
284
  let initPromise = null;
285
285
  nitro.hooks.hook("prerender:generate", async (route) => {
286
286
  if (route.error) {
@@ -345,8 +345,8 @@ function setupPrerenderHandler(dbPath, siteInfo, llmsTxtConfig, indexNowKey) {
345
345
  logger.debug(`Created database dump at __ai-ready/pages.dump (${(dumpData.length / 1024).toFixed(1)}kb compressed)`);
346
346
  const pageHashes = pages.filter((p) => p.contentHash).map((p) => ({ route: p.route, hash: p.contentHash }));
347
347
  let prevMeta = null;
348
- if (state.indexNowKey && state.siteInfo?.url) {
349
- prevMeta = await fetchPreviousMeta(state.siteInfo.url, state.indexNowKey);
348
+ if (state.indexNow && state.siteInfo?.url) {
349
+ prevMeta = await fetchPreviousMeta(state.siteInfo.url, state.indexNow);
350
350
  }
351
351
  const buildId = Date.now().toString(36);
352
352
  const metaContent = JSON.stringify({
@@ -357,8 +357,8 @@ function setupPrerenderHandler(dbPath, siteInfo, llmsTxtConfig, indexNowKey) {
357
357
  });
358
358
  await writeFile(join(publicDataDir, "pages.meta.json"), metaContent, "utf-8");
359
359
  logger.debug(`Wrote build metadata: buildId=${buildId}, ${pageHashes.length} page hashes`);
360
- if (state.indexNowKey && state.siteInfo?.url && prevMeta) {
361
- await handleStaticIndexNow(pageHashes, state.siteInfo.url, state.indexNowKey, prevMeta);
360
+ if (state.indexNow && state.siteInfo?.url && prevMeta) {
361
+ await handleStaticIndexNow(pageHashes, state.siteInfo.url, state.indexNow, prevMeta);
362
362
  }
363
363
  }
364
364
  const llmsStats = await prerenderRoute(nitro, "/llms.txt");
@@ -637,7 +637,7 @@ const module$1 = defineNuxtModule({
637
637
  const buildDbPath = join(nuxt.options.buildDir, ".data/ai-ready/build.db");
638
638
  const runtimeSyncConfig = typeof config.runtimeSync === "object" ? config.runtimeSync : {};
639
639
  const runtimeSyncEnabled = !!config.runtimeSync || !!config.cron;
640
- const indexNowKey = config.indexNowKey || process.env.NUXT_AI_READY_INDEX_NOW_KEY;
640
+ const indexNow = config.indexNow === true ? createHash("sha256").update(useSiteConfig().url || "nuxt-ai-ready").digest("hex").slice(0, 32) : config.indexNow || process.env.NUXT_AI_READY_INDEX_NOW_KEY;
641
641
  let runtimeSyncSecret = config.runtimeSyncSecret || process.env.NUXT_AI_READY_RUNTIME_SYNC_SECRET;
642
642
  if (!runtimeSyncSecret && runtimeSyncEnabled) {
643
643
  runtimeSyncSecret = randomBytes(32).toString("hex");
@@ -653,7 +653,7 @@ const module$1 = defineNuxtModule({
653
653
  secret: runtimeSyncSecret,
654
654
  features: {
655
655
  cron: !!config.cron,
656
- indexNow: !!indexNowKey,
656
+ indexNow: !!indexNow,
657
657
  runtimeSync: runtimeSyncEnabled
658
658
  }
659
659
  });
@@ -749,7 +749,7 @@ export const errorRoutes = []`;
749
749
  pruneTtl: runtimeSyncConfig.pruneTtl ?? 0
750
750
  },
751
751
  runtimeSyncSecret,
752
- indexNowKey
752
+ indexNow
753
753
  };
754
754
  addServerHandler({
755
755
  middleware: true,
@@ -776,8 +776,8 @@ export const errorRoutes = []`;
776
776
  addServerHandler({ route: "/__ai-ready/prune", method: "post", handler: resolve("./runtime/server/routes/__ai-ready/prune.post") });
777
777
  addServerHandler({ route: "/__ai-ready/restore", method: "post", handler: resolve("./runtime/server/routes/__ai-ready/restore.post") });
778
778
  }
779
- if (indexNowKey) {
780
- addServerHandler({ route: `/${indexNowKey}.txt`, handler: resolve("./runtime/server/routes/indexnow-key.get") });
779
+ if (indexNow) {
780
+ addServerHandler({ route: `/${indexNow}.txt`, handler: resolve("./runtime/server/routes/indexnow-key.get") });
781
781
  addServerHandler({ route: "/__ai-ready/indexnow", method: "post", handler: resolve("./runtime/server/routes/__ai-ready/indexnow.post") });
782
782
  if (!runtimeSyncEnabled) {
783
783
  addServerHandler({ route: "/__ai-ready/status", handler: resolve("./runtime/server/routes/__ai-ready/status.get") });
@@ -803,7 +803,7 @@ export const errorRoutes = []`;
803
803
  name: siteConfig.name,
804
804
  url: siteConfig.url,
805
805
  description: siteConfig.description
806
- }, mergedLlmsTxt, indexNowKey);
806
+ }, mergedLlmsTxt, indexNow);
807
807
  }
808
808
  nuxt.options.nitro.routeRules = nuxt.options.nitro.routeRules || {};
809
809
  nuxt.options.nitro.routeRules["/llms.txt"] = { headers: { "Content-Type": "text/plain; charset=utf-8" } };
@@ -3,7 +3,7 @@ import { useRuntimeConfig } from "nitropack/runtime";
3
3
  import { syncToIndexNow } from "../../utils/indexnow.js";
4
4
  export default eventHandler(async (event) => {
5
5
  const config = useRuntimeConfig(event)["nuxt-ai-ready"];
6
- if (!config.indexNowKey) {
6
+ if (!config.indexNow) {
7
7
  throw createError({ statusCode: 400, message: "IndexNow not configured" });
8
8
  }
9
9
  const query = getQuery(event);
@@ -18,7 +18,7 @@ export default eventHandler(async (event) => {
18
18
  indexed: total - pending,
19
19
  pending
20
20
  };
21
- if (config.indexNowKey) {
21
+ if (config.indexNow) {
22
22
  const [indexNowPending, indexNowStats] = await Promise.all([
23
23
  countPagesNeedingIndexNowSync(event),
24
24
  getIndexNowStats(event)
@@ -135,7 +135,7 @@ export default eventHandler(async (event) => {
135
135
  indexNowRemaining: run.indexNowRemaining,
136
136
  errors: run.errors
137
137
  }));
138
- if (runtimeConfig.indexNowKey) {
138
+ if (runtimeConfig.indexNow) {
139
139
  const [indexNowPending, indexNowStats, indexNowLogEntries] = await Promise.all([
140
140
  countPagesNeedingIndexNowSync(event),
141
141
  getIndexNowStats(event),
@@ -2,8 +2,8 @@ import { eventHandler, setHeader } from "h3";
2
2
  import { useRuntimeConfig } from "nitropack/runtime";
3
3
  export default eventHandler((event) => {
4
4
  const config = useRuntimeConfig(event)["nuxt-ai-ready"];
5
- if (!config.indexNowKey)
5
+ if (!config.indexNow)
6
6
  return null;
7
7
  setHeader(event, "Content-Type", "text/plain; charset=utf-8");
8
- return config.indexNowKey;
8
+ return config.indexNow;
9
9
  });
@@ -53,7 +53,7 @@ export async function submitToIndexNow(routes, config, siteUrl) {
53
53
  export async function syncToIndexNow(event, limit = 100, options) {
54
54
  const config = useRuntimeConfig(event)["nuxt-ai-ready"];
55
55
  const siteConfig = getSiteConfig(event);
56
- if (!config.indexNowKey) {
56
+ if (!config.indexNow) {
57
57
  return { success: false, submitted: 0, remaining: 0, error: "IndexNow not configured" };
58
58
  }
59
59
  if (!siteConfig.url) {
@@ -80,7 +80,7 @@ export async function syncToIndexNow(event, limit = 100, options) {
80
80
  return { success: true, submitted: 0, remaining: 0 };
81
81
  }
82
82
  const routes = pages.map((p) => p.route);
83
- const result = await submitToIndexNow(routes, { key: config.indexNowKey }, siteConfig.url);
83
+ const result = await submitToIndexNow(routes, { key: config.indexNow }, siteConfig.url);
84
84
  const dbUpdates = async () => {
85
85
  if (config.debug) {
86
86
  await logIndexNowSubmission(event, routes.length, result.success, result.error);
@@ -28,7 +28,7 @@ export async function runCron(providedEvent, options) {
28
28
  const results = {};
29
29
  const allErrors = [];
30
30
  if (debug) {
31
- logger.info(`[cron] Starting cron run (batchSize: ${options?.batchSize ?? config.runtimeSync.batchSize}, indexNow: ${!!config.indexNowKey})`);
31
+ logger.info(`[cron] Starting cron run (batchSize: ${options?.batchSize ?? config.runtimeSync.batchSize}, indexNow: ${!!config.indexNow})`);
32
32
  }
33
33
  if (config.runtimeSync.enabled) {
34
34
  results.stale = await checkAndHandleStale(event).catch((err) => {
@@ -72,7 +72,7 @@ export async function runCron(providedEvent, options) {
72
72
  logger.info(`[cron] Index: ${indexResult.indexed} pages (${indexResult.remaining} remaining${indexResult.errors.length > 0 ? `, ${indexResult.errors.length} errors` : ""})`);
73
73
  }
74
74
  }
75
- if (config.indexNowKey) {
75
+ if (config.indexNow) {
76
76
  const indexNowResult = await syncToIndexNow(event, 100).catch((err) => {
77
77
  console.warn("[ai-ready:cron] IndexNow sync failed:", err.message);
78
78
  return { success: false, submitted: 0, remaining: 0, error: err.message };
@@ -106,16 +106,15 @@ export interface ModuleOptions {
106
106
  /**
107
107
  * Enable scheduled cron task (runs every minute)
108
108
  * When true, automatically enables runtimeSync for background indexing
109
- * Also runs IndexNow sync if indexNowKey is configured
109
+ * Also runs IndexNow sync if indexNow is enabled
110
110
  */
111
111
  cron?: boolean;
112
112
  /**
113
- * IndexNow API key for instant search engine notifications
114
- * When set, enables IndexNow submissions to Bing, Yandex, Naver, Seznam
115
- * Get one from https://www.bing.com/indexnow
116
- * Can also be set via NUXT_AI_READY_INDEX_NOW_KEY env var
113
+ * Enable IndexNow for instant search engine notifications
114
+ * Submits to Bing, Yandex, Naver, Seznam when content changes
115
+ * Set to `true` to derive key from site URL, or provide your own string
117
116
  */
118
- indexNowKey?: string;
117
+ indexNow?: boolean | string;
119
118
  /**
120
119
  * Secret token for authenticating runtime sync endpoints
121
120
  * When set, requires ?secret=<token> query param for poll/prune/indexnow endpoints
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-ai-ready",
3
3
  "type": "module",
4
- "version": "0.8.2",
4
+ "version": "0.9.0",
5
5
  "description": "Best practice AI & LLM discoverability for Nuxt sites.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",