@nuxtjs/sitemap 8.3.4 → 8.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.
@@ -1,7 +1,10 @@
1
+ import { fileURLToPath } from 'node:url'
1
2
  import { resolve } from 'pathe'
2
3
 
4
+ const currentDir = fileURLToPath(new URL('.', import.meta.url))
5
+
3
6
  // Nuxt SEO devtools panel, shipped as a layer (Model C). Components flat-registered
4
7
  // so intra-panel references resolve by name.
5
8
  export default defineNuxtConfig({
6
- components: [{ path: resolve(__dirname, './components'), pathPrefix: false }],
9
+ components: [{ path: resolve(currentDir, './components'), pathPrefix: false }],
7
10
  })
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=3.9.0"
5
5
  },
6
6
  "configKey": "sitemap",
7
- "version": "8.3.4",
7
+ "version": "8.4.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -2,14 +2,14 @@ import { useNuxt, hasNuxtModule, addTypeTemplate, addTemplate, defineNuxtModule,
2
2
  import { defu } from 'defu';
3
3
  import { withSiteUrl, installNuxtSiteConfig } from 'nuxt-site-config/kit';
4
4
  import { isPathFile } from 'nuxt-site-config/urls';
5
- import { isNuxtGenerate, renderNitroTypeAugmentations, useModuleLogger, setupNitroRuntimeCompatibility, getNuxtModuleOptions, resolveNitroPreset, resolveNuxtContentVersion, createPagesPromise, createNitroPromise } from 'nuxtseo-shared/kit';
5
+ import { isNuxtGenerate, renderNitroTypeAugmentations, useModuleLogger, setupNitroRuntimeCompatibility, getNuxtModuleOptions, resolveNitroPreset, resolveContentProvider, setupContentRuntime, createPagesPromise, createNitroPromise } from 'nuxtseo-shared/kit';
6
6
  import { serializeFilters } from 'nuxtseo-shared/utils';
7
7
  import { dirname, extname } from 'pathe';
8
8
  import { readPackageJSON } from 'pkg-types';
9
9
  import { withBase, withHttps, withTrailingSlash, withoutLeadingSlash, joinURL, withLeadingSlash } from 'ufo';
10
10
  import { setupDevToolsUI as setupDevToolsUI$1 } from 'nuxtseo-shared/devtools';
11
11
  import { readFileSync, statSync } from 'node:fs';
12
- import { mkdir, writeFile } from 'node:fs/promises';
12
+ import { rm, mkdir, writeFile } from 'node:fs/promises';
13
13
  import { join } from 'node:path';
14
14
  import { colors } from 'consola/utils';
15
15
  import { splitForLocales, createPathFilter } from '../dist/runtime/utils-pure.js';
@@ -18,6 +18,9 @@ import { normaliseDate } from '../dist/runtime/server/sitemap/urlset/normalise.j
18
18
  import { mapPathForI18nPages, splitPathForI18nLocales as splitPathForI18nLocales$1, expandCompactLocaleRoute, normalizeLocales, generatePathForI18nPages } from 'nuxtseo-shared/i18n';
19
19
  import 'ultrahtml';
20
20
 
