@nuxtjs/sitemap 7.3.0 → 7.4.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/client/200.html +8 -8
- package/dist/client/404.html +8 -8
- package/dist/client/_nuxt/ChEizYIG.js +172 -0
- package/dist/client/_nuxt/{lL_X76lO.js → SQMF8ibg.js} +1 -1
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/048e1f4c-a575-4a94-bbab-d777afe4c585.json +1 -0
- package/dist/client/_nuxt/{DJVkgDQ2.js → cqJZcoo0.js} +1 -1
- package/dist/client/_nuxt/{entry.CgW0_noo.css → entry.BzbtAPc0.css} +1 -1
- package/dist/client/_nuxt/error-404.DljSaiyF.css +1 -0
- package/dist/client/_nuxt/error-500.DbX9fggi.css +1 -0
- package/dist/client/index.html +8 -8
- package/dist/module.cjs +30 -20
- package/dist/module.json +1 -1
- package/dist/module.mjs +30 -20
- package/dist/runtime/server/content-compat.d.ts +1 -0
- package/dist/runtime/server/content-compat.js +2 -0
- package/dist/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.d.ts +1 -1
- package/dist/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.js +4 -2
- package/dist/runtime/server/routes/sitemap.xsl.js +37 -13
- package/dist/runtime/server/routes/sitemap_index.xml.js +6 -2
- package/dist/runtime/server/sitemap/builder/sitemap-index.d.ts +11 -2
- package/dist/runtime/server/sitemap/builder/sitemap-index.js +23 -5
- package/dist/runtime/server/sitemap/builder/sitemap.d.ts +7 -1
- package/dist/runtime/server/sitemap/builder/sitemap.js +10 -5
- package/dist/runtime/server/sitemap/builder/xml.d.ts +4 -1
- package/dist/runtime/server/sitemap/builder/xml.js +13 -4
- package/dist/runtime/server/sitemap/nitro.js +7 -3
- package/dist/runtime/server/sitemap/urlset/normalise.js +4 -3
- package/dist/runtime/server/sitemap/urlset/sources.js +62 -17
- package/dist/runtime/server/utils.d.ts +1 -0
- package/dist/runtime/server/utils.js +3 -0
- package/dist/runtime/types.d.ts +1 -0
- package/package.json +16 -15
- package/dist/client/_nuxt/SmY-NWqO.js +0 -172
- package/dist/client/_nuxt/builds/meta/e48bfd5b-6605-4bbc-a466-32a664787616.json +0 -1
- package/dist/client/_nuxt/error-404.CtcyoHAN.css +0 -1
- package/dist/client/_nuxt/error-500.BIlfyoPk.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { defineEventHandler, getHeader, setHeader } from "h3";
|
|
1
|
+
import { defineEventHandler, getHeader, setHeader, getQuery as h3GetQuery } from "h3";
|
|
2
2
|
import { getQuery, parseURL, withQuery } from "ufo";
|
|
3
|
-
import { useSitemapRuntimeConfig } from "../utils.js";
|
|
3
|
+
import { useSitemapRuntimeConfig, xmlEscape } from "../utils.js";
|
|
4
4
|
import { useSiteConfig } from "#site-config/server/composables/useSiteConfig";
|
|
5
5
|
import { createSitePathResolver } from "#site-config/server/composables/utils";
|
|
6
6
|
export default defineEventHandler(async (e) => {
|
|
@@ -23,17 +23,37 @@ export default defineEventHandler(async (e) => {
|
|
|
23
23
|
const isShowingCanonical = typeof canonicalQuery !== "undefined" && canonicalQuery !== "false";
|
|
24
24
|
const conditionalTips = [
|
|
25
25
|
'You are looking at a <a href="https://developer.mozilla.org/en-US/docs/Web/XSLT/Transforming_XML_with_XSLT/An_Overview" style="color: #398465" target="_blank">XML stylesheet</a>. Read the <a href="https://nuxtseo.com/sitemap/guides/customising-ui" style="color: #398465" target="_blank">docs</a> to learn how to customize it. View the page source to see the raw XML.',
|
|
26
|
-
`URLs missing? Check Nuxt Devtools Sitemap tab (or the <a href="${withQuery("/__sitemap__/debug.json", { sitemap: sitemapName })}" style="color: #398465" target="_blank">debug endpoint</a>).`
|
|
26
|
+
`URLs missing? Check Nuxt Devtools Sitemap tab (or the <a href="${xmlEscape(withQuery("/__sitemap__/debug.json", { sitemap: sitemapName }))}" style="color: #398465" target="_blank">debug endpoint</a>).`
|
|
27
27
|
];
|
|
28
|
+
const fetchErrors = [];
|
|
29
|
+
const xslQuery = h3GetQuery(e);
|
|
30
|
+
if (xslQuery.error_messages) {
|
|
31
|
+
const errorMessages = xslQuery.error_messages;
|
|
32
|
+
const errorUrls = xslQuery.error_urls;
|
|
33
|
+
if (errorMessages) {
|
|
34
|
+
const messages = Array.isArray(errorMessages) ? errorMessages : [errorMessages];
|
|
35
|
+
const urls = Array.isArray(errorUrls) ? errorUrls : errorUrls ? [errorUrls] : [];
|
|
36
|
+
messages.forEach((msg, i) => {
|
|
37
|
+
const errorParts = [xmlEscape(msg)];
|
|
38
|
+
if (urls[i]) {
|
|
39
|
+
errorParts.push(xmlEscape(urls[i]));
|
|
40
|
+
}
|
|
41
|
+
fetchErrors.push(`<strong style="color: #dc2626;">Error ${i + 1}:</strong> ${errorParts.join(" - ")}`);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
28
45
|
if (!isShowingCanonical) {
|
|
29
46
|
const canonicalPreviewUrl = withQuery(referrer, { canonical: "" });
|
|
30
|
-
conditionalTips.push(`Your canonical site URL is <strong>${siteUrl}</strong>.`);
|
|
31
|
-
conditionalTips.push(`You can preview your canonical sitemap by visiting <a href="${canonicalPreviewUrl}" style="color: #398465; white-space: nowrap;">${fixPath(canonicalPreviewUrl)}?canonical</a>`);
|
|
47
|
+
conditionalTips.push(`Your canonical site URL is <strong>${xmlEscape(siteUrl)}</strong>.`);
|
|
48
|
+
conditionalTips.push(`You can preview your canonical sitemap by visiting <a href="${xmlEscape(canonicalPreviewUrl)}" style="color: #398465; white-space: nowrap;">${xmlEscape(fixPath(canonicalPreviewUrl))}?canonical</a>`);
|
|
32
49
|
} else {
|
|
33
|
-
conditionalTips.push(`You are viewing the canonical sitemap. You can switch to using the request origin: <a href="${fixPath(referrer)}" style="color: #398465; white-space: nowrap ">${fixPath(referrer)}</a>`);
|
|
50
|
+
conditionalTips.push(`You are viewing the canonical sitemap. You can switch to using the request origin: <a href="${xmlEscape(fixPath(referrer))}" style="color: #398465; white-space: nowrap ">${xmlEscape(fixPath(referrer))}</a>`);
|
|
34
51
|
}
|
|
35
|
-
const
|
|
36
|
-
const
|
|
52
|
+
const hasRuntimeErrors = fetchErrors.length > 0;
|
|
53
|
+
const showDevTips = import.meta.dev && xslTips !== false;
|
|
54
|
+
const showSidebar = showDevTips || hasRuntimeErrors;
|
|
55
|
+
const devTips = showDevTips ? conditionalTips.map((t) => `<li><p>${t}</p></li>`).join("\n") : "";
|
|
56
|
+
const runtimeErrors = hasRuntimeErrors ? fetchErrors.map((t) => `<li><p>${t}</p></li>`).join("\n") : "";
|
|
37
57
|
let columns = [...xslColumns];
|
|
38
58
|
if (!columns.length) {
|
|
39
59
|
columns = [
|
|
@@ -97,12 +117,12 @@ export default defineEventHandler(async (e) => {
|
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
.expl a {
|
|
100
|
-
color: #398465
|
|
120
|
+
color: #398465;
|
|
101
121
|
font-weight: 600;
|
|
102
122
|
}
|
|
103
123
|
|
|
104
124
|
.expl a:visited {
|
|
105
|
-
color: #398465
|
|
125
|
+
color: #398465;
|
|
106
126
|
}
|
|
107
127
|
|
|
108
128
|
a {
|
|
@@ -153,8 +173,8 @@ export default defineEventHandler(async (e) => {
|
|
|
153
173
|
<div>
|
|
154
174
|
<div id="content">
|
|
155
175
|
<h1 class="text-2xl mb-3">XML Sitemap</h1>
|
|
156
|
-
<h2>${title}</h2>
|
|
157
|
-
${isNotIndexButHasIndex ? `<p style="font-size: 12px; margin-bottom: 1rem;"><a href="${fixPath("/sitemap_index.xml")}">${fixPath("/sitemap_index.xml")}</a></p>` : ""}
|
|
176
|
+
<h2>${xmlEscape(title)}</h2>
|
|
177
|
+
${isNotIndexButHasIndex ? `<p style="font-size: 12px; margin-bottom: 1rem;"><a href="${xmlEscape(fixPath("/sitemap_index.xml"))}">${xmlEscape(fixPath("/sitemap_index.xml"))}</a></p>` : ""}
|
|
158
178
|
<xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) > 0">
|
|
159
179
|
<p class="expl" style="margin-bottom: 1rem;">
|
|
160
180
|
This XML Sitemap Index file contains
|
|
@@ -221,7 +241,11 @@ export default defineEventHandler(async (e) => {
|
|
|
221
241
|
</xsl:if>
|
|
222
242
|
</div>
|
|
223
243
|
</div>
|
|
224
|
-
${
|
|
244
|
+
${showSidebar ? `<div class="w-30 top-2 shadow rounded p-5 right-2" style="margin: 0 auto;">
|
|
245
|
+
${showDevTips ? `<div><p><strong>Development Tips</strong></p><ul style="margin: 1rem 0; padding: 0;">${devTips}</ul></div>` : ""}
|
|
246
|
+
${hasRuntimeErrors ? `<div${showDevTips ? ' style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;"' : ""}><p><strong style="color: #dc2626;">Runtime Errors</strong></p><ul style="margin: 1rem 0; padding: 0;">${runtimeErrors}</ul></div>` : ""}
|
|
247
|
+
${showDevTips ? `<p style="margin-top: 1rem;">${creditName}</p>` : ""}
|
|
248
|
+
</div>` : ""}
|
|
225
249
|
</div>
|
|
226
250
|
</body>
|
|
227
251
|
</html>
|
|
@@ -8,7 +8,7 @@ export default defineEventHandler(async (e) => {
|
|
|
8
8
|
const runtimeConfig = useSitemapRuntimeConfig();
|
|
9
9
|
const nitro = useNitroApp();
|
|
10
10
|
const resolvers = useNitroUrlResolvers(e);
|
|
11
|
-
const sitemaps = await buildSitemapIndex(resolvers, runtimeConfig, nitro);
|
|
11
|
+
const { entries: sitemaps, failedSources } = await buildSitemapIndex(resolvers, runtimeConfig, nitro);
|
|
12
12
|
if (import.meta.prerender) {
|
|
13
13
|
appendHeader(
|
|
14
14
|
e,
|
|
@@ -18,7 +18,11 @@ export default defineEventHandler(async (e) => {
|
|
|
18
18
|
}
|
|
19
19
|
const indexResolvedCtx = { sitemaps, event: e };
|
|
20
20
|
await nitro.hooks.callHook("sitemap:index-resolved", indexResolvedCtx);
|
|
21
|
-
const
|
|
21
|
+
const errorInfo = failedSources.length > 0 ? {
|
|
22
|
+
messages: failedSources.map((f) => f.error),
|
|
23
|
+
urls: failedSources.map((f) => f.url)
|
|
24
|
+
} : void 0;
|
|
25
|
+
const output = urlsToIndexXml(indexResolvedCtx.sitemaps, resolvers, runtimeConfig, errorInfo);
|
|
22
26
|
const ctx = { sitemap: output, sitemapName: "sitemap", event: e };
|
|
23
27
|
await nitro.hooks.callHook("sitemap:output", ctx);
|
|
24
28
|
setHeader(e, "Content-Type", "text/xml; charset=UTF-8");
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { NitroApp } from 'nitropack/types';
|
|
2
2
|
import type { ModuleRuntimeConfig, NitroUrlResolvers, SitemapIndexEntry } from '../../../types.js';
|
|
3
|
-
export declare function urlsToIndexXml(sitemaps: SitemapIndexEntry[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'
|
|
4
|
-
|
|
3
|
+
export declare function urlsToIndexXml(sitemaps: SitemapIndexEntry[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
|
|
4
|
+
messages: string[];
|
|
5
|
+
urls: string[];
|
|
6
|
+
}): string;
|
|
7
|
+
export declare function buildSitemapIndex(resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp): Promise<{
|
|
8
|
+
entries: SitemapIndexEntry[];
|
|
9
|
+
failedSources: Array<{
|
|
10
|
+
url: string;
|
|
11
|
+
error: string;
|
|
12
|
+
}>;
|
|
13
|
+
}>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defu } from "defu";
|
|
2
|
-
import { joinURL } from "ufo";
|
|
2
|
+
import { joinURL, withQuery } from "ufo";
|
|
3
3
|
import { defineCachedFunction } from "nitropack/runtime";
|
|
4
4
|
import { getHeader } from "h3";
|
|
5
5
|
import { normaliseDate } from "../urlset/normalise.js";
|
|
@@ -45,6 +45,7 @@ async function buildSitemapIndexInternal(resolvers, runtimeConfig, nitro) {
|
|
|
45
45
|
return sortEntries ? sortInPlace(urls) : urls;
|
|
46
46
|
}
|
|
47
47
|
const chunks = {};
|
|
48
|
+
const allFailedSources = [];
|
|
48
49
|
for (const sitemapName in sitemaps) {
|
|
49
50
|
if (sitemapName === "index" || sitemapName === "chunks") continue;
|
|
50
51
|
const sitemapConfig = sitemaps[sitemapName];
|
|
@@ -68,6 +69,11 @@ async function buildSitemapIndexInternal(resolvers, runtimeConfig, nitro) {
|
|
|
68
69
|
sourcesInput = ctx.sources;
|
|
69
70
|
}
|
|
70
71
|
const sources = await resolveSitemapSources(sourcesInput, resolvers.event);
|
|
72
|
+
const failedSources = sources.filter((source) => source.error && source._isFailure).map((source) => ({
|
|
73
|
+
url: typeof source.fetch === "string" ? source.fetch : source.fetch?.[0] || "unknown",
|
|
74
|
+
error: source.error || "Unknown error"
|
|
75
|
+
}));
|
|
76
|
+
allFailedSources.push(...failedSources);
|
|
71
77
|
const resolvedCtx = {
|
|
72
78
|
urls: sources.flatMap((s) => s.urls),
|
|
73
79
|
sitemapName: sitemap.sitemapName,
|
|
@@ -113,6 +119,11 @@ async function buildSitemapIndexInternal(resolvers, runtimeConfig, nitro) {
|
|
|
113
119
|
sourcesInput = ctx.sources;
|
|
114
120
|
}
|
|
115
121
|
const sources = await resolveSitemapSources(sourcesInput, resolvers.event);
|
|
122
|
+
const failedSources = sources.filter((source) => source.error && source._isFailure).map((source) => ({
|
|
123
|
+
url: typeof source.fetch === "string" ? source.fetch : source.fetch?.[0] || "unknown",
|
|
124
|
+
error: source.error || "Unknown error"
|
|
125
|
+
}));
|
|
126
|
+
allFailedSources.push(...failedSources);
|
|
116
127
|
const resolvedCtx = {
|
|
117
128
|
urls: sources.flatMap((s) => s.urls),
|
|
118
129
|
sitemapName: sitemapConfig.sitemapName,
|
|
@@ -144,9 +155,9 @@ async function buildSitemapIndexInternal(resolvers, runtimeConfig, nitro) {
|
|
|
144
155
|
return typeof entry === "string" ? { sitemap: entry } : entry;
|
|
145
156
|
}));
|
|
146
157
|
}
|
|
147
|
-
return entries;
|
|
158
|
+
return { entries, failedSources: allFailedSources };
|
|
148
159
|
}
|
|
149
|
-
export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, minify }) {
|
|
160
|
+
export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, minify }, errorInfo) {
|
|
150
161
|
const sitemapXml = sitemaps.map((e) => [
|
|
151
162
|
" <sitemap>",
|
|
152
163
|
` <loc>${escapeValueForXml(e.sitemap)}</loc>`,
|
|
@@ -158,7 +169,14 @@ export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, min
|
|
|
158
169
|
'<?xml version="1.0" encoding="UTF-8"?>'
|
|
159
170
|
];
|
|
160
171
|
if (xsl) {
|
|
161
|
-
|
|
172
|
+
let relativeBaseUrl = resolvers.relativeBaseUrlResolver?.(xsl) ?? xsl;
|
|
173
|
+
if (errorInfo && errorInfo.messages.length > 0) {
|
|
174
|
+
relativeBaseUrl = withQuery(relativeBaseUrl, {
|
|
175
|
+
errors: "true",
|
|
176
|
+
error_messages: errorInfo.messages,
|
|
177
|
+
error_urls: errorInfo.urls
|
|
178
|
+
});
|
|
179
|
+
}
|
|
162
180
|
xmlParts.push(`<?xml-stylesheet type="text/xsl" href="${escapeValueForXml(relativeBaseUrl)}"?>`);
|
|
163
181
|
}
|
|
164
182
|
xmlParts.push(
|
|
@@ -172,7 +190,7 @@ export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, min
|
|
|
172
190
|
return minify ? xmlParts.join("").replace(/(?<!<[^>]*)\s(?![^<]*>)/g, "") : xmlParts.join("\n");
|
|
173
191
|
}
|
|
174
192
|
export async function buildSitemapIndex(resolvers, runtimeConfig, nitro) {
|
|
175
|
-
if (!import.meta.dev &&
|
|
193
|
+
if (!import.meta.dev && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0 && resolvers.event) {
|
|
176
194
|
return buildSitemapIndexCached(resolvers.event, resolvers, runtimeConfig, nitro);
|
|
177
195
|
}
|
|
178
196
|
return buildSitemapIndexInternal(resolvers, runtimeConfig, nitro);
|
|
@@ -6,5 +6,11 @@ export interface NormalizedI18n extends ResolvedSitemapUrl {
|
|
|
6
6
|
_index?: number;
|
|
7
7
|
}
|
|
8
8
|
export declare function resolveSitemapEntries(sitemap: SitemapDefinition, urls: SitemapUrlInput[], runtimeConfig: Pick<ModuleRuntimeConfig, 'autoI18n' | 'isI18nMapped'>, resolvers?: NitroUrlResolvers): ResolvedSitemapUrl[];
|
|
9
|
-
export declare function buildSitemapUrls(sitemap: SitemapDefinition, resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp): Promise<
|
|
9
|
+
export declare function buildSitemapUrls(sitemap: SitemapDefinition, resolvers: NitroUrlResolvers, runtimeConfig: ModuleRuntimeConfig, nitro?: NitroApp): Promise<{
|
|
10
|
+
urls: ResolvedSitemapUrl[];
|
|
11
|
+
failedSources: Array<{
|
|
12
|
+
url: string;
|
|
13
|
+
error: string;
|
|
14
|
+
}>;
|
|
15
|
+
}>;
|
|
10
16
|
export { urlsToXml } from './xml.js';
|
|
@@ -182,11 +182,11 @@ export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro)
|
|
|
182
182
|
defaultSitemapsChunkSize
|
|
183
183
|
} = runtimeConfig;
|
|
184
184
|
const chunkInfo = parseChunkInfo(sitemap.sitemapName, sitemaps, defaultSitemapsChunkSize);
|
|
185
|
-
function maybeSort(
|
|
186
|
-
return sortEntries ? sortInPlace(
|
|
185
|
+
function maybeSort(urls2) {
|
|
186
|
+
return sortEntries ? sortInPlace(urls2) : urls2;
|
|
187
187
|
}
|
|
188
|
-
function maybeSlice(
|
|
189
|
-
return sliceUrlsForChunk(
|
|
188
|
+
function maybeSlice(urls2) {
|
|
189
|
+
return sliceUrlsForChunk(urls2, sitemap.sitemapName, sitemaps, defaultSitemapsChunkSize);
|
|
190
190
|
}
|
|
191
191
|
if (autoI18n?.differentDomains) {
|
|
192
192
|
const domain = autoI18n.locales.find((e) => [e.language, e.code].includes(sitemap.sitemapName))?.domain;
|
|
@@ -218,6 +218,10 @@ export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro)
|
|
|
218
218
|
sourcesInput = ctx.sources;
|
|
219
219
|
}
|
|
220
220
|
const sources = await resolveSitemapSources(sourcesInput, resolvers.event);
|
|
221
|
+
const failedSources = sources.filter((source) => source.error && source._isFailure).map((source) => ({
|
|
222
|
+
url: typeof source.fetch === "string" ? source.fetch : source.fetch?.[0] || "unknown",
|
|
223
|
+
error: source.error || "Unknown error"
|
|
224
|
+
}));
|
|
221
225
|
const resolvedCtx = {
|
|
222
226
|
urls: sources.flatMap((s) => s.urls),
|
|
223
227
|
sitemapName: sitemap.sitemapName,
|
|
@@ -231,6 +235,7 @@ export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro)
|
|
|
231
235
|
return true;
|
|
232
236
|
});
|
|
233
237
|
const sortedUrls = maybeSort(filteredUrls);
|
|
234
|
-
|
|
238
|
+
const urls = maybeSlice(sortedUrls);
|
|
239
|
+
return { urls, failedSources };
|
|
235
240
|
}
|
|
236
241
|
export { urlsToXml } from "./xml.js";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl } from '../../../types.js';
|
|
2
2
|
export declare function escapeValueForXml(value: boolean | string | number): string;
|
|
3
|
-
export declare function urlsToXml(urls: ResolvedSitemapUrl[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'
|
|
3
|
+
export declare function urlsToXml(urls: ResolvedSitemapUrl[], resolvers: NitroUrlResolvers, { version, xsl, credits, minify }: Pick<ModuleRuntimeConfig, 'version' | 'xsl' | 'credits' | 'minify'>, errorInfo?: {
|
|
4
|
+
messages: string[];
|
|
5
|
+
urls: string[];
|
|
6
|
+
}): string;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { withQuery } from "ufo";
|
|
2
|
+
import { xmlEscape } from "../../utils.js";
|
|
1
3
|
export function escapeValueForXml(value) {
|
|
2
4
|
if (value === true || value === false)
|
|
3
5
|
return value ? "yes" : "no";
|
|
4
|
-
return String(value)
|
|
6
|
+
return xmlEscape(String(value));
|
|
5
7
|
}
|
|
6
8
|
const URLSET_OPENING_TAG = '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
|
|
7
9
|
function buildUrlXml(url) {
|
|
@@ -162,13 +164,20 @@ function buildUrlXml(url) {
|
|
|
162
164
|
parts[partIndex++] = " </url>";
|
|
163
165
|
return parts.slice(0, partIndex).join("\n");
|
|
164
166
|
}
|
|
165
|
-
export function urlsToXml(urls, resolvers, { version, xsl, credits, minify }) {
|
|
167
|
+
export function urlsToXml(urls, resolvers, { version, xsl, credits, minify }, errorInfo) {
|
|
166
168
|
const estimatedSize = urls.length + 5;
|
|
167
169
|
const xmlParts = Array.from({ length: estimatedSize });
|
|
168
170
|
let partIndex = 0;
|
|
169
|
-
|
|
171
|
+
let xslHref = xsl ? resolvers.relativeBaseUrlResolver(xsl) : false;
|
|
172
|
+
if (xslHref && errorInfo && errorInfo.messages.length > 0) {
|
|
173
|
+
xslHref = withQuery(xslHref, {
|
|
174
|
+
errors: "true",
|
|
175
|
+
error_messages: errorInfo.messages,
|
|
176
|
+
error_urls: errorInfo.urls
|
|
177
|
+
});
|
|
178
|
+
}
|
|
170
179
|
if (xslHref) {
|
|
171
|
-
xmlParts[partIndex++] = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="${xslHref}"?>`;
|
|
180
|
+
xmlParts[partIndex++] = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="${escapeValueForXml(xslHref)}"?>`;
|
|
172
181
|
} else {
|
|
173
182
|
xmlParts[partIndex++] = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
174
183
|
}
|
|
@@ -41,7 +41,7 @@ async function buildSitemapXml(event, definition, resolvers, runtimeConfig) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
const sitemapUrls = await buildSitemapUrls(definition, resolvers, runtimeConfig, nitro);
|
|
44
|
+
const { urls: sitemapUrls, failedSources } = await buildSitemapUrls(definition, resolvers, runtimeConfig, nitro);
|
|
45
45
|
const routeRuleMatcher = createNitroRouteRuleMatcher();
|
|
46
46
|
const { autoI18n } = runtimeConfig;
|
|
47
47
|
let validCount = 0;
|
|
@@ -94,7 +94,11 @@ async function buildSitemapXml(event, definition, resolvers, runtimeConfig) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
const
|
|
97
|
+
const errorInfo = failedSources.length > 0 ? {
|
|
98
|
+
messages: failedSources.map((f) => f.error),
|
|
99
|
+
urls: failedSources.map((f) => f.url)
|
|
100
|
+
} : void 0;
|
|
101
|
+
const sitemap = urlsToXml(urls, resolvers, runtimeConfig, errorInfo);
|
|
98
102
|
const ctx = { sitemap, sitemapName, event };
|
|
99
103
|
await nitro.hooks.callHook("sitemap:output", ctx);
|
|
100
104
|
return ctx.sitemap;
|
|
@@ -120,7 +124,7 @@ const buildSitemapXmlCached = defineCachedFunction(
|
|
|
120
124
|
);
|
|
121
125
|
export async function createSitemap(event, definition, runtimeConfig) {
|
|
122
126
|
const resolvers = useNitroUrlResolvers(event);
|
|
123
|
-
const shouldCache = !import.meta.dev && runtimeConfig.cacheMaxAgeSeconds > 0;
|
|
127
|
+
const shouldCache = !import.meta.dev && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0;
|
|
124
128
|
const xml = shouldCache ? await buildSitemapXmlCached(event, definition, resolvers, runtimeConfig) : await buildSitemapXml(event, definition, resolvers, runtimeConfig);
|
|
125
129
|
setHeader(event, "Content-Type", "text/xml; charset=UTF-8");
|
|
126
130
|
if (runtimeConfig.cacheMaxAgeSeconds) {
|
|
@@ -75,7 +75,7 @@ export function normaliseEntry(_e, defaults, resolvers) {
|
|
|
75
75
|
delete e.lastmod;
|
|
76
76
|
e.loc = resolve(e.loc, resolvers);
|
|
77
77
|
if (e.alternatives) {
|
|
78
|
-
const alternatives = e.alternatives;
|
|
78
|
+
const alternatives = e.alternatives.map((a) => ({ ...a }));
|
|
79
79
|
for (let i = 0; i < alternatives.length; i++) {
|
|
80
80
|
const alt = alternatives[i];
|
|
81
81
|
if (typeof alt.href === "string") {
|
|
@@ -87,19 +87,20 @@ export function normaliseEntry(_e, defaults, resolvers) {
|
|
|
87
87
|
e.alternatives = mergeOnKey(alternatives, "hreflang");
|
|
88
88
|
}
|
|
89
89
|
if (e.images) {
|
|
90
|
-
const images = e.images;
|
|
90
|
+
const images = e.images.map((i) => ({ ...i }));
|
|
91
91
|
for (let i = 0; i < images.length; i++) {
|
|
92
92
|
images[i].loc = resolve(images[i].loc, resolvers);
|
|
93
93
|
}
|
|
94
94
|
e.images = mergeOnKey(images, "loc");
|
|
95
95
|
}
|
|
96
96
|
if (e.videos) {
|
|
97
|
-
const videos = e.videos;
|
|
97
|
+
const videos = e.videos.map((v) => ({ ...v }));
|
|
98
98
|
for (let i = 0; i < videos.length; i++) {
|
|
99
99
|
if (videos[i].content_loc) {
|
|
100
100
|
videos[i].content_loc = resolve(videos[i].content_loc, resolvers);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
e.videos = mergeOnKey(videos, "content_loc");
|
|
103
104
|
}
|
|
104
105
|
return e;
|
|
105
106
|
}
|
|
@@ -2,35 +2,70 @@ import { getRequestHost } from "h3";
|
|
|
2
2
|
import { defu } from "defu";
|
|
3
3
|
import { parseURL } from "ufo";
|
|
4
4
|
import { extractSitemapXML } from "../utils/extractSitemapXML.js";
|
|
5
|
+
import { logger } from "../../../utils-pure.js";
|
|
6
|
+
async function tryFetchWithFallback(url, options, event) {
|
|
7
|
+
const isExternalUrl = !url.startsWith("/");
|
|
8
|
+
if (isExternalUrl) {
|
|
9
|
+
const strategies = [
|
|
10
|
+
// Strategy 1: Use globalThis.$fetch (original approach)
|
|
11
|
+
() => globalThis.$fetch(url, options),
|
|
12
|
+
// Strategy 2: If event is available, try using event context even for external URLs
|
|
13
|
+
event ? () => event.$fetch(url, options) : null,
|
|
14
|
+
// Strategy 3: Use native fetch as last resort
|
|
15
|
+
() => $fetch(url, options)
|
|
16
|
+
].filter(Boolean);
|
|
17
|
+
let lastError = null;
|
|
18
|
+
for (const strategy of strategies) {
|
|
19
|
+
try {
|
|
20
|
+
return await strategy();
|
|
21
|
+
} catch (error) {
|
|
22
|
+
lastError = error;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
throw lastError;
|
|
27
|
+
}
|
|
28
|
+
const fetchContainer = url.startsWith("/") && event ? event : globalThis;
|
|
29
|
+
return await fetchContainer.$fetch(url, options);
|
|
30
|
+
}
|
|
5
31
|
export async function fetchDataSource(input, event) {
|
|
6
32
|
const context = typeof input.context === "string" ? { name: input.context } : input.context || { name: "fetch" };
|
|
7
|
-
context.tips = context.tips || [];
|
|
8
33
|
const url = typeof input.fetch === "string" ? input.fetch : input.fetch[0];
|
|
9
34
|
const options = typeof input.fetch === "string" ? {} : input.fetch[1];
|
|
10
35
|
const start = Date.now();
|
|
11
|
-
const
|
|
36
|
+
const isExternalUrl = !url.startsWith("/");
|
|
37
|
+
const timeout = isExternalUrl ? 1e4 : options.timeout || 5e3;
|
|
12
38
|
const timeoutController = new AbortController();
|
|
13
39
|
const abortRequestTimeout = setTimeout(() => timeoutController.abort(), timeout);
|
|
14
|
-
let isMaybeErrorResponse = false;
|
|
15
|
-
const isXmlRequest = parseURL(url).pathname.endsWith(".xml");
|
|
16
|
-
const fetchContainer = url.startsWith("/") && event ? event : globalThis;
|
|
17
40
|
try {
|
|
18
|
-
|
|
41
|
+
let isMaybeErrorResponse = false;
|
|
42
|
+
const isXmlRequest = parseURL(url).pathname.endsWith(".xml");
|
|
43
|
+
const mergedHeaders = defu(
|
|
44
|
+
options?.headers,
|
|
45
|
+
{
|
|
46
|
+
Accept: isXmlRequest ? "text/xml" : "application/json"
|
|
47
|
+
},
|
|
48
|
+
event ? { host: getRequestHost(event, { xForwardedHost: true }) } : {}
|
|
49
|
+
);
|
|
50
|
+
const fetchOptions = {
|
|
19
51
|
...options,
|
|
20
52
|
responseType: isXmlRequest ? "text" : "json",
|
|
21
53
|
signal: timeoutController.signal,
|
|
22
|
-
headers:
|
|
23
|
-
|
|
24
|
-
|
|
54
|
+
headers: mergedHeaders,
|
|
55
|
+
// Use ofetch's built-in retry for external sources
|
|
56
|
+
...isExternalUrl && {
|
|
57
|
+
retry: 2,
|
|
58
|
+
retryDelay: 200
|
|
59
|
+
},
|
|
25
60
|
// @ts-expect-error untyped
|
|
26
61
|
onResponse({ response }) {
|
|
27
62
|
if (typeof response._data === "string" && response._data.startsWith("<!DOCTYPE html>"))
|
|
28
63
|
isMaybeErrorResponse = true;
|
|
29
64
|
}
|
|
30
|
-
}
|
|
65
|
+
};
|
|
66
|
+
const res = await tryFetchWithFallback(url, fetchOptions, event);
|
|
31
67
|
const timeTakenMs = Date.now() - start;
|
|
32
68
|
if (isMaybeErrorResponse) {
|
|
33
|
-
context.tips.push("This is usually because the URL isn't correct or is throwing an error. Please check the URL");
|
|
34
69
|
return {
|
|
35
70
|
...input,
|
|
36
71
|
context,
|
|
@@ -53,16 +88,26 @@ export async function fetchDataSource(input, event) {
|
|
|
53
88
|
};
|
|
54
89
|
} catch (_err) {
|
|
55
90
|
const error = _err;
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
91
|
+
if (isExternalUrl) {
|
|
92
|
+
const errorInfo = {
|
|
93
|
+
url,
|
|
94
|
+
timeout,
|
|
95
|
+
error: error.message,
|
|
96
|
+
statusCode: error.response?.status,
|
|
97
|
+
statusText: error.response?.statusText,
|
|
98
|
+
method: options?.method || "GET"
|
|
99
|
+
};
|
|
100
|
+
logger.error("Failed to fetch external source.", errorInfo);
|
|
101
|
+
} else {
|
|
102
|
+
logger.error("Failed to fetch source.", { url, error: error.message });
|
|
103
|
+
}
|
|
61
104
|
return {
|
|
62
105
|
...input,
|
|
63
106
|
context,
|
|
64
107
|
urls: [],
|
|
65
|
-
error: error.message
|
|
108
|
+
error: error.message,
|
|
109
|
+
_isFailure: true
|
|
110
|
+
// Mark as failure to prevent caching
|
|
66
111
|
};
|
|
67
112
|
} finally {
|
|
68
113
|
if (abortRequestTimeout) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
2
|
import { normalizeRuntimeFilters } from "../utils-pure.js";
|
|
3
3
|
export * from "../utils-pure.js";
|
|
4
|
+
export function xmlEscape(str) {
|
|
5
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6
|
+
}
|
|
4
7
|
export function useSitemapRuntimeConfig(e) {
|
|
5
8
|
const clone = JSON.parse(JSON.stringify(useRuntimeConfig(e).sitemap));
|
|
6
9
|
for (const k in clone.sitemaps) {
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -178,6 +178,7 @@ export interface SitemapSourceResolved extends Omit<SitemapSourceBase, 'urls'> {
|
|
|
178
178
|
urls: SitemapUrlInput[];
|
|
179
179
|
error?: any;
|
|
180
180
|
timeTakenMs?: number;
|
|
181
|
+
_isFailure?: boolean;
|
|
181
182
|
}
|
|
182
183
|
export type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rules' | '@nuxtjs/i18n:pages' | '@nuxt/content:document-driven';
|
|
183
184
|
export type SitemapSourceInput = string | [string, FetchOptions] | SitemapSourceBase | SitemapSourceResolved;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtjs/sitemap",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.4.0",
|
|
5
5
|
"description": "Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@nuxt/devtools-kit": "^2.
|
|
48
|
-
"@nuxt/kit": "^3.17.
|
|
47
|
+
"@nuxt/devtools-kit": "^2.5.0",
|
|
48
|
+
"@nuxt/kit": "^3.17.5",
|
|
49
49
|
"chalk": "^5.4.1",
|
|
50
50
|
"defu": "^6.1.4",
|
|
51
51
|
"h3-compression": "^0.3.2",
|
|
@@ -59,23 +59,24 @@
|
|
|
59
59
|
"ufo": "^1.6.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@arethetypeswrong/cli": "^0.18.
|
|
63
|
-
"@nuxt/content": "^3.
|
|
64
|
-
"@nuxt/eslint-config": "^1.4.
|
|
62
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
63
|
+
"@nuxt/content": "^3.6.0",
|
|
64
|
+
"@nuxt/eslint-config": "^1.4.1",
|
|
65
65
|
"@nuxt/module-builder": "^1.0.1",
|
|
66
|
-
"@nuxt/test-utils": "^3.19.
|
|
67
|
-
"@nuxt/ui": "^3.1.
|
|
68
|
-
"@nuxtjs/i18n": "^9.5.
|
|
66
|
+
"@nuxt/test-utils": "^3.19.1",
|
|
67
|
+
"@nuxt/ui": "^3.1.3",
|
|
68
|
+
"@nuxtjs/i18n": "^9.5.5",
|
|
69
69
|
"@nuxtjs/robots": "^5.2.10",
|
|
70
|
+
"better-sqlite3": "^11.10.0",
|
|
70
71
|
"bumpp": "^10.1.1",
|
|
71
|
-
"eslint": "^9.
|
|
72
|
-
"eslint-plugin-n": "^17.
|
|
73
|
-
"execa": "^9.
|
|
74
|
-
"happy-dom": "^
|
|
75
|
-
"nuxt": "^3.17.
|
|
72
|
+
"eslint": "^9.29.0",
|
|
73
|
+
"eslint-plugin-n": "^17.20.0",
|
|
74
|
+
"execa": "^9.6.0",
|
|
75
|
+
"happy-dom": "^18.0.1",
|
|
76
|
+
"nuxt": "^3.17.5",
|
|
76
77
|
"nuxt-i18n-micro": "^1.87.0",
|
|
77
78
|
"typescript": "^5.8.3",
|
|
78
|
-
"vitest": "^3.
|
|
79
|
+
"vitest": "^3.2.3",
|
|
79
80
|
"vue-tsc": "^2.2.10"
|
|
80
81
|
},
|
|
81
82
|
"scripts": {
|