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 +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +19 -19
- package/dist/runtime/server/routes/__ai-ready/indexnow.post.js +1 -1
- package/dist/runtime/server/routes/__ai-ready/status.get.js +1 -1
- package/dist/runtime/server/routes/__ai-ready-debug.get.js +1 -1
- package/dist/runtime/server/routes/indexnow-key.get.js +2 -2
- package/dist/runtime/server/utils/indexnow.js +2 -2
- package/dist/runtime/server/utils/runCron.js +2 -2
- package/dist/runtime/types.d.ts +5 -6
- package/package.json +1 -1
package/dist/module.d.mts
CHANGED
package/dist/module.json
CHANGED
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,
|
|
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}/${
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
349
|
-
prevMeta = await fetchPreviousMeta(state.siteInfo.url, state.
|
|
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.
|
|
361
|
-
await handleStaticIndexNow(pageHashes, state.siteInfo.url, state.
|
|
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
|
|
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: !!
|
|
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
|
-
|
|
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 (
|
|
780
|
-
addServerHandler({ route: `/${
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
5
|
+
if (!config.indexNow)
|
|
6
6
|
return null;
|
|
7
7
|
setHeader(event, "Content-Type", "text/plain; charset=utf-8");
|
|
8
|
-
return config.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 };
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -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
|
|
109
|
+
* Also runs IndexNow sync if indexNow is enabled
|
|
110
110
|
*/
|
|
111
111
|
cron?: boolean;
|
|
112
112
|
/**
|
|
113
|
-
* IndexNow
|
|
114
|
-
*
|
|
115
|
-
*
|
|
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
|
-
|
|
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
|