21
+ const COMARK_CONTENT_SOURCE = "@harlan-zw/comark-content:urls";
22
+ const COMARK_CONTENT_SITEMAP_ROUTE = "/__sitemap__/comark-content-urls.json";
23
+
21
24
  function setupDevToolsUI(_options, resolve, nuxt = useNuxt()) {
22
25
  setupDevToolsUI$1(
23
26
  { route: "/__nuxt-sitemap", name: "sitemap", title: "Sitemap", icon: "carbon:load-balancer-application" },
@@ -72,6 +75,10 @@ export async function readSourcesFromFilesystem(filename) {
72
75
  `;
73
76
  });
74
77
  nuxt.hooks.hook("nitro:init", async (nitro) => {
78
+ await Promise.all([
79
+ rm(join(runtimeAssetsPath, "global-sources.json"), { force: true }),
80
+ rm(join(runtimeAssetsPath, "child-sources.json"), { force: true })
81
+ ]);
75
82
  nitro.hooks.hook("prerender:generate", async (route) => {
76
83
  const html = route.contents;
77
84
  if (!route.fileName?.endsWith(".html") || !html || ["/200.html", "/404.html"].includes(route.route))
@@ -455,7 +462,34 @@ function generateExtraRoutesFromNuxtConfig(nuxt = useNuxt()) {
455
462
  }).map(([k]) => k).filter(filterForValidPage);
456
463
  return { routeRules };
457
464
  }
465
+ function resolveExcludedAppSources(resolved, authored) {
466
+ if (resolved === true || authored === true)
467
+ return true;
468
+ if (!Array.isArray(authored))
469
+ return resolved;
470
+ const excluded = [...resolved];
471
+ for (const source of authored) {
472
+ if (typeof source === "string" && !excluded.includes(source))
473
+ excluded.push(source);
474
+ }
475
+ return excluded;
476
+ }
458
477
 
478
+ const IMAGE_TAGS = /* @__PURE__ */ new Set(["image", "img", "nuxtimg", "nuxt-img"]);
479
+ function discoverContentImages(body) {
480
+ const images = [];
481
+ const walk = (nodes) => {
482
+ for (const node of nodes || []) {
483
+ if (!Array.isArray(node) || typeof node[0] !== "string")
484
+ continue;
485
+ if (IMAGE_TAGS.has(node[0]) && node[1]?.src)
486
+ images.push({ loc: node[1].src });
487
+ walk(node.slice(2));
488
+ }
489
+ };
490
+ walk(body?.value ?? body?.nodes);
491
+ return images;
492
+ }
459
493
  const module$1 = defineNuxtModule({
460
494
  meta: {
461
495
  name: "@nuxtjs/sitemap",
@@ -480,6 +514,10 @@ const module$1 = defineNuxtModule({
480
514
  version: ">=2",
481
515
  optional: true
482
516
  },
517
+ "@harlan-zw/comark-content": {
518
+ version: ">=0.1.2",
519
+ optional: true
520
+ },
483
521
  "@nuxtjs/robots": {
484
522
  version: ">=4",
485
523
  optional: true
@@ -673,11 +711,11 @@ const module$1 = defineNuxtModule({
673
711
  const hasCustomI18nSitemaps = i18nSitemaps.length > 0;
674
712
  if (hasCustomI18nSitemaps) {
675
713
  for (const { name: name2, cfg } of i18nSitemaps) {
714
+ const { sitemapName: _sitemapName, _route, _isChunking, _chunkSize, _chunkCount, ...inheritedConfig } = cfg;
676
715
  for (const locale of resolvedAutoI18n.locales) {
677
716
  newSitemaps[`${locale._sitemap}-${name2}`] = {
678
- includeAppSources: true,
679
- ...cfg.exclude?.length && { exclude: cfg.exclude },
680
- ...cfg.include?.length && { include: cfg.include }
717
+ ...inheritedConfig,
718
+ includeAppSources: true
681
719
  };
682
720
  }
683
721
  }
@@ -770,20 +808,13 @@ const module$1 = defineNuxtModule({
770
808
  addServerPlugin(resolve("./runtime/server/plugins/stream-transport"));
771
809
  }
772
810
  const isNuxtContentDocumentDriven = !!nuxt.options.content?.documentDriven || config.strictNuxtContentPaths;
773
- const contentVersion = await resolveNuxtContentVersion();
774
- const isNuxtContentV3 = contentVersion && contentVersion.version === 3;
811
+ const contentProvider = await resolveContentProvider(nuxt);
812
+ const isNuxtContentV3 = contentProvider._tag === "NuxtContent" && contentProvider.version === 3;
813
+ const isNuxtContentV2 = contentProvider._tag === "NuxtContent" && contentProvider.version === 2;
814
+ const isComarkContent = contentProvider._tag === "Comark";
775
815
  const nuxtV3Collections = /* @__PURE__ */ new Set();
776
- const isNuxtContentV2 = contentVersion && contentVersion.version === 2;
777
- if (isNuxtContentV3) {
778
- if (nuxt.options._installedModules.some((m) => m.meta.name === "Content")) {
779
- logger.warn("You have loaded `@nuxt/content` before `@nuxtjs/sitemap`, this may cause issues with the integration. Please ensure `@nuxtjs/sitemap` is loaded first.");
780
- }
781
- config.exclude.push("/__nuxt_content/**");
782
- const needsCustomAlias = await hasNuxtModuleCompatibility("@nuxt/content", "<3.6.0");
783
- if (needsCustomAlias) {
784
- nuxt.options.alias["#sitemap/content-v3-nitro-path"] = resolve(dirname(resolveModule("@nuxt/content")), "runtime/nitro");
785
- nuxt.options.alias["@nuxt/content/nitro"] = resolve("./runtime/server/content-compat");
786
- }
816
+ setupContentRuntime(contentProvider, nuxt);
817
+ const registerContentSitemapHook = (options) => {
787
818
  nuxt.hooks.hook("content:file:afterParse", (ctx) => {
788
819
  try {
789
820
  const content = ctx.content;
@@ -792,7 +823,7 @@ const module$1 = defineNuxtModule({
792
823
  ctx.content.sitemap = null;
793
824
  return;
794
825
  }
795
- if (!ctx.collection.fields || !("sitemap" in ctx.collection.fields)) {
826
+ if (options.requireCollectionField && (!ctx.collection.fields || !("sitemap" in ctx.collection.fields))) {
796
827
  ctx.content.sitemap = null;
797
828
  return;
798
829
  }
@@ -805,13 +836,8 @@ const module$1 = defineNuxtModule({
805
836
  return;
806
837
  }
807
838
  const images = [];
808
- if (config.discoverImages) {
809
- images.push(
810
- ...content.body?.value?.filter(
811
- (c) => ["image", "img", "nuxtimg", "nuxt-img"].includes(c[0])
812
- ).filter((c) => c[1]?.src).map((c) => ({ loc: c[1].src })) || []
813
- );
814
- }
839
+ if (config.discoverImages)
840
+ images.push(...discoverContentImages(content.body));
815
841
  const lastmod = content.seo?.articleModifiedTime || content.updatedAt;
816
842
  const defaults = {
817
843
  loc: content.path
@@ -825,6 +851,8 @@ const module$1 = defineNuxtModule({
825
851
  logger.warn(`Failed to process sitemap data for content file (collection: ${ctx.collection?.name}, path: ${ctx.content?.path}), skipping.`, e);
826
852
  }
827
853
  });
854
+ };
855
+ const addContentCallbackVirtuals = () => {
828
856
  nuxt.hook("nitro:config", (nitroConfig) => {
829
857
  const filterEntries = [];
830
858
  if (globalThis.__sitemapCollectionFilters) {
@@ -842,9 +870,22 @@ ${filterEntries.join("\n")}`;
842
870
  nitroConfig.virtual["#sitemap/content-on-url"] = `export const onUrlFns = new Map()
843
871
  ${onUrlEntries.join("\n")}`;
844
872
  });
873
+ };
874
+ if (isNuxtContentV3) {
875
+ if (nuxt.options._installedModules.some((m) => m.meta.name === "Content")) {
876
+ logger.warn("You have loaded `@nuxt/content` before `@nuxtjs/sitemap`, this may cause issues with the integration. Please ensure `@nuxtjs/sitemap` is loaded first.");
877
+ }
878
+ config.exclude.push("/__nuxt_content/**");
879
+ const needsCustomAlias = await hasNuxtModuleCompatibility("@nuxt/content", "<3.6.0");
880
+ if (needsCustomAlias) {
881
+ nuxt.options.alias["#sitemap/content-v3-nitro-path"] = resolve(dirname(resolveModule("@nuxt/content")), "runtime/nitro");
882
+ nuxt.options.alias["@nuxt/content/nitro"] = resolve("./runtime/server/content-compat");
883
+ }
884
+ registerContentSitemapHook({ requireCollectionField: true });
885
+ addContentCallbackVirtuals();
845
886
  addServerHandler({
846
887
  route: "/__sitemap__/nuxt-content-urls.json",
847
- handler: resolve("./runtime/server/routes/__sitemap__/nuxt-content-urls-v3")
888
+ handler: resolve("./runtime/server/routes/__sitemap__/content-urls")
848
889
  });
849
890
  if (config.strictNuxtContentPaths) {
850
891
  logger.warn("You have set `strictNuxtContentPaths: true` but are using @nuxt/content v3. This is not required, please remove it.");
@@ -857,6 +898,24 @@ ${onUrlEntries.join("\n")}`;
857
898
  },
858
899
  fetch: "/__sitemap__/nuxt-content-urls.json"
859
900
  });
901
+ } else if (isComarkContent) {
902
+ registerContentSitemapHook({ requireCollectionField: false });
903
+ addContentCallbackVirtuals();
904
+ addServerHandler({
905
+ route: COMARK_CONTENT_SITEMAP_ROUTE,
906
+ handler: resolve("./runtime/server/routes/__sitemap__/content-urls")
907
+ });
908
+ if (config.strictNuxtContentPaths) {
909
+ logger.warn("You have set `strictNuxtContentPaths: true` but are using comark-content. This is not required, please remove it.");
910
+ }
911
+ appGlobalSources.push({
912
+ context: {
913
+ name: COMARK_CONTENT_SOURCE,
914
+ description: "Generated from your markdown files.",
915
+ tips: nuxtV3Collections.size ? [`Parsing the following collections: ${Array.from(nuxtV3Collections).join(", ")}`] : ["No collections found. Set `sitemap: false` on a collection to keep it out."]
916
+ },
917
+ fetch: COMARK_CONTENT_SITEMAP_ROUTE
918
+ });
860
919
  } else if (isNuxtContentV2) {
861
920
  addServerPlugin(resolve("./runtime/server/plugins/nuxt-content-v2"));
862
921
  addServerHandler({
@@ -1097,6 +1156,10 @@ ${onUrlEntries.join("\n")}`;
1097
1156
  return r.contentType?.includes("text/html");
1098
1157
  };
1099
1158
  const generateGlobalSources = async () => {
1159
+ const excludedAppSources = resolveExcludedAppSources(
1160
+ config.excludeAppSources,
1161
+ nuxt.options.sitemap?.excludeAppSources
1162
+ );
1100
1163
  const { routeRules: routeRules2 } = generateExtraRoutesFromNuxtConfig();
1101
1164
  const nitro = await nitroPromise;
1102
1165
  const prerenderedRoutes2 = nitro._prerenderedRoutes || [];
@@ -1173,7 +1236,7 @@ ${onUrlEntries.join("\n")}`;
1173
1236
  s.sourceType = "user";
1174
1237
  return s;
1175
1238
  }),
1176
- ...(config.excludeAppSources === true ? [] : [
1239
+ ...(excludedAppSources === true ? [] : [
1177
1240
  ...appGlobalSources,
1178
1241
  {
1179
1242
  context: {
@@ -1205,7 +1268,7 @@ ${onUrlEntries.join("\n")}`;
1205
1268
  },
1206
1269
  urls: prerenderUrlsFinal
1207
1270
  }
