@saasmakers/ui 1.4.41 → 1.4.42

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/nuxt.config.ts CHANGED
@@ -4,7 +4,6 @@ import { defineNuxtConfig } from 'nuxt/config'
4
4
  import uno from './uno.config.js'
5
5
 
6
6
  const currentDir = path.dirname(fileURLToPath(import.meta.url))
7
- const nuxtSeoPatchesDir = path.join(currentDir, 'server/patches/nuxt-seo')
8
7
 
9
8
  export default defineNuxtConfig({
10
9
  modules: [
@@ -118,17 +117,4 @@ export default defineNuxtConfig({
118
117
  shortcuts: uno.shortcuts,
119
118
  theme: uno.theme,
120
119
  },
121
-
122
- // h3 v2 (Nuxt 4.4+) compatibility shims for @nuxtjs/robots and @nuxtjs/sitemap
123
- nitro: {
124
- alias: {
125
- '@nuxtjs/robots/dist/runtime/server/middleware/injectContext.js': path.join(nuxtSeoPatchesDir, 'robots-injectContext.js'),
126
- '@nuxtjs/sitemap/dist/runtime/server/routes/sitemap.xsl.js': path.join(nuxtSeoPatchesDir, 'routes/sitemap.xsl.js'),
127
- '@nuxtjs/sitemap/dist/runtime/server/sitemap/builder/sitemap-index.js': path.join(nuxtSeoPatchesDir, 'sitemap/builder/sitemap-index.js'),
128
- '@nuxtjs/sitemap/dist/runtime/server/sitemap/builder/sitemap.js': path.join(nuxtSeoPatchesDir, 'sitemap/builder/sitemap.js'),
129
- '@nuxtjs/sitemap/dist/runtime/server/sitemap/event-handlers.js': path.join(nuxtSeoPatchesDir, 'sitemap/event-handlers.js'),
130
- '@nuxtjs/sitemap/dist/runtime/server/sitemap/nitro.js': path.join(nuxtSeoPatchesDir, 'sitemap/nitro.js'),
131
- '@nuxtjs/sitemap/dist/runtime/server/utils/h3-compat.js': path.join(nuxtSeoPatchesDir, 'utils/h3-compat.js'),
132
- },
133
- },
134
120
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
- "version": "1.4.41",
3
+ "version": "1.4.42",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",
@@ -18,7 +18,6 @@
18
18
  "files": [
19
19
  "app",
20
20
  "public",
21
- "server",
22
21
  "uno.config.ts"
23
22
  ],
24
23
  "dependencies": {
@@ -47,16 +46,16 @@
47
46
  "snarkdown": "2.0.0",
48
47
  "unocss": "66.7.0",
49
48
  "vue": "3.5.35",
50
- "vue-router": "5.1.0"
49
+ "vue-router": "4.6.4"
51
50
  },
52
51
  "devDependencies": {
53
- "nuxt": "4.4.8",
52
+ "nuxt": "4.3.1",
54
53
  "typescript": "5.9.3",
55
54
  "@saasmakers/shared": "0.2.5"
56
55
  },
57
56
  "peerDependencies": {
58
57
  "@saasmakers/shared": "^0.2.0",
59
- "nuxt": "^4.4.0"
58
+ "nuxt": "^4.3.1"
60
59
  },
61
60
  "publishConfig": {
62
61
  "access": "public"
@@ -1,35 +0,0 @@
1
- import { defineEventHandler, getQuery, setHeader } from "h3";
2
- import { parseQuery } from "ufo";
3
- import { getPathRobotConfig } from "../composables/getPathRobotConfig.js";
4
- import { useRuntimeConfigNuxtRobots } from "../composables/useRuntimeConfigNuxtRobots.js";
5
-
6
- function safeGetQuery(event) {
7
- try {
8
- return getQuery(event);
9
- }
10
- catch {
11
- const path = event.path || event.node?.req?.url || "/";
12
- const searchIndex = path.indexOf("?");
13
- const search = searchIndex === -1 ? "" : path.slice(searchIndex);
14
- return parseQuery(search);
15
- }
16
- }
17
-
18
- export default defineEventHandler(async (e) => {
19
- if (e.path === "/robots.txt" || e.path.startsWith("/__") || e.path.startsWith("/api") || e.path.startsWith("/_nuxt"))
20
- return;
21
- const nuxtRobotsConfig = useRuntimeConfigNuxtRobots(e);
22
- if (nuxtRobotsConfig) {
23
- const { header } = nuxtRobotsConfig;
24
- const robotConfig = getPathRobotConfig(e, { skipSiteIndexable: Boolean(safeGetQuery(e)?.mockProductionEnv) });
25
- if (header) {
26
- setHeader(e, "X-Robots-Tag", robotConfig.rule);
27
- }
28
- e.context.robots = robotConfig;
29
- if (import.meta.dev) {
30
- const productionRobotConfig = getPathRobotConfig(e, { skipSiteIndexable: true });
31
- setHeader(e, "X-Robots-Production", productionRobotConfig.rule);
32
- e.context.robotsProduction = productionRobotConfig;
33
- }
34
- }
35
- });
@@ -1,452 +0,0 @@
1
- import { defineEventHandler } from "h3";
2
- import { safeGetHeader, safeGetQuery, safeSetHeader } from "../utils/h3-compat.js";
3
- import { getQuery, parseURL, withQuery } from "ufo";
4
- import { getSiteConfig } from "#site-config/server/composables";
5
- import { createSitePathResolver } from "#site-config/server/composables/utils";
6
- import { useSitemapRuntimeConfig, xmlEscape } from "../utils.js";
7
- export default defineEventHandler(async (e) => {
8
- const fixPath = createSitePathResolver(e, { absolute: false, withBase: true });
9
- const { sitemapName: fallbackSitemapName, cacheMaxAgeSeconds, version, xslColumns, xslTips } = useSitemapRuntimeConfig();
10
- safeSetHeader(e, "Content-Type", "application/xslt+xml");
11
- if (cacheMaxAgeSeconds)
12
- safeSetHeader(e, "Cache-Control", `public, max-age=${cacheMaxAgeSeconds}, must-revalidate`);
13
- else
14
- safeSetHeader(e, "Cache-Control", `no-cache, no-store`);
15
- const { name: siteName, url: siteUrl } = getSiteConfig(e);
16
- const referrer = safeGetHeader(e, "Referer") || "/";
17
- const referrerPath = parseURL(referrer).pathname;
18
- const isNotIndexButHasIndex = referrerPath !== "/sitemap.xml" && referrerPath !== "/sitemap_index.xml" && referrerPath.endsWith(".xml");
19
- const sitemapName = parseURL(referrer).pathname.split("/").pop()?.split("-sitemap")[0] || fallbackSitemapName;
20
- const title = `${siteName}${sitemapName !== "sitemap.xml" ? ` - ${sitemapName === "sitemap_index.xml" ? "index" : sitemapName}` : ""}`.replace(/&/g, "&");
21
- const isIndexPage = referrerPath === "/sitemap.xml" || referrerPath === "/sitemap_index.xml";
22
- const canonicalQuery = getQuery(referrer).canonical;
23
- const isShowingCanonical = typeof canonicalQuery !== "undefined" && canonicalQuery !== "false";
24
- const debugUrl = xmlEscape(withQuery("/__sitemap__/debug.json", { sitemap: sitemapName }));
25
- const devUrl = xmlEscape(referrerPath);
26
- const prodUrl = xmlEscape(withQuery(referrerPath, { canonical: "" }));
27
- const fetchErrors = [];
28
- const xslQuery = safeGetQuery(e);
29
- if (xslQuery.error_messages) {
30
- const errorMessages = xslQuery.error_messages;
31
- const errorUrls = xslQuery.error_urls;
32
- if (errorMessages) {
33
- const messages = Array.isArray(errorMessages) ? errorMessages : [errorMessages];
34
- const urls = Array.isArray(errorUrls) ? errorUrls : errorUrls ? [errorUrls] : [];
35
- messages.forEach((msg, i) => {
36
- const errorParts = [xmlEscape(msg)];
37
- if (urls[i])
38
- errorParts.push(xmlEscape(urls[i]));
39
- fetchErrors.push(`<span class="error-item">${errorParts.join(" \u2014 ")}</span>`);
40
- });
41
- }
42
- }
43
- const hasRuntimeErrors = fetchErrors.length > 0;
44
- const showDevTools = import.meta.dev && xslTips !== false;
45
- const hints = [
46
- `This is an XSL sitemap (CSS for XML). Disable with <code>xsl: false</code>`,
47
- `View the raw XML by adding <code>?canonical</code> to the URL`,
48
- `Check <code>/__sitemap__/debug.json</code> for full sitemap diagnostics`
49
- ];
50
- const hint = hints[Math.floor(Math.random() * hints.length)];
51
- let columns = [...xslColumns];
52
- if (!columns.length) {
53
- columns = [
54
- { label: "URL", width: "50%" },
55
- { label: "Images", width: "25%", select: "count(image:image)" },
56
- { label: "Last Updated", width: "25%", select: "concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))" }
57
- ];
58
- }
59
- return `<?xml version="1.0" encoding="UTF-8"?>
60
- <xsl:stylesheet version="2.0"
61
- xmlns:html="http://www.w3.org/TR/REC-html40"
62
- xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
63
- xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
64
- xmlns:xhtml="http://www.w3.org/1999/xhtml"
65
- xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
66
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
67
- <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
68
- <xsl:template match="/">
69
- <html xmlns="http://www.w3.org/1999/xhtml">
70
- <head>
71
- <title>XML Sitemap</title>
72
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
73
- <style type="text/css">
74
- :root {
75
- --accent: #00dc82;
76
- --accent-hover: #00b86b;
77
- --bg: #0a0a0a;
78
- --bg-elevated: #141414;
79
- --bg-subtle: #1a1a1a;
80
- --border: #262626;
81
- --border-subtle: #1f1f1f;
82
- --text: #e5e5e5;
83
- --text-muted: #737373;
84
- --text-faint: #525252;
85
- --error: #ef4444;
86
- --error-bg: rgba(239,68,68,0.1);
87
- --warning: #f59e0b;
88
- }
89
- * { box-sizing: border-box; }
90
- body {
91
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
92
- font-size: 13px;
93
- color: var(--text);
94
- background: var(--bg);
95
- margin: 0;
96
- padding: 0;
97
- line-height: 1.6;
98
- -webkit-font-smoothing: antialiased;
99
- }
100
- a { color: inherit; transition: color 0.15s; }
101
- a:hover { color: var(--accent); }
102
-
103
- /* Debug bar (dev only) */
104
- .debug-bar {
105
- position: fixed;
106
- bottom: 0.75rem;
107
- left: 50%;
108
- transform: translateX(-50%);
109
- width: 80%;
110
- background: var(--bg-elevated);
111
- border: 1px solid var(--border);
112
- border-radius: 10px;
113
- padding: 0 1rem;
114
- height: 2.5rem;
115
- display: flex;
116
- align-items: center;
117
- gap: 0.75rem;
118
- z-index: 100;
119
- font-size: 11px;
120
- }
121
- .debug-bar-brand {
122
- display: flex;
123
- align-items: center;
124
- gap: 0.5rem;
125
- color: var(--text-muted);
126
- text-decoration: none;
127
- }
128
- .debug-bar-brand:hover { color: var(--text); }
129
- .debug-bar-brand svg { flex-shrink: 0; }
130
- .debug-bar-hint {
131
- color: var(--text-faint);
132
- margin-right: auto;
133
- white-space: nowrap;
134
- overflow: hidden;
135
- text-overflow: ellipsis;
136
- }
137
- .debug-bar-hint code {
138
- background: var(--bg-subtle);
139
- padding: 0.1rem 0.3rem;
140
- border-radius: 3px;
141
- font-size: 10px;
142
- }
143
- .mode-badge {
144
- font-size: 9px;
145
- font-weight: 600;
146
- text-transform: uppercase;
147
- letter-spacing: 0.04em;
148
- padding: 0.2rem 0.4rem;
149
- border-radius: 3px;
150
- }
151
- .mode-dev { background: rgba(245,158,11,0.15); color: var(--warning); }
152
- .mode-prod { background: rgba(0,220,130,0.12); color: var(--accent); }
153
- .mode-toggle {
154
- display: inline-flex;
155
- border-radius: 4px;
156
- overflow: hidden;
157
- background: var(--bg-subtle);
158
- padding: 2px;
159
- gap: 1px;
160
- }
161
- .mode-toggle a {
162
- padding: 0.2rem 0.4rem;
163
- font-size: 9px;
164
- font-weight: 500;
165
- text-decoration: none;
166
- color: var(--text-muted);
167
- border-radius: 2px;
168
- transition: all 0.15s;
169
- }
170
- .mode-toggle a:hover { color: var(--text); }
171
- .mode-toggle a.active {
172
- background: var(--accent);
173
- color: #0a0a0a;
174
- }
175
- .btn {
176
- display: inline-flex;
177
- align-items: center;
178
- gap: 0.25rem;
179
- padding: 0.25rem 0.5rem;
180
- border-radius: 4px;
181
- text-decoration: none;
182
- font-size: 10px;
183
- font-weight: 500;
184
- transition: all 0.15s;
185
- }
186
- .btn-primary {
187
- background: var(--accent);
188
- color: #0a0a0a;
189
- }
190
- .btn-primary:hover { background: var(--accent-hover); color: #0a0a0a; }
191
- .btn svg { width: 12px; height: 12px; }
192
-
193
- /* Error banner */
194
- .error-banner {
195
- background: var(--error-bg);
196
- border-bottom: 1px solid rgba(239,68,68,0.2);
197
- padding: 0.75rem 1.5rem;
198
- color: #fca5a5;
199
- font-size: 12px;
200
- }
201
- .error-banner strong { color: var(--error); }
202
- .error-item { display: block; margin-top: 0.375rem; color: #fca5a5; }
203
- .error-debug-link {
204
- display: inline-flex;
205
- align-items: center;
206
- gap: 0.25rem;
207
- margin-top: 0.625rem;
208
- padding: 0.25rem 0.5rem;
209
- background: var(--error);
210
- color: #fff;
211
- border-radius: 4px;
212
- font-size: 11px;
213
- font-weight: 500;
214
- text-decoration: none;
215
- transition: background 0.15s;
216
- }
217
- .error-debug-link:hover { background: #dc2626; color: #fff; }
218
-
219
- /* Main content */
220
- .container {
221
- max-width: 1200px;
222
- margin: 0 auto;
223
- padding: 1.5rem;
224
- }
225
- .header {
226
- margin-bottom: 1.25rem;
227
- }
228
- .header h1 {
229
- font-size: 1rem;
230
- font-weight: 600;
231
- margin: 0 0 0.25rem 0;
232
- color: var(--text);
233
- }
234
- .header-meta {
235
- color: var(--text-muted);
236
- font-size: 12px;
237
- }
238
- .header-meta a {
239
- color: var(--text-muted);
240
- text-decoration: underline;
241
- text-decoration-color: var(--border);
242
- text-underline-offset: 2px;
243
- }
244
- .header-meta a:hover { color: var(--accent); text-decoration-color: var(--accent); }
245
-
246
- /* Table */
247
- .table-wrap {
248
- border: 1px solid var(--border);
249
- border-radius: 8px;
250
- overflow: hidden;
251
- background: var(--bg-elevated);
252
- }
253
- table {
254
- width: 100%;
255
- border-collapse: collapse;
256
- }
257
- th {
258
- text-align: left;
259
- padding: 0.625rem 1rem;
260
- font-size: 10px;
261
- font-weight: 600;
262
- text-transform: uppercase;
263
- letter-spacing: 0.05em;
264
- color: var(--text-muted);
265
- background: var(--bg-subtle);
266
- border-bottom: 1px solid var(--border);
267
- }
268
- td {
269
- padding: 0.5rem 1rem;
270
- border-bottom: 1px solid var(--border-subtle);
271
- font-size: 12px;
272
- color: var(--text);
273
- }
274
- tr:last-child td { border-bottom: none; }
275
- tr:hover td { background: rgba(255,255,255,0.02); }
276
- td a {
277
- text-decoration: none;
278
- word-break: break-all;
279
- color: var(--text);
280
- }
281
- td a:hover { color: var(--accent); }
282
- .inline-warning {
283
- font-size: 11px;
284
- color: var(--warning);
285
- margin-top: 0.25rem;
286
- line-height: 1.4;
287
- }
288
- .inline-warning::before {
289
- content: "\u26A0 ";
290
- }
291
- .count {
292
- display: inline-block;
293
- min-width: 1.25rem;
294
- padding: 0.125rem 0.375rem;
295
- background: var(--bg-subtle);
296
- border-radius: 4px;
297
- text-align: center;
298
- font-size: 11px;
299
- color: var(--text-muted);
300
- font-variant-numeric: tabular-nums;
301
- }
302
- .count:empty::before { content: "0"; }
303
-
304
- /* Light mode */
305
- @media (prefers-color-scheme: light) {
306
- :root {
307
- --accent: #00a963;
308
- --accent-hover: #008f54;
309
- --bg: #ffffff;
310
- --bg-elevated: #f5f5f5;
311
- --bg-subtle: #ebebeb;
312
- --border: #d4d4d4;
313
- --border-subtle: #e5e5e5;
314
- --text: #171717;
315
- --text-muted: #525252;
316
- --text-faint: #737373;
317
- --error: #dc2626;
318
- --error-bg: rgba(220,38,38,0.08);
319
- --warning: #b45309;
320
- }
321
- tr:hover td { background: rgba(0,0,0,0.02); }
322
- .btn-primary { color: #fff; }
323
- .btn-primary:hover { color: #fff; }
324
- .mode-toggle a.active { color: #fff; }
325
- .error-banner { color: #991b1b; }
326
- .error-item { color: #b91c1c; }
327
- .error-debug-link { color: #fff; }
328
- .error-debug-link:hover { color: #fff; }
329
- }
330
-
331
- .debug-bar-version {
332
- color: var(--text-faint);
333
- font-size: 10px;
334
- }
335
-
336
- /* Responsive */
337
- @media (max-width: 640px) {
338
- .debug-bar { padding: 0 0.75rem; gap: 0.5rem; width: 95%; }
339
- .debug-bar-brand span { display: none; }
340
- .debug-bar-hint { display: none; }
341
- .debug-bar-version { display: none; }
342
- .mode-badge { display: none; }
343
- .container { padding: 1rem; }
344
- th, td { padding: 0.5rem 0.75rem; }
345
- }
346
- ${showDevTools ? "body { padding-bottom: 3.5rem; }" : ""}
347
- </style>
348
- </head>
349
- <body>
350
- ${hasRuntimeErrors ? `<div class="error-banner">
351
- <strong>Sitemap Generation Errors</strong>
352
- ${fetchErrors.join("")}
353
- <a href="${debugUrl}" target="_blank" class="error-debug-link">View Debug Info \u2192</a>
354
- </div>` : ""}
355
- <div class="container">
356
- <div class="header">
357
- <h1>${xmlEscape(title)}</h1>
358
- <div class="header-meta">
359
- ${isNotIndexButHasIndex ? `Part of <a href="${xmlEscape(fixPath("/sitemap_index.xml"))}">${xmlEscape(fixPath("/sitemap_index.xml"))}</a> \xB7 ` : ""}
360
- <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &gt; 0">
361
- <xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> sitemaps
362
- </xsl:if>
363
- <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &lt; 1">
364
- <xsl:value-of select="count(sitemap:urlset/sitemap:url)"/> URLs
365
- </xsl:if>
366
- </div>
367
- </div>
368
- <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &gt; 0">
369
- <div class="table-wrap">
370
- <table>
371
- <thead>
372
- <tr>
373
- <th style="width:70%">Sitemap</th>
374
- <th style="width:30%">Last Modified</th>
375
- </tr>
376
- </thead>
377
- <tbody>
378
- <xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
379
- <xsl:variable name="sitemapURL">
380
- <xsl:value-of select="sitemap:loc"/>
381
- </xsl:variable>
382
- <tr>
383
- <td>
384
- <a href="{$sitemapURL}">
385
- <xsl:value-of select="sitemap:loc"/>
386
- </a>
387
- </td>
388
- <td>
389
- <xsl:value-of
390
- select="concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))"/>
391
- </td>
392
- </tr>
393
- </xsl:for-each>
394
- </tbody>
395
- </table>
396
- </div>
397
- </xsl:if>
398
- <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) &lt; 1">
399
- <div class="table-wrap">
400
- <table>
401
- <thead>
402
- <tr>
403
- ${columns.map((c) => `<th style="width:${c.width}">${c.label}</th>`).join("\n")}
404
- </tr>
405
- </thead>
406
- <tbody>
407
- <xsl:for-each select="sitemap:urlset/sitemap:url">
408
- <tr>
409
- <td>
410
- <xsl:variable name="itemURL">
411
- <xsl:value-of select="sitemap:loc"/>
412
- </xsl:variable>
413
- <a href="{$itemURL}">
414
- <xsl:value-of select="sitemap:loc"/>
415
- </a>
416
- ${showDevTools ? `<xsl:for-each select="comment()[starts-with(normalize-space(.), 'WARN:')]">
417
- <div class="inline-warning">
418
- <xsl:value-of select="substring-after(normalize-space(.), 'WARN:')"/>
419
- </div>
420
- </xsl:for-each>` : ""}
421
- </td>
422
- ${columns.filter((c) => c.label !== "URL").map((c) => `<td><span class="count"><xsl:value-of select="${c.select}"/></span></td>`).join("\n")}
423
- </tr>
424
- </xsl:for-each>
425
- </tbody>
426
- </table>
427
- </div>
428
- </xsl:if>
429
- </div>
430
- ${showDevTools ? `<div class="debug-bar">
431
- <a href="${xmlEscape(fixPath("/sitemap_index.xml"))}" class="debug-bar-brand">
432
- <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 32 32"><path fill="#00dc82" d="M4 26h4v4H4zm10 0h4v4h-4zm10 0h4v4h-4zm1-10h-8v-2h-2v2H7a2 2 0 0 0-2 2v6h2v-6h8v6h2v-6h8v6h2v-6a2 2 0 0 0-2-2zM9 2v10h14V2zm2 2h2v6h-2zm10 6h-6V4h6z"/></svg>
433
- <span>Sitemap Debug Bar</span>
434
- </a>
435
- <span class="debug-bar-version">v${version} \xB7 ${xmlEscape(siteUrl)}</span>
436
- <span class="debug-bar-hint">Hint: ${hint}</span>
437
- ${isIndexPage ? `<span class="mode-badge ${isShowingCanonical ? "mode-prod" : "mode-dev"}">${isShowingCanonical ? "Prod" : "Dev"}</span>
438
- <div class="mode-toggle">
439
- <a href="${isShowingCanonical ? devUrl : "#"}" class="${!isShowingCanonical ? "active" : ""}">Dev</a>
440
- <a href="${!isShowingCanonical ? prodUrl : "#"}" class="${isShowingCanonical ? "active" : ""}">Prod</a>
441
- </div>` : ""}
442
- <a href="${debugUrl}" target="_blank" class="btn btn-primary">
443
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
444
- Debug
445
- </a>
446
- </div>` : ""}
447
- </body>
448
- </html>
449
- </xsl:template>
450
- </xsl:stylesheet>
451
- `;
452
- });
@@ -1,145 +0,0 @@
1
- import { safeGetHeader } from "../../utils/h3-compat.js";
2
- import { defineCachedFunction } from "nitropack/runtime";
3
- import { joinURL, withQuery } from "ufo";
4
- import staticConfig from "#sitemap-virtual/static-config.mjs";
5
- import { normaliseDate } from "../urlset/normalise.js";
6
- import { getResolvedSitemapUrls } from "./sitemap.js";
7
- import { escapeValueForXml } from "./xml.js";
8
- const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
9
- const buildSitemapIndexCached = defineCachedFunction(
10
- async (event, resolvers, runtimeConfig, nitro) => {
11
- return buildSitemapIndexInternal(resolvers, runtimeConfig, nitro);
12
- },
13
- {
14
- name: "sitemap:index",
15
- group: "sitemap",
16
- maxAge: SERVER_CACHE_MAX_AGE,
17
- base: "sitemap",
18
- // Use the sitemap storage
19
- getKey: (event) => {
20
- const host = safeGetHeader(event, "host") || safeGetHeader(event, "x-forwarded-host") || "";
21
- const proto = safeGetHeader(event, "x-forwarded-proto") || "https";
22
- return `sitemap-index-${proto}-${host}`;
23
- },
24
- swr: true
25
- // Enable stale-while-revalidate
26
- }
27
- );
28
- async function buildSitemapIndexInternal(resolvers, runtimeConfig, nitro) {
29
- const {
30
- sitemaps,
31
- autoLastmod,
32
- defaultSitemapsChunkSize,
33
- sitemapsPathPrefix
34
- } = runtimeConfig;
35
- if (!sitemaps)
36
- throw new Error("Attempting to build a sitemap index without required `sitemaps` configuration.");
37
- const nonChunkedNames = [];
38
- const allFailedSources = [];
39
- for (const sitemapName in sitemaps) {
40
- if (sitemapName === "index" || sitemapName === "chunks")
41
- continue;
42
- const sitemapConfig = sitemaps[sitemapName];
43
- if (sitemapConfig.chunks || sitemapConfig._isChunking) {
44
- sitemapConfig._isChunking = true;
45
- sitemapConfig._chunkSize = sitemapConfig.chunkSize || (typeof sitemapConfig.chunks === "number" ? sitemapConfig.chunks : defaultSitemapsChunkSize || 1e3);
46
- } else {
47
- nonChunkedNames.push(sitemapName);
48
- }
49
- }
50
- const indexLastmod = autoLastmod ? normaliseDate(/* @__PURE__ */ new Date()) : void 0;
51
- const entries = [];
52
- if (typeof sitemaps.chunks !== "undefined") {
53
- const sitemap = sitemaps.chunks;
54
- const resolved = await getResolvedSitemapUrls(sitemap, "sitemap", true, resolvers, runtimeConfig, nitro);
55
- allFailedSources.push(...resolved.failedSources);
56
- const chunkCount = Math.ceil(resolved.urls.length / defaultSitemapsChunkSize);
57
- for (let i = 0; i < chunkCount; i++) {
58
- const entry = {
59
- _sitemapName: String(i),
60
- sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || "", `/${i}.xml`))
61
- };
62
- if (indexLastmod)
63
- entry.lastmod = indexLastmod;
64
- entries.push(entry);
65
- }
66
- }
67
- for (const name of nonChunkedNames) {
68
- const entry = {
69
- _sitemapName: name,
70
- sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || "", `/${name}.xml`))
71
- };
72
- if (indexLastmod)
73
- entry.lastmod = indexLastmod;
74
- entries.push(entry);
75
- }
76
- for (const sitemapName in sitemaps) {
77
- const sitemapConfig = sitemaps[sitemapName];
78
- if (sitemapName !== "index" && sitemapConfig._isChunking) {
79
- const chunkSize = sitemapConfig._chunkSize || defaultSitemapsChunkSize || 1e3;
80
- let chunkCount;
81
- if (typeof sitemapConfig.chunkCount === "number" && sitemapConfig.chunkCount > 0) {
82
- chunkCount = sitemapConfig.chunkCount;
83
- } else {
84
- const resolved = await getResolvedSitemapUrls(sitemapConfig, sitemapName, true, resolvers, runtimeConfig, nitro);
85
- allFailedSources.push(...resolved.failedSources);
86
- chunkCount = Math.ceil(resolved.urls.length / chunkSize);
87
- }
88
- sitemapConfig._chunkCount = chunkCount;
89
- for (let i = 0; i < chunkCount; i++) {
90
- const chunkName = `${sitemapName}-${i}`;
91
- const entry = {
92
- _sitemapName: chunkName,
93
- sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || "", `/${chunkName}.xml`))
94
- };
95
- if (indexLastmod)
96
- entry.lastmod = indexLastmod;
97
- entries.push(entry);
98
- }
99
- }
100
- }
101
- if (sitemaps.index) {
102
- entries.push(...sitemaps.index.sitemaps.map((entry) => {
103
- return typeof entry === "string" ? { sitemap: entry } : entry;
104
- }));
105
- }
106
- return { entries, failedSources: allFailedSources };
107
- }
108
- export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, minify }, errorInfo) {
109
- const sitemapXml = sitemaps.map((e) => [
110
- " <sitemap>",
111
- ` <loc>${escapeValueForXml(e.sitemap)}</loc>`,
112
- // lastmod is optional
113
- e.lastmod ? ` <lastmod>${escapeValueForXml(e.lastmod)}</lastmod>` : false,
114
- " </sitemap>"
115
- ].filter(Boolean).join("\n")).join("\n");
116
- const xmlParts = [
117
- '<?xml version="1.0" encoding="UTF-8"?>'
118
- ];
119
- if (xsl) {
120
- let relativeBaseUrl = resolvers.relativeBaseUrlResolver?.(xsl) ?? xsl;
121
- if (errorInfo && errorInfo.messages.length > 0) {
122
- relativeBaseUrl = withQuery(relativeBaseUrl, {
123
- errors: "true",
124
- error_messages: errorInfo.messages,
125
- error_urls: errorInfo.urls
126
- });
127
- }
128
- xmlParts.push(`<?xml-stylesheet type="text/xsl" href="${escapeValueForXml(relativeBaseUrl)}"?>`);
129
- }
130
- xmlParts.push(
131
- '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
132
- sitemapXml,
133
- "</sitemapindex>"
134
- );
135
- if (credits) {
136
- xmlParts.push(`<!-- XML Sitemap Index generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`);
137
- }
138
- return minify ? xmlParts.join("").replace(/(?<!<[^>]*)\s(?![^<]*>)/g, "") : xmlParts.join("\n");
139
- }
140
- export async function buildSitemapIndex(resolvers, runtimeConfig, nitro) {
141
- if (!import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0 && resolvers.event) {
142
- return buildSitemapIndexCached(resolvers.event, resolvers, runtimeConfig, nitro);
143
- }
144
- return buildSitemapIndexInternal(resolvers, runtimeConfig, nitro);
145
- }
@@ -1,276 +0,0 @@
1
- import { safeGetHeader } from "../../utils/h3-compat.js";
2
- import { defineCachedFunction, useRuntimeConfig } from "nitropack/runtime";
3
- import { resolveSitePath } from "nuxt-site-config/urls";
4
- import { joinURL, withHttps } from "ufo";
5
- import staticConfig from "#sitemap-virtual/static-config.mjs";
6
- import { applyDynamicParams, createPathFilter, findPageMapping, logger, resolveI18nSitemapLocaleKey, splitForLocales } from "../../../utils-pure.js";
7
- import { preNormalizeEntry } from "../urlset/normalise.js";
8
- import { sortInPlace } from "../urlset/sort.js";
9
- import { childSitemapSources, globalSitemapSources, resolveSitemapSources } from "../urlset/sources.js";
10
- import { parseChunkInfo, sliceUrlsForChunk } from "../utils/chunk.js";
11
- const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
12
- export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers, baseURL) {
13
- const {
14
- autoI18n,
15
- isI18nMapped
16
- } = runtimeConfig;
17
- const filterPath = createPathFilter({
18
- include: sitemap.include,
19
- exclude: sitemap.exclude
20
- }, baseURL || "/");
21
- const _urls = urls.map((_e) => {
22
- const e = preNormalizeEntry(_e, resolvers);
23
- if (!e.loc || !filterPath(e.loc))
24
- return false;
25
- return e;
26
- }).filter(Boolean);
27
- let validI18nUrlsForTransform = [];
28
- const withoutPrefixPaths = {};
29
- if (autoI18n && autoI18n.strategy !== "no_prefix") {
30
- const localeCodes = autoI18n.locales.map((l) => l.code);
31
- const localeByCode = new Map(autoI18n.locales.map((l) => [l.code, l]));
32
- const isPrefixStrategy = autoI18n.strategy === "prefix";
33
- const isPrefixExceptOrAndDefault = autoI18n.strategy === "prefix_and_default" || autoI18n.strategy === "prefix_except_default";
34
- const xDefaultAndLocales = [{ code: "x-default", _hreflang: "x-default" }, ...autoI18n.locales];
35
- const defaultLocale = autoI18n.defaultLocale;
36
- const hasPages = !!autoI18n.pages;
37
- const hasDifferentDomains = !!autoI18n.differentDomains;
38
- validI18nUrlsForTransform = _urls.map((_e, i) => {
39
- if (_e._abs)
40
- return false;
41
- const split = splitForLocales(_e._relativeLoc, localeCodes);
42
- let localeCode = split[0];
43
- const pathWithoutPrefix = split[1];
44
- if (!localeCode)
45
- localeCode = defaultLocale;
46
- const e = _e;
47
- e._pathWithoutPrefix = pathWithoutPrefix;
48
- const locale = localeByCode.get(localeCode);
49
- if (!locale)
50
- return false;
51
- e._locale = locale;
52
- e._index = i;
53
- e._key = `${e._sitemap || ""}${e._path?.pathname || "/"}${e._path?.search || ""}`;
54
- withoutPrefixPaths[pathWithoutPrefix] = withoutPrefixPaths[pathWithoutPrefix] || [];
55
- if (!withoutPrefixPaths[pathWithoutPrefix].some((e2) => e2._locale.code === locale.code))
56
- withoutPrefixPaths[pathWithoutPrefix].push(e);
57
- return e;
58
- }).filter(Boolean);
59
- for (const e of validI18nUrlsForTransform) {
60
- if (!e._i18nTransform && !e.alternatives?.length) {
61
- const alternatives = (withoutPrefixPaths[e._pathWithoutPrefix] || []).map((u) => {
62
- const entries = [];
63
- if (u._locale.code === defaultLocale) {
64
- entries.push({
65
- href: u.loc,
66
- hreflang: "x-default"
67
- });
68
- }
69
- entries.push({
70
- href: u.loc,
71
- hreflang: u._locale._hreflang || defaultLocale
72
- });
73
- return entries;
74
- }).flat().filter(Boolean);
75
- if (alternatives.length)
76
- e.alternatives = alternatives;
77
- } else if (e._i18nTransform) {
78
- delete e._i18nTransform;
79
- if (hasDifferentDomains) {
80
- const defLocale = localeByCode.get(defaultLocale);
81
- e.alternatives = [
82
- {
83
- ...defLocale,
84
- code: "x-default"
85
- },
86
- ...autoI18n.locales.filter((l) => !!l.domain)
87
- ].map((locale) => {
88
- return {
89
- hreflang: locale._hreflang,
90
- href: joinURL(withHttps(locale.domain), e._pathWithoutPrefix)
91
- };
92
- });
93
- } else {
94
- const pageMatch = hasPages ? findPageMapping(e._pathWithoutPrefix, autoI18n.pages) : null;
95
- const pathSearch = e._path?.search || "";
96
- const pathWithoutPrefix = e._pathWithoutPrefix;
97
- for (const l of autoI18n.locales) {
98
- let loc = pathWithoutPrefix;
99
- if (pageMatch && pageMatch.mappings[l.code] !== void 0) {
100
- const customPath = pageMatch.mappings[l.code];
101
- if (customPath === false)
102
- continue;
103
- if (typeof customPath === "string") {
104
- loc = customPath[0] === "/" ? customPath : `/${customPath}`;
105
- loc = applyDynamicParams(loc, pageMatch.paramSegments);
106
- if (isPrefixStrategy || isPrefixExceptOrAndDefault && l.code !== defaultLocale)
107
- loc = joinURL(`/${l.code}`, loc);
108
- }
109
- } else if (!hasDifferentDomains && !(isPrefixExceptOrAndDefault && l.code === defaultLocale)) {
110
- loc = joinURL(`/${l.code}`, pathWithoutPrefix);
111
- }
112
- const _sitemap = isI18nMapped ? l._sitemap : void 0;
113
- const alternatives = [];
114
- for (const locale of xDefaultAndLocales) {
115
- const code = locale.code === "x-default" ? defaultLocale : locale.code;
116
- const isDefault = locale.code === "x-default" || locale.code === defaultLocale;
117
- let href = pathWithoutPrefix;
118
- if (pageMatch && pageMatch.mappings[code] !== void 0) {
119
- const customPath = pageMatch.mappings[code];
120
- if (customPath === false)
121
- continue;
122
- if (typeof customPath === "string") {
123
- href = customPath[0] === "/" ? customPath : `/${customPath}`;
124
- href = applyDynamicParams(href, pageMatch.paramSegments);
125
- if (isPrefixStrategy || isPrefixExceptOrAndDefault && !isDefault)
126
- href = joinURL("/", code, href);
127
- }
128
- } else if (isPrefixStrategy) {
129
- href = joinURL("/", code, pathWithoutPrefix);
130
- } else if (isPrefixExceptOrAndDefault && !isDefault) {
131
- href = joinURL("/", code, pathWithoutPrefix);
132
- }
133
- if (!filterPath(href))
134
- continue;
135
- alternatives.push({
136
- hreflang: locale._hreflang,
137
- href
138
- });
139
- }
140
- const { _index: _, ...rest } = e;
141
- const newEntry = preNormalizeEntry({
142
- _sitemap,
143
- ...rest,
144
- _key: `${_sitemap || ""}${loc || "/"}${pathSearch}`,
145
- _locale: l,
146
- loc,
147
- alternatives
148
- }, resolvers);
149
- if (e._locale.code === newEntry._locale.code) {
150
- _urls[e._index] = newEntry;
151
- e._index = void 0;
152
- } else {
153
- _urls.push(newEntry);
154
- }
155
- }
156
- }
157
- }
158
- if (isI18nMapped) {
159
- e._sitemap = e._sitemap || e._locale._sitemap;
160
- e._key = `${e._sitemap || ""}${e.loc || "/"}${e._path?.search || ""}`;
161
- }
162
- if (e._index)
163
- _urls[e._index] = e;
164
- }
165
- }
166
- return _urls;
167
- }
168
- export async function buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro) {
169
- const { sitemaps, autoI18n, isI18nMapped, isMultiSitemap, sortEntries } = runtimeConfig;
170
- let sourcesInput = effectiveSitemap.includeAppSources ? [...await globalSitemapSources(), ...await childSitemapSources(effectiveSitemap)] : await childSitemapSources(effectiveSitemap);
171
- if (nitro && resolvers.event) {
172
- const ctx = {
173
- event: resolvers.event,
174
- sitemapName: matchName,
175
- sources: sourcesInput
176
- };
177
- await nitro.hooks.callHook("sitemap:sources", ctx);
178
- sourcesInput = ctx.sources;
179
- }
180
- const sources = await resolveSitemapSources(sourcesInput, resolvers.event);
181
- const failedSources = sources.filter((source) => source.error && source._isFailure).map((source) => ({
182
- url: typeof source.fetch === "string" ? source.fetch : source.fetch?.[0] || "unknown",
183
- error: source.error || "Unknown error"
184
- }));
185
- const resolvedCtx = {
186
- urls: sources.flatMap((s) => s.urls),
187
- sitemapName: matchName,
188
- event: resolvers.event
189
- };
190
- await nitro?.hooks.callHook("sitemap:input", resolvedCtx);
191
- const enhancedUrls = resolveSitemapEntries(effectiveSitemap, resolvedCtx.urls, { autoI18n, isI18nMapped }, resolvers, useRuntimeConfig().app.baseURL);
192
- const localeSitemapKeys = isI18nMapped && autoI18n ? autoI18n.locales.map((l) => l._sitemap) : [];
193
- if (isMultiSitemap) {
194
- const sitemapNames = Object.keys(sitemaps).filter((k) => k !== "index");
195
- const warnedSitemaps = nitro?._sitemapWarnedSitemaps || /* @__PURE__ */ new Set();
196
- for (const e of enhancedUrls) {
197
- const hasMatchingSitemap = typeof e._sitemap === "string" && (sitemapNames.includes(e._sitemap) || isI18nMapped && sitemapNames.some((name) => resolveI18nSitemapLocaleKey(name, localeSitemapKeys) === e._sitemap));
198
- if (typeof e._sitemap === "string" && !hasMatchingSitemap) {
199
- if (!warnedSitemaps.has(e._sitemap)) {
200
- warnedSitemaps.add(e._sitemap);
201
- logger.error(`Sitemap \`${e._sitemap}\` not found in sitemap config. Available sitemaps: ${sitemapNames.join(", ")}. Entry \`${e.loc}\` will be omitted.`);
202
- }
203
- }
204
- }
205
- if (nitro) {
206
- nitro._sitemapWarnedSitemaps = warnedSitemaps;
207
- }
208
- }
209
- const filteredUrls = enhancedUrls.filter((e) => {
210
- if (e._sitemap === false)
211
- return false;
212
- if (isMultiSitemap && e._sitemap && matchName) {
213
- if (isChunked)
214
- return e._sitemap === matchName;
215
- if (e._sitemap === matchName)
216
- return true;
217
- if (isI18nMapped)
218
- return e._sitemap === resolveI18nSitemapLocaleKey(matchName, localeSitemapKeys);
219
- return false;
220
- }
221
- return true;
222
- });
223
- const urls = sortEntries ? sortInPlace(filteredUrls) : filteredUrls;
224
- return { urls, failedSources };
225
- }
226
- export const buildResolvedSitemapUrlsCached = defineCachedFunction(
227
- async (_event, effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro) => buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro),
228
- {
229
- name: "sitemap:resolved-urls",
230
- group: "sitemap",
231
- base: "sitemap",
232
- maxAge: SERVER_CACHE_MAX_AGE,
233
- getKey: (event, _effectiveSitemap, matchName, isChunked) => {
234
- const host = safeGetHeader(event, "host") || safeGetHeader(event, "x-forwarded-host") || "";
235
- const proto = safeGetHeader(event, "x-forwarded-proto") || "https";
236
- return `resolved-${isChunked ? "chunked-" : ""}${matchName}-${proto}-${host}`;
237
- },
238
- swr: true
239
- }
240
- );
241
- export async function getResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro) {
242
- const event = resolvers.event;
243
- const shouldCache = !import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0;
244
- if (shouldCache && event) {
245
- return buildResolvedSitemapUrlsCached(event, effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro);
246
- }
247
- return buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro);
248
- }
249
- export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro) {
250
- const { sitemaps, autoI18n, defaultSitemapsChunkSize } = runtimeConfig;
251
- const chunkSize = defaultSitemapsChunkSize || void 0;
252
- const chunkInfo = parseChunkInfo(sitemap.sitemapName, sitemaps, chunkSize);
253
- if (autoI18n?.differentDomains) {
254
- const domain = autoI18n.locales.find((e) => e.language === sitemap.sitemapName || e.code === sitemap.sitemapName)?.domain;
255
- if (domain) {
256
- const _tester = resolvers.canonicalUrlResolver;
257
- resolvers.canonicalUrlResolver = (path) => resolveSitePath(path, {
258
- absolute: true,
259
- withBase: false,
260
- siteUrl: withHttps(domain),
261
- trailingSlash: _tester("/test/").endsWith("/"),
262
- base: "/"
263
- });
264
- }
265
- }
266
- let effectiveSitemap = sitemap;
267
- const baseSitemapName = chunkInfo.baseSitemapName;
268
- if (chunkInfo.isChunked && baseSitemapName !== sitemap.sitemapName && sitemaps[baseSitemapName]) {
269
- effectiveSitemap = sitemaps[baseSitemapName];
270
- }
271
- const matchName = chunkInfo.isChunked ? baseSitemapName : sitemap.sitemapName;
272
- const resolved = await getResolvedSitemapUrls(effectiveSitemap, matchName, chunkInfo.isChunked, resolvers, runtimeConfig, nitro);
273
- const urls = sliceUrlsForChunk(resolved.urls, sitemap.sitemapName, sitemaps, chunkSize);
274
- return { urls, failedSources: resolved.failedSources };
275
- }
276
- export { urlsToXml } from "./xml.js";
@@ -1,87 +0,0 @@
1
- import { createError, getRouterParam, sendRedirect } from "h3";
2
- import { safeAppendHeader, safeSetHeader } from "../utils/h3-compat.js";
3
- import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
4
- import { joinURL, withBase, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from "ufo";
5
- import { useSitemapRuntimeConfig } from "../utils.js";
6
- import { buildSitemapIndex, urlsToIndexXml } from "./builder/sitemap-index.js";
7
- import { createSitemap, useNitroUrlResolvers } from "./nitro.js";
8
- import { getSitemapConfig, parseChunkInfo } from "./utils/chunk.js";
9
- export async function sitemapXmlEventHandler(e) {
10
- const runtimeConfig = useSitemapRuntimeConfig();
11
- const { sitemaps } = runtimeConfig;
12
- if ("index" in sitemaps)
13
- return sendRedirect(e, withBase("/sitemap_index.xml", useRuntimeConfig().app.baseURL), import.meta.dev ? 302 : 301);
14
- return createSitemap(e, Object.values(sitemaps)[0], runtimeConfig);
15
- }
16
- export async function sitemapIndexXmlEventHandler(e) {
17
- const runtimeConfig = useSitemapRuntimeConfig();
18
- const nitro = useNitroApp();
19
- const resolvers = useNitroUrlResolvers(e);
20
- const { entries: sitemaps, failedSources } = await buildSitemapIndex(resolvers, runtimeConfig, nitro);
21
- if (import.meta.prerender) {
22
- safeAppendHeader(
23
- e,
24
- "x-nitro-prerender",
25
- sitemaps.filter((entry) => !!entry._sitemapName).map((entry) => encodeURIComponent(joinURL(runtimeConfig.sitemapsPathPrefix || "", `/${entry._sitemapName}.xml`))).join(", ")
26
- );
27
- }
28
- const indexResolvedCtx = { sitemaps, event: e };
29
- await nitro.hooks.callHook("sitemap:index-resolved", indexResolvedCtx);
30
- const errorInfo = failedSources.length > 0 ? { messages: failedSources.map((f) => f.error), urls: failedSources.map((f) => f.url) } : void 0;
31
- const output = urlsToIndexXml(indexResolvedCtx.sitemaps, resolvers, runtimeConfig, errorInfo);
32
- const ctx = { sitemap: output, sitemapName: "sitemap", event: e };
33
- await nitro.hooks.callHook("sitemap:output", ctx);
34
- safeSetHeader(e, "Content-Type", "text/xml; charset=UTF-8");
35
- if (runtimeConfig.cacheMaxAgeSeconds) {
36
- safeSetHeader(e, "Cache-Control", `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, s-maxage=${runtimeConfig.cacheMaxAgeSeconds}, stale-while-revalidate=3600`);
37
- const now = /* @__PURE__ */ new Date();
38
- safeSetHeader(e, "X-Sitemap-Generated", now.toISOString());
39
- safeSetHeader(e, "X-Sitemap-Cache-Duration", `${runtimeConfig.cacheMaxAgeSeconds}s`);
40
- const expiryTime = new Date(now.getTime() + runtimeConfig.cacheMaxAgeSeconds * 1e3);
41
- safeSetHeader(e, "X-Sitemap-Cache-Expires", expiryTime.toISOString());
42
- const remainingSeconds = Math.floor((expiryTime.getTime() - now.getTime()) / 1e3);
43
- safeSetHeader(e, "X-Sitemap-Cache-Remaining", `${remainingSeconds}s`);
44
- } else {
45
- safeSetHeader(e, "Cache-Control", `no-cache, no-store`);
46
- }
47
- return ctx.sitemap;
48
- }
49
- export async function sitemapChildXmlEventHandler(e) {
50
- if (!e.path.endsWith(".xml"))
51
- return;
52
- const runtimeConfig = useSitemapRuntimeConfig(e);
53
- const { sitemaps } = runtimeConfig;
54
- let sitemapName = getRouterParam(e, "sitemap");
55
- if (!sitemapName) {
56
- const path = e.path;
57
- const match = path.match(/(?:\/__sitemap__\/)?(.+)\.xml$/);
58
- if (match)
59
- sitemapName = match[1];
60
- }
61
- if (!sitemapName)
62
- throw createError({ statusCode: 400, message: "Invalid sitemap request" });
63
- sitemapName = sitemapName.replace(/\.xml$/, "");
64
- sitemapName = withLeadingSlash(sitemapName);
65
- if (sitemapName.startsWith("/__sitemap__/"))
66
- sitemapName = sitemapName.replace("/__sitemap__/", "/");
67
- if (runtimeConfig.sitemapsPathPrefix) {
68
- const prefix = withLeadingSlash(runtimeConfig.sitemapsPathPrefix);
69
- if (sitemapName.startsWith(prefix))
70
- sitemapName = sitemapName.replace(prefix, "/");
71
- }
72
- sitemapName = withoutLeadingSlash(withoutTrailingSlash(sitemapName));
73
- const chunkInfo = parseChunkInfo(sitemapName, sitemaps, runtimeConfig.defaultSitemapsChunkSize);
74
- const isAutoChunked = typeof sitemaps.chunks !== "undefined" && !Number.isNaN(Number(sitemapName));
75
- const sitemapExists = sitemapName in sitemaps || chunkInfo.baseSitemapName in sitemaps || isAutoChunked;
76
- if (!sitemapExists)
77
- throw createError({ statusCode: 404, message: `Sitemap "${sitemapName}" not found.` });
78
- if (chunkInfo.isChunked && chunkInfo.chunkIndex !== void 0) {
79
- const baseSitemap = sitemaps[chunkInfo.baseSitemapName];
80
- if (baseSitemap && !baseSitemap.chunks && !baseSitemap._isChunking)
81
- throw createError({ statusCode: 404, message: `Sitemap "${chunkInfo.baseSitemapName}" does not support chunking.` });
82
- if (baseSitemap?._chunkCount !== void 0 && chunkInfo.chunkIndex >= baseSitemap._chunkCount)
83
- throw createError({ statusCode: 404, message: `Chunk ${chunkInfo.chunkIndex} does not exist for sitemap "${chunkInfo.baseSitemapName}".` });
84
- }
85
- const sitemapConfig = getSitemapConfig(sitemapName, sitemaps, runtimeConfig.defaultSitemapsChunkSize || void 0);
86
- return createSitemap(e, sitemapConfig, runtimeConfig);
87
- }
@@ -1,157 +0,0 @@
1
- import { defu } from "defu";
2
- import { createError } from "h3";
3
- import { safeGetHeader, safeGetQuery, safeSetHeader } from "../utils/h3-compat.js";
4
- import { defineCachedFunction, useNitroApp } from "nitropack/runtime";
5
- import { fixSlashes } from "nuxt-site-config/urls";
6
- import { getPathRobotConfig } from "#internal/nuxt-robots/getPathRobotConfig";
7
- import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
8
- import { createSitePathResolver } from "#site-config/server/composables/utils";
9
- import staticConfig from "#sitemap-virtual/static-config.mjs";
10
- import { logger, mergeOnKey, splitForLocales } from "../../utils-pure.js";
11
- import { createNitroRouteRuleMatcher } from "../kit.js";
12
- import { buildSitemapUrls, urlsToXml } from "./builder/sitemap.js";
13
- import { normaliseEntry, preNormalizeEntry } from "./urlset/normalise.js";
14
- import { sortInPlace } from "./urlset/sort.js";
15
- const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
16
-
17
- export function useNitroUrlResolvers(e) {
18
- const canonicalQuery = safeGetQuery(e).canonical;
19
- const isShowingCanonical = typeof canonicalQuery !== "undefined" && canonicalQuery !== "false";
20
- const siteConfig = getSiteConfig(e);
21
- return {
22
- event: e,
23
- fixSlashes: (path) => fixSlashes(siteConfig.trailingSlash, path),
24
- // we need these as they depend on the nitro event
25
- canonicalUrlResolver: createSitePathResolver(e, {
26
- canonical: isShowingCanonical || !import.meta.dev,
27
- absolute: true,
28
- withBase: true
29
- }),
30
- relativeBaseUrlResolver: createSitePathResolver(e, { absolute: false, withBase: true })
31
- };
32
- }
33
- async function buildSitemapXml(event, definition, resolvers, runtimeConfig) {
34
- const { sitemapName } = definition;
35
- const nitro = useNitroApp();
36
- if (import.meta.prerender) {
37
- const config = getSiteConfig(event);
38
- if (!config.url && !nitro._sitemapWarned) {
39
- nitro._sitemapWarned = true;
40
- logger.error("Sitemap Site URL missing!");
41
- logger.info("To fix this please add `{ site: { url: 'site.com' } }` to your Nuxt config or a `NUXT_PUBLIC_SITE_URL=site.com` to your .env. Learn more at https://nuxtseo.com/site-config/getting-started/how-it-works");
42
- throw createError({
43
- statusMessage: "You must provide a site URL to prerender a sitemap.",
44
- statusCode: 500
45
- });
46
- }
47
- }
48
- const { urls: sitemapUrls, failedSources } = await buildSitemapUrls(definition, resolvers, runtimeConfig, nitro);
49
- if (import.meta.prerender && failedSources.length) {
50
- throw createError({
51
- statusCode: 500,
52
- message: `Sitemap generation failed due to ${failedSources.length} failed sources: ${failedSources.map((s) => `"${s.url}" (${s.error})`).join(", ")}`
53
- });
54
- }
55
- const routeRuleMatcher = createNitroRouteRuleMatcher();
56
- const { autoI18n } = runtimeConfig;
57
- let validCount = 0;
58
- for (let i = 0; i < sitemapUrls.length; i++) {
59
- const u = sitemapUrls[i];
60
- const path = u._path?.pathname || u.loc;
61
- if (!getPathRobotConfig(event, { path, skipSiteIndexable: true }).indexable)
62
- continue;
63
- let routeRules = routeRuleMatcher(path);
64
- if (autoI18n?.locales && autoI18n?.strategy !== "no_prefix") {
65
- const match = splitForLocales(path, autoI18n.locales.map((l) => l.code));
66
- const pathWithoutPrefix = match[1];
67
- if (pathWithoutPrefix && pathWithoutPrefix !== path)
68
- routeRules = defu(routeRules, routeRuleMatcher(pathWithoutPrefix));
69
- }
70
- if (routeRules.sitemap === false)
71
- continue;
72
- if (typeof routeRules.robots !== "undefined" && !routeRules.robots)
73
- continue;
74
- const hasRobotsDisabled = Object.entries(routeRules.headers || {}).some(([name, value]) => name.toLowerCase() === "x-robots-tag" && value.toLowerCase().includes("noindex"));
75
- if (routeRules.redirect || hasRobotsDisabled)
76
- continue;
77
- sitemapUrls[validCount++] = routeRules.sitemap ? defu(u, routeRules.sitemap) : u;
78
- }
79
- sitemapUrls.length = validCount;
80
- if (import.meta.dev && validCount === 0 && sitemapUrls.length > 0) {
81
- logger.warn(`Sitemap had ${sitemapUrls.length} that were all filtered out. This may be due to a robots rules blocking these URLs from indexing. Check your /** route rules or robots.txt configuration.`);
82
- }
83
- const locSize = sitemapUrls.length;
84
- const resolvedCtx = {
85
- urls: sitemapUrls,
86
- sitemapName,
87
- event
88
- };
89
- await nitro.hooks.callHook("sitemap:resolved", resolvedCtx);
90
- if (resolvedCtx.urls.length !== locSize) {
91
- resolvedCtx.urls = resolvedCtx.urls.map((e) => preNormalizeEntry(e, resolvers));
92
- }
93
- const maybeSort = (urls2) => runtimeConfig.sortEntries ? sortInPlace(urls2) : urls2;
94
- const defaults = definition.defaults || {};
95
- const normalizedPreDedupe = resolvedCtx.urls.map((e) => normaliseEntry(e, defaults, resolvers));
96
- const urls = maybeSort(mergeOnKey(normalizedPreDedupe, "_key").map((e) => normaliseEntry(e, defaults, resolvers)));
97
- if (definition._isChunking && definition.sitemapName.includes("-")) {
98
- const parts = definition.sitemapName.split("-");
99
- const lastPart = parts.pop();
100
- if (!Number.isNaN(Number(lastPart))) {
101
- const chunkIndex = Number(lastPart);
102
- const baseSitemapName = parts.join("-");
103
- if (urls.length === 0 && chunkIndex > 0) {
104
- throw createError({
105
- statusCode: 404,
106
- message: `Sitemap chunk ${chunkIndex} for "${baseSitemapName}" does not exist.`
107
- });
108
- }
109
- }
110
- }
111
- const errorInfo = failedSources.length > 0 ? {
112
- messages: failedSources.map((f) => f.error),
113
- urls: failedSources.map((f) => f.url)
114
- } : void 0;
115
- const sitemap = urlsToXml(urls, resolvers, runtimeConfig, errorInfo);
116
- const ctx = { sitemap, sitemapName, event };
117
- await nitro.hooks.callHook("sitemap:output", ctx);
118
- return ctx.sitemap;
119
- }
120
- const buildSitemapXmlCached = defineCachedFunction(
121
- buildSitemapXml,
122
- {
123
- name: "sitemap:xml",
124
- group: "sitemap",
125
- maxAge: SERVER_CACHE_MAX_AGE,
126
- base: "sitemap",
127
- // Use the sitemap storage
128
- getKey: (event, definition) => {
129
- const host = safeGetHeader(event, "host") || safeGetHeader(event, "x-forwarded-host") || "";
130
- const proto = safeGetHeader(event, "x-forwarded-proto") || "https";
131
- const sitemapName = definition.sitemapName || "default";
132
- return `${sitemapName}-${proto}-${host}`;
133
- },
134
- swr: true
135
- // Enable stale-while-revalidate
136
- }
137
- );
138
- export async function createSitemap(event, definition, runtimeConfig) {
139
- const resolvers = useNitroUrlResolvers(event);
140
- const shouldCache = !import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0;
141
- const xml = shouldCache ? await buildSitemapXmlCached(event, definition, resolvers, runtimeConfig) : await buildSitemapXml(event, definition, resolvers, runtimeConfig);
142
- safeSetHeader(event, "Content-Type", "text/xml; charset=UTF-8");
143
- if (runtimeConfig.cacheMaxAgeSeconds) {
144
- safeSetHeader(event, "Cache-Control", `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, s-maxage=${runtimeConfig.cacheMaxAgeSeconds}, stale-while-revalidate=3600`);
145
- const now = /* @__PURE__ */ new Date();
146
- safeSetHeader(event, "X-Sitemap-Generated", now.toISOString());
147
- safeSetHeader(event, "X-Sitemap-Cache-Duration", `${runtimeConfig.cacheMaxAgeSeconds}s`);
148
- const expiryTime = new Date(now.getTime() + runtimeConfig.cacheMaxAgeSeconds * 1e3);
149
- safeSetHeader(event, "X-Sitemap-Cache-Expires", expiryTime.toISOString());
150
- const remainingSeconds = Math.floor((expiryTime.getTime() - now.getTime()) / 1e3);
151
- safeSetHeader(event, "X-Sitemap-Cache-Remaining", `${remainingSeconds}s`);
152
- } else {
153
- safeSetHeader(event, "Cache-Control", `no-cache, no-store`);
154
- }
155
- event.context._isSitemap = true;
156
- return xml;
157
- }
@@ -1,55 +0,0 @@
1
- import { appendHeader, getHeader, getQuery, setHeader } from "h3";
2
- import { parseQuery } from "ufo";
3
-
4
- export function safeGetQuery(event) {
5
- try {
6
- return getQuery(event);
7
- }
8
- catch {
9
- const path = event.path || event.node?.req?.url || "/";
10
- const searchIndex = path.indexOf("?");
11
- const search = searchIndex === -1 ? "" : path.slice(searchIndex);
12
- return parseQuery(search);
13
- }
14
- }
15
-
16
- function useNodeResponse(event) {
17
- return !event.res?.headers && event.node?.res;
18
- }
19
-
20
- export function safeGetHeader(event, name) {
21
- if (typeof event.req?.headers?.get === "function") {
22
- return getHeader(event, name);
23
- }
24
- const nodeHeaders = event.node?.req?.headers;
25
- if (nodeHeaders) {
26
- const key = name.toLowerCase();
27
- const value = nodeHeaders[key] ?? nodeHeaders[name];
28
- return Array.isArray(value) ? value[0] : value;
29
- }
30
- return void 0;
31
- }
32
-
33
- export function safeSetHeader(event, name, value) {
34
- if (useNodeResponse(event)) {
35
- event.node.res.setHeader(name, value);
36
- return;
37
- }
38
- setHeader(event, name, value);
39
- }
40
-
41
- export function safeAppendHeader(event, name, value) {
42
- if (useNodeResponse(event)) {
43
- const res = event.node.res;
44
- const current = res.getHeader(name);
45
- if (current === void 0) {
46
- res.setHeader(name, value);
47
- } else if (Array.isArray(current)) {
48
- res.setHeader(name, [...current, value]);
49
- } else {
50
- res.setHeader(name, [current, value]);
51
- }
52
- return;
53
- }
54
- appendHeader(event, name, value);
55
- }