1208
- ]).filter((s) => !config.excludeAppSources.includes(s.context.name) && (!!s.urls?.length || !!s.fetch)).map((s) => {
1271
+ ]).filter((s) => !excludedAppSources.includes(s.context.name) && (!!s.urls?.length || !!s.fetch)).map((s) => {
1209
1272
  s.sourceType = "app";
1210
1273
  return s;
1211
1274
  })
@@ -0,0 +1,9 @@
1
+ /**
2
+ * URLs from the installed content module's page collections.
3
+ *
4
+ * One route for every provider. `#nuxtseo/content` normalizes the manifest shape
5
+ * and the query builder; the `sitemap` field on each entry is written by the
6
+ * module's `content:file:afterParse` hook.
7
+ */
8
+ declare const _default: any;
9
+ export default _default;
@@ -0,0 +1,34 @@
1
+ import * as contentRuntime from "#nuxtseo/content";
2
+ import { defineEventHandler } from "#nuxtseo/h3";
3
+ import { filters } from "#sitemap/content-filters";
4
+ import { onUrlFns } from "#sitemap/content-on-url";
5
+ const { listPageCollections, provider, queryPages } = contentRuntime;
6
+ export default defineEventHandler(async (e) => {
7
+ const collections = (await listPageCollections(e)).filter((collection) => collection.inSitemap && collection.hasField("sitemap")).map((collection) => collection.name);
8
+ const results = await Promise.all(collections.map(async (collection) => {
9
+ const needsAllFields = filters?.has(collection) || onUrlFns?.has(collection);
10
+ const query = queryPages(e, collection).where("path", "IS NOT NULL").where("sitemap", "IS NOT NULL");
11
+ if (!needsAllFields)
12
+ query.select("path", "sitemap");
13
+ try {
14
+ const entries = await query.all();
15
+ const filter = filters?.get(collection);
16
+ return { collection, entries: filter ? entries.filter(filter) : entries };
17
+ } catch (err) {
18
+ const hint = provider === "nuxt-content-v3" ? " On serverless the content DB is restored from a prerendered sql_dump.txt that isn't readable inside the function (nuxt/content#3805). Fix: prerender the sitemap so content URLs resolve at build, or configure a runtime database (D1/Turso/Postgres)." : "";
19
+ console.error(`[@nuxtjs/sitemap] Couldn't query content collection "${collection}" for the sitemap, so its URLs will be missing.${hint}`, err);
20
+ return { collection, entries: [] };
21
+ }
22
+ }));
23
+ return results.flatMap(({ collection, entries }) => {
24
+ const onUrl = onUrlFns?.get(collection);
25
+ return entries.filter((entry) => entry.sitemap !== false && entry.path && !entry.path.endsWith(".navigation")).map((entry) => {
26
+ const url = {
27
+ loc: entry.path,
28
+ ...typeof entry.sitemap === "object" && entry.sitemap ? entry.sitemap : {}
29
+ };
30
+ onUrl?.(url, entry, collection);
31
+ return url;
32
+ });
33
+ });
34
+ });
@@ -204,7 +204,7 @@ export interface SitemapSourceResolved extends Omit<SitemapSourceBase, 'urls'> {
204
204
  message: string;
205
205
  }[];
206
206
  }
207
- export type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rules' | '@nuxtjs/i18n:pages' | 'nuxt-i18n-micro:pages' | '@nuxt/content@v2:urls' | '@nuxt/content@v3:urls';
207
+ export type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rules' | '@nuxtjs/i18n:pages' | 'nuxt-i18n-micro:pages' | '@nuxt/content@v2:urls' | '@nuxt/content@v3:urls' | '@harlan-zw/comark-content:urls';
208
208
  export type SitemapSourceInput = string | [string, FetchOptions] | SitemapSourceBase | SitemapSourceResolved;
209
209
  interface LocaleObject extends Record<string, any> {
210
210
  code: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxtjs/sitemap",
3
3
  "type": "module",
4
- "version": "8.3.4",
4
+ "version": "8.4.0",
5
5
  "description": "Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -58,42 +58,43 @@
58
58
  "@nuxt/kit": "^4.5.2",
59
59
  "consola": "^3.4.2",
60
60
  "defu": "^6.1.7",
61
- "nuxt-site-config": "^4.2.0",
62
- "nuxtseo-shared": "^5.3.11",
61
+ "nuxt-site-config": "^4.2.3",
62
+ "nuxtseo-shared": "^5.3.14",
63
63
  "ofetch": "^1.5.1",
64
64
  "pathe": "^2.0.3",
65
65
  "pkg-types": "^2.3.1",
66
66
  "radix3": "^1.1.2",
67
67
  "ufo": "^1.6.4",
68
68
  "ultrahtml": "^1.7.0",
69
- "sitemapd": "^0.2.1"
69
+ "sitemapd": "^0.2.2"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@antfu/eslint-config": "^9.3.0",
73
73
  "@arethetypeswrong/cli": "^0.18.5",
74
+ "@harlan-zw/comark-content": "^0.1.3",
74
75
  "@nuxt/content": "^3.15.2",
75
- "@nuxt/devtools-kit": "4.0.0-alpha.9",
76
+ "@nuxt/devtools-kit": "4.0.0-alpha.11",
76
77
  "@nuxt/module-builder": "^1.0.3",
77
78
  "@nuxt/test-utils": "^4.1.0",
78
79
  "@nuxt/ui": "^4.10.0",
79
80
  "@nuxtjs/i18n": "^10.6.0",
80
- "@nuxtjs/robots": "^6.1.4",
81
+ "@nuxtjs/robots": "^6.1.5",
81
82
  "@vue/test-utils": "^2.4.11",
82
83
  "better-sqlite3": "^13.0.3",
83
- "bumpp": "^12.2.0",
84
+ "bumpp": "^12.2.1",
84
85
  "eslint": "^10.8.1",
85
- "eslint-plugin-harlanzw": "^0.17.1",
86
+ "eslint-plugin-harlanzw": "^0.20.0",
86
87
  "happy-dom": "^20.11.2",
87
88
  "nuxt": "^4.5.2",
88
89
  "nuxt-i18n-micro": "^3.28.0",
89
- "nuxtseo-layer-devtools": "^5.3.11",
90
+ "nuxtseo-layer-devtools": "^5.3.14",
90
91
  "std-env": "^4.2.0",
91
92
  "typescript": "6.0.3",
92
93
  "unbuild": "^3.6.1",
93
- "vitest": "^4.1.10",
94
- "vue-tsc": "^3.3.9",
94
+ "vitest": "^4.1.11",
95
+ "vue-tsc": "^3.3.10",
95
96
  "zod": "^4.4.3",
96
- "@nuxtjs/sitemap": "8.3.4"
97
+ "@nuxtjs/sitemap": "8.4.0"
97
98
  },
98
99
  "scripts": {
99
100
  "lint": "eslint .",
@@ -1,2 +0,0 @@
1
- declare const _default: any;
2
- export default _default;
@@ -1,40 +0,0 @@
1
- import { queryCollection } from "@nuxt/content/server";
2
- import manifest from "#content/manifest";
3
- import { defineEventHandler } from "#nuxtseo/h3";
4
- import { filters } from "#sitemap/content-filters";
5
- import { onUrlFns } from "#sitemap/content-on-url";
6
- export default defineEventHandler(async (e) => {
7
- const collections = [];
8
- for (const collection in manifest) {
9
- if (manifest[collection].fields.sitemap)
10
- collections.push(collection);
11
- }
12
- const contentList = [];
13
- for (const collection of collections) {
14
- const needsAllFields = filters?.has(collection) || onUrlFns?.has(collection);
15
- const query = queryCollection(e, collection).where("path", "IS NOT NULL").where("sitemap", "IS NOT NULL");
16
- if (!needsAllFields)
17
- query.select("path", "sitemap");
18
- contentList.push(
19
- query.all().then((results2) => {
20
- const filter = filters?.get(collection);
21
- return { collection, entries: filter ? results2.filter(filter) : results2 };
22
- }).catch((err) => {
23
- console.error(`[@nuxtjs/sitemap] Couldn't query @nuxt/content collection "${collection}" for the sitemap, so its URLs will be missing. On serverless the content DB is restored from a prerendered sql_dump.txt that isn't readable inside the function (nuxt/content#3805). Fix: prerender the sitemap so content URLs resolve at build, or configure a runtime database (D1/Turso/Postgres).`, err);
24
- return { collection, entries: [] };
25
- })
26
- );
27
- }
28
- const results = await Promise.all(contentList);
29
- return results.flatMap(({ collection, entries }) => {
30
- const onUrl = onUrlFns?.get(collection);
31
- return entries.filter((c) => c.sitemap !== false && c.path && !c.path.endsWith(".navigation")).map((c) => {
32
- const url = {
33
- loc: c.path,
34
- ...typeof c.sitemap === "object" ? c.sitemap : {}
35
- };
36
- onUrl?.(url, c, collection);
37
- return url;
38
- });
39
- }).filter(Boolean);
40
- });