@puzzle-ui/seo 0.0.0-stage → 0.5.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.
Files changed (220) hide show
  1. package/README.md +755 -2
  2. package/dist/LICENSE +21 -0
  3. package/dist/context/config.cjs +82 -0
  4. package/dist/context/config.cjs.map +1 -0
  5. package/dist/context/config.d.cts +6 -0
  6. package/dist/context/config.d.ts +6 -0
  7. package/dist/context/config.js +80 -0
  8. package/dist/context/config.js.map +1 -0
  9. package/dist/context/index.cjs +6 -0
  10. package/dist/context/index.d.cts +4 -0
  11. package/dist/context/index.d.ts +4 -0
  12. package/dist/context/index.js +3 -0
  13. package/dist/context/types.d.cts +46 -0
  14. package/dist/context/types.d.ts +46 -0
  15. package/dist/core/graph.cjs +464 -0
  16. package/dist/core/graph.cjs.map +1 -0
  17. package/dist/core/graph.d.cts +62 -0
  18. package/dist/core/graph.d.ts +62 -0
  19. package/dist/core/graph.js +456 -0
  20. package/dist/core/graph.js.map +1 -0
  21. package/dist/core/index.cjs +11 -0
  22. package/dist/core/index.d.cts +3 -0
  23. package/dist/core/index.d.ts +3 -0
  24. package/dist/core/index.js +1 -0
  25. package/dist/core/types.d.cts +92 -0
  26. package/dist/core/types.d.ts +92 -0
  27. package/dist/index.cjs +96 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +48 -0
  30. package/dist/index.d.ts +38 -0
  31. package/dist/index.js +19 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/metadata/index.cjs +4 -0
  34. package/dist/metadata/index.d.cts +3 -0
  35. package/dist/metadata/index.d.ts +3 -0
  36. package/dist/metadata/index.js +1 -0
  37. package/dist/metadata/resolve.cjs +122 -0
  38. package/dist/metadata/resolve.cjs.map +1 -0
  39. package/dist/metadata/resolve.d.cts +4 -0
  40. package/dist/metadata/resolve.d.ts +4 -0
  41. package/dist/metadata/resolve.js +121 -0
  42. package/dist/metadata/resolve.js.map +1 -0
  43. package/dist/metadata/types.d.cts +82 -0
  44. package/dist/metadata/types.d.ts +82 -0
  45. package/dist/next/index.cjs +13 -0
  46. package/dist/next/index.cjs.map +1 -0
  47. package/dist/next/index.d.cts +15 -0
  48. package/dist/next/index.d.ts +14 -0
  49. package/dist/next/index.js +12 -0
  50. package/dist/next/index.js.map +1 -0
  51. package/dist/react/context.cjs +30 -0
  52. package/dist/react/context.cjs.map +1 -0
  53. package/dist/react/context.d.cts +14 -0
  54. package/dist/react/context.d.ts +14 -0
  55. package/dist/react/context.js +27 -0
  56. package/dist/react/context.js.map +1 -0
  57. package/dist/react/index.cjs +48 -0
  58. package/dist/react/index.d.cts +6 -0
  59. package/dist/react/index.d.ts +6 -0
  60. package/dist/react/index.js +5 -0
  61. package/dist/react/metadata.cjs +100 -0
  62. package/dist/react/metadata.cjs.map +1 -0
  63. package/dist/react/metadata.d.cts +18 -0
  64. package/dist/react/metadata.d.ts +18 -0
  65. package/dist/react/metadata.js +99 -0
  66. package/dist/react/metadata.js.map +1 -0
  67. package/dist/react/provider.cjs +42 -0
  68. package/dist/react/provider.cjs.map +1 -0
  69. package/dist/react/provider.d.cts +3 -0
  70. package/dist/react/provider.d.ts +3 -0
  71. package/dist/react/provider.js +42 -0
  72. package/dist/react/provider.js.map +1 -0
  73. package/dist/react/schema-components.cjs +294 -0
  74. package/dist/react/schema-components.cjs.map +1 -0
  75. package/dist/react/schema-components.d.cts +84 -0
  76. package/dist/react/schema-components.d.ts +80 -0
  77. package/dist/react/schema-components.js +254 -0
  78. package/dist/react/schema-components.js.map +1 -0
  79. package/dist/react/types.d.cts +81 -0
  80. package/dist/react/types.d.ts +80 -0
  81. package/dist/react-router/index.cjs +13 -0
  82. package/dist/react-router/index.cjs.map +1 -0
  83. package/dist/react-router/index.d.cts +21 -0
  84. package/dist/react-router/index.d.ts +20 -0
  85. package/dist/react-router/index.js +12 -0
  86. package/dist/react-router/index.js.map +1 -0
  87. package/dist/robots/generate.cjs +88 -0
  88. package/dist/robots/generate.cjs.map +1 -0
  89. package/dist/robots/generate.d.cts +4 -0
  90. package/dist/robots/generate.d.ts +4 -0
  91. package/dist/robots/generate.js +86 -0
  92. package/dist/robots/generate.js.map +1 -0
  93. package/dist/robots/index.cjs +5 -0
  94. package/dist/robots/index.d.cts +3 -0
  95. package/dist/robots/index.d.ts +3 -0
  96. package/dist/robots/index.js +1 -0
  97. package/dist/robots/types.d.cts +27 -0
  98. package/dist/robots/types.d.ts +27 -0
  99. package/dist/schema/create-registry.cjs +12 -0
  100. package/dist/schema/create-registry.cjs.map +1 -0
  101. package/dist/schema/create-registry.d.cts +2 -0
  102. package/dist/schema/create-registry.d.ts +2 -0
  103. package/dist/schema/create-registry.js +12 -0
  104. package/dist/schema/create-registry.js.map +1 -0
  105. package/dist/schema/generated/index.d.cts +2 -0
  106. package/dist/schema/generated/schemaorg.cjs +36386 -0
  107. package/dist/schema/generated/schemaorg.cjs.map +1 -0
  108. package/dist/schema/generated/schemaorg.d.cts +945 -0
  109. package/dist/schema/generated/schemaorg.d.ts +945 -0
  110. package/dist/schema/generated/schemaorg.js +36384 -0
  111. package/dist/schema/generated/schemaorg.js.map +1 -0
  112. package/dist/schema/index.cjs +11 -0
  113. package/dist/schema/index.d.cts +7 -0
  114. package/dist/schema/index.d.ts +6 -0
  115. package/dist/schema/index.js +5 -0
  116. package/dist/schema/registry.cjs +194 -0
  117. package/dist/schema/registry.cjs.map +1 -0
  118. package/dist/schema/registry.d.cts +2 -0
  119. package/dist/schema/registry.d.ts +2 -0
  120. package/dist/schema/registry.js +194 -0
  121. package/dist/schema/registry.js.map +1 -0
  122. package/dist/schema/types.d.cts +79 -0
  123. package/dist/schema/types.d.ts +79 -0
  124. package/dist/schema/validate.cjs +131 -0
  125. package/dist/schema/validate.cjs.map +1 -0
  126. package/dist/schema/validate.d.cts +16 -0
  127. package/dist/schema/validate.d.ts +16 -0
  128. package/dist/schema/validate.js +131 -0
  129. package/dist/schema/validate.js.map +1 -0
  130. package/dist/server/handlers.cjs +96 -0
  131. package/dist/server/handlers.cjs.map +1 -0
  132. package/dist/server/handlers.d.cts +3 -0
  133. package/dist/server/handlers.d.ts +3 -0
  134. package/dist/server/handlers.js +95 -0
  135. package/dist/server/handlers.js.map +1 -0
  136. package/dist/server/index.cjs +6 -0
  137. package/dist/server/index.d.cts +4 -0
  138. package/dist/server/index.d.ts +4 -0
  139. package/dist/server/index.js +3 -0
  140. package/dist/server/routes.cjs +17 -0
  141. package/dist/server/routes.cjs.map +1 -0
  142. package/dist/server/routes.d.cts +2 -0
  143. package/dist/server/routes.d.ts +2 -0
  144. package/dist/server/routes.js +17 -0
  145. package/dist/server/routes.js.map +1 -0
  146. package/dist/server/types.d.cts +61 -0
  147. package/dist/server/types.d.ts +61 -0
  148. package/dist/sitemap/generate.cjs +147 -0
  149. package/dist/sitemap/generate.cjs.map +1 -0
  150. package/dist/sitemap/generate.d.cts +3 -0
  151. package/dist/sitemap/generate.d.ts +3 -0
  152. package/dist/sitemap/generate.js +146 -0
  153. package/dist/sitemap/generate.js.map +1 -0
  154. package/dist/sitemap/index.cjs +11 -0
  155. package/dist/sitemap/index.d.cts +4 -0
  156. package/dist/sitemap/index.d.ts +4 -0
  157. package/dist/sitemap/index.js +3 -0
  158. package/dist/sitemap/types.d.cts +82 -0
  159. package/dist/sitemap/types.d.ts +82 -0
  160. package/dist/sitemap/xml.cjs +215 -0
  161. package/dist/sitemap/xml.cjs.map +1 -0
  162. package/dist/sitemap/xml.d.cts +16 -0
  163. package/dist/sitemap/xml.d.ts +16 -0
  164. package/dist/sitemap/xml.js +210 -0
  165. package/dist/sitemap/xml.js.map +1 -0
  166. package/dist/tanstack/index.cjs +13 -0
  167. package/dist/tanstack/index.cjs.map +1 -0
  168. package/dist/tanstack/index.d.cts +19 -0
  169. package/dist/tanstack/index.d.ts +18 -0
  170. package/dist/tanstack/index.js +12 -0
  171. package/dist/tanstack/index.js.map +1 -0
  172. package/dist/testing/diagnostics.cjs +27 -0
  173. package/dist/testing/diagnostics.cjs.map +1 -0
  174. package/dist/testing/diagnostics.d.cts +3 -0
  175. package/dist/testing/diagnostics.d.ts +3 -0
  176. package/dist/testing/diagnostics.js +27 -0
  177. package/dist/testing/diagnostics.js.map +1 -0
  178. package/dist/testing/fixture.cjs +80 -0
  179. package/dist/testing/fixture.cjs.map +1 -0
  180. package/dist/testing/fixture.d.cts +4 -0
  181. package/dist/testing/fixture.d.ts +4 -0
  182. package/dist/testing/fixture.js +78 -0
  183. package/dist/testing/fixture.js.map +1 -0
  184. package/dist/testing/index.cjs +9 -0
  185. package/dist/testing/index.d.cts +5 -0
  186. package/dist/testing/index.d.ts +5 -0
  187. package/dist/testing/index.js +4 -0
  188. package/dist/testing/inspect.cjs +63 -0
  189. package/dist/testing/inspect.cjs.map +1 -0
  190. package/dist/testing/inspect.d.cts +5 -0
  191. package/dist/testing/inspect.d.ts +5 -0
  192. package/dist/testing/inspect.js +63 -0
  193. package/dist/testing/inspect.js.map +1 -0
  194. package/dist/testing/types.d.cts +40 -0
  195. package/dist/testing/types.d.ts +40 -0
  196. package/dist/urls/index.cjs +8 -0
  197. package/dist/urls/index.d.cts +3 -0
  198. package/dist/urls/index.d.ts +3 -0
  199. package/dist/urls/index.js +1 -0
  200. package/dist/urls/normalize.cjs +65 -0
  201. package/dist/urls/normalize.cjs.map +1 -0
  202. package/dist/urls/normalize.d.cts +14 -0
  203. package/dist/urls/normalize.d.ts +14 -0
  204. package/dist/urls/normalize.js +60 -0
  205. package/dist/urls/normalize.js.map +1 -0
  206. package/dist/urls/types.d.cts +22 -0
  207. package/dist/urls/types.d.ts +22 -0
  208. package/dist/validation/index.cjs +5 -0
  209. package/dist/validation/index.d.cts +3 -0
  210. package/dist/validation/index.d.ts +3 -0
  211. package/dist/validation/index.js +1 -0
  212. package/dist/validation/types.d.cts +75 -0
  213. package/dist/validation/types.d.ts +75 -0
  214. package/dist/validation/validate.cjs +452 -0
  215. package/dist/validation/validate.cjs.map +1 -0
  216. package/dist/validation/validate.d.cts +7 -0
  217. package/dist/validation/validate.d.ts +6 -0
  218. package/dist/validation/validate.js +450 -0
  219. package/dist/validation/validate.js.map +1 -0
  220. package/package.json +203 -4
@@ -0,0 +1,82 @@
1
+ import { SeoUrl } from "../core/types.js";
2
+ export interface SitemapImage {
3
+ url: SeoUrl | string;
4
+ title?: string;
5
+ caption?: string;
6
+ }
7
+ export interface SitemapVideo {
8
+ thumbnailUrl: string;
9
+ title: string;
10
+ description: string;
11
+ contentUrl?: string;
12
+ playerUrl?: string;
13
+ duration?: number;
14
+ expirationDate?: Date | string;
15
+ rating?: number;
16
+ viewCount?: number;
17
+ publicationDate?: Date | string;
18
+ familyFriendly?: boolean;
19
+ restriction?: {
20
+ relationship: "allow" | "deny";
21
+ countries: string[];
22
+ };
23
+ requiresSubscription?: boolean;
24
+ live?: boolean;
25
+ tags?: string[];
26
+ category?: string;
27
+ }
28
+ export interface SitemapAlternate {
29
+ locale: string;
30
+ url: SeoUrl | string;
31
+ }
32
+ export interface SitemapEntry {
33
+ url: SeoUrl | string;
34
+ lastModified?: Date | string;
35
+ changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
36
+ priority?: number;
37
+ images?: SitemapImage[];
38
+ videos?: SitemapVideo[];
39
+ alternates?: SitemapAlternate[];
40
+ /** Excludes the entry when false. */
41
+ indexable?: boolean;
42
+ /** Only HTTP 200 entries are included. */
43
+ statusCode?: number;
44
+ /** Excludes redirect entries. */
45
+ redirectTo?: string;
46
+ }
47
+ export interface SitemapOptions {
48
+ baseUrl: SeoUrl | string;
49
+ entries: SitemapEntry[];
50
+ includeLastModified?: boolean;
51
+ allowExternalUrls?: boolean;
52
+ }
53
+ export interface SitemapGenerationOptions extends SitemapOptions {
54
+ maxEntries?: number;
55
+ maxBytes?: number;
56
+ filename?: string;
57
+ validateAlternates?: boolean;
58
+ }
59
+ export interface SitemapValidationIssue {
60
+ code: string;
61
+ message: string;
62
+ url?: string;
63
+ path?: string;
64
+ }
65
+ export interface SitemapResult {
66
+ xml: string;
67
+ entries: SitemapEntry[];
68
+ issues: SitemapValidationIssue[];
69
+ }
70
+ export interface SitemapFile {
71
+ filename: string;
72
+ url: SeoUrl;
73
+ xml: string;
74
+ entries: SitemapEntry[];
75
+ }
76
+ export interface SitemapCollection {
77
+ files: SitemapFile[];
78
+ indexXml?: string;
79
+ indexUrl?: SeoUrl;
80
+ totalEntries: number;
81
+ issues: SitemapValidationIssue[];
82
+ }
@@ -0,0 +1,215 @@
1
+ const require_normalize = require("../urls/normalize.cjs");
2
+ // src/sitemap/xml.ts
3
+ const XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
4
+ const SITEMAP_NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
5
+ const IMAGE_NS = "http://www.google.com/schemas/sitemap-image/1.1";
6
+ const VIDEO_NS = "http://www.google.com/schemas/sitemap-video/1.1";
7
+ const XHTML_NS = "http://www.w3.org/1999/xhtml";
8
+ const escapeXml = (value) => value.replace(/[<>&'"]/g, (character) => {
9
+ switch (character) {
10
+ case "<": return "&lt;";
11
+ case ">": return "&gt;";
12
+ case "&": return "&amp;";
13
+ case "'": return "&apos;";
14
+ case "\"": return "&quot;";
15
+ default: return character;
16
+ }
17
+ });
18
+ const formatSitemapDate = (value) => {
19
+ const date = value instanceof Date ? value : new Date(value);
20
+ if (Number.isNaN(date.getTime())) throw new Error(`Invalid sitemap date: "${value}".`);
21
+ return date.toISOString();
22
+ };
23
+ const tag = (name, value) => `<${name}>${escapeXml(value)}</${name}>`;
24
+ const serializeImage = (image) => {
25
+ const elements = [tag("image:loc", image.url)];
26
+ if (image.title) elements.push(tag("image:title", image.title));
27
+ if (image.caption) elements.push(tag("image:caption", image.caption));
28
+ return `<image:image>${elements.join("")}</image:image>`;
29
+ };
30
+ const serializeVideo = (video) => {
31
+ const elements = [
32
+ tag("video:thumbnail_loc", video.thumbnailUrl),
33
+ tag("video:title", video.title),
34
+ tag("video:description", video.description)
35
+ ];
36
+ if (video.contentUrl) elements.push(tag("video:content_loc", video.contentUrl));
37
+ if (video.playerUrl) elements.push(tag("video:player_loc", video.playerUrl));
38
+ if (video.duration !== void 0) elements.push(tag("video:duration", String(video.duration)));
39
+ if (video.expirationDate) elements.push(tag("video:expiration_date", formatSitemapDate(video.expirationDate)));
40
+ if (video.rating !== void 0) elements.push(tag("video:rating", String(video.rating)));
41
+ if (video.viewCount !== void 0) elements.push(tag("video:view_count", String(video.viewCount)));
42
+ if (video.publicationDate) elements.push(tag("video:publication_date", formatSitemapDate(video.publicationDate)));
43
+ if (video.familyFriendly !== void 0) elements.push(tag("video:family_friendly", video.familyFriendly ? "yes" : "no"));
44
+ if (video.restriction) elements.push(`<video:restriction relationship="${video.restriction.relationship}">${escapeXml(video.restriction.countries.join(" "))}</video:restriction>`);
45
+ if (video.requiresSubscription !== void 0) elements.push(tag("video:requires_subscription", video.requiresSubscription ? "yes" : "no"));
46
+ if (video.live !== void 0) elements.push(tag("video:live", video.live ? "yes" : "no"));
47
+ if (video.category) elements.push(tag("video:category", video.category));
48
+ for (const item of video.tags ?? []) elements.push(tag("video:tag", item));
49
+ return `<video:video>${elements.join("")}</video:video>`;
50
+ };
51
+ const serializeAlternate = (alternate) => `<xhtml:link rel="alternate" hreflang="${escapeXml(alternate.locale)}" href="${escapeXml(alternate.url)}" />`;
52
+ const serializeSitemapEntry = (entry, options) => {
53
+ const url = require_normalize.normalizeSeoUrl(entry.url, {
54
+ baseUrl: options.baseUrl,
55
+ allowExternal: options.allowExternalUrls ?? false
56
+ });
57
+ const elements = [tag("loc", url)];
58
+ if (options.includeLastModified !== false && entry.lastModified) elements.push(tag("lastmod", formatSitemapDate(entry.lastModified)));
59
+ if (entry.changeFrequency) elements.push(tag("changefreq", entry.changeFrequency));
60
+ if (entry.priority !== void 0) elements.push(tag("priority", entry.priority.toFixed(1)));
61
+ for (const alternate of entry.alternates ?? []) elements.push(serializeAlternate({
62
+ ...alternate,
63
+ url: require_normalize.normalizeSeoUrl(alternate.url, {
64
+ baseUrl: options.baseUrl,
65
+ allowExternal: options.allowExternalUrls ?? false
66
+ })
67
+ }));
68
+ for (const image of entry.images ?? []) elements.push(serializeImage({
69
+ ...image,
70
+ url: require_normalize.normalizeSeoUrl(image.url, {
71
+ baseUrl: options.baseUrl,
72
+ allowExternal: true
73
+ })
74
+ }));
75
+ for (const video of entry.videos ?? []) elements.push(serializeVideo(video));
76
+ return `<url>${elements.join("")}</url>`;
77
+ };
78
+ const createSitemapXml = (entries, options) => {
79
+ const hasImages = entries.some((entry) => entry.images?.length);
80
+ const hasVideos = entries.some((entry) => entry.videos?.length);
81
+ const hasAlternates = entries.some((entry) => entry.alternates?.length);
82
+ const namespaces = [
83
+ `xmlns="${SITEMAP_NS}"`,
84
+ ...hasImages ? [`xmlns:image="${IMAGE_NS}"`] : [],
85
+ ...hasVideos ? [`xmlns:video="${VIDEO_NS}"`] : [],
86
+ ...hasAlternates ? [`xmlns:xhtml="${XHTML_NS}"`] : []
87
+ ];
88
+ const content = entries.map((entry) => serializeSitemapEntry(entry, options)).join("");
89
+ return [
90
+ XML_HEADER,
91
+ `<urlset ${namespaces.join(" ")}>`,
92
+ content,
93
+ "</urlset>"
94
+ ].join("");
95
+ };
96
+ const sitemapByteLength = (value) => new TextEncoder().encode(value).byteLength;
97
+ const validateSitemapEntries = (entries, options) => {
98
+ const issues = [];
99
+ const urls = /* @__PURE__ */ new Map();
100
+ for (const entry of entries) {
101
+ let url;
102
+ try {
103
+ url = require_normalize.normalizeSeoUrl(entry.url, {
104
+ baseUrl: options.baseUrl,
105
+ allowExternal: options.allowExternalUrls ?? false
106
+ });
107
+ } catch (error) {
108
+ issues.push({
109
+ code: "INVALID_URL",
110
+ message: error instanceof Error ? error.message : "Invalid URL.",
111
+ url: String(entry.url)
112
+ });
113
+ continue;
114
+ }
115
+ if (urls.has(url)) issues.push({
116
+ code: "DUPLICATE_URL",
117
+ message: `Duplicate sitemap URL: ${url}.`,
118
+ url
119
+ });
120
+ else urls.set(url, entry);
121
+ if (entry.priority !== void 0 && (!Number.isFinite(entry.priority) || entry.priority < 0 || entry.priority > 1)) issues.push({
122
+ code: "INVALID_PRIORITY",
123
+ message: "Sitemap priority must be between 0 and 1.",
124
+ url
125
+ });
126
+ if (entry.statusCode !== void 0 && entry.statusCode !== 200) issues.push({
127
+ code: "NON_SUCCESS_STATUS",
128
+ message: "Only HTTP 200 pages belong in a sitemap.",
129
+ url
130
+ });
131
+ for (const video of entry.videos ?? []) {
132
+ if (!video.title.trim() || !video.description.trim()) issues.push({
133
+ code: "INVALID_VIDEO",
134
+ message: "Video title and description are required.",
135
+ url
136
+ });
137
+ if (video.duration !== void 0 && (!Number.isInteger(video.duration) || video.duration < 0)) issues.push({
138
+ code: "INVALID_VIDEO_DURATION",
139
+ message: "Video duration must be a non-negative integer.",
140
+ url
141
+ });
142
+ if (video.rating !== void 0 && (!Number.isFinite(video.rating) || video.rating < 0 || video.rating > 5)) issues.push({
143
+ code: "INVALID_VIDEO_RATING",
144
+ message: "Video rating must be between 0 and 5.",
145
+ url
146
+ });
147
+ }
148
+ if (entry.changeFrequency && ![
149
+ "always",
150
+ "hourly",
151
+ "daily",
152
+ "weekly",
153
+ "monthly",
154
+ "yearly",
155
+ "never"
156
+ ].includes(entry.changeFrequency)) issues.push({
157
+ code: "INVALID_CHANGE_FREQUENCY",
158
+ message: "Invalid sitemap change frequency.",
159
+ url
160
+ });
161
+ if (options.validateAlternates) {
162
+ const locales = /* @__PURE__ */ new Set();
163
+ for (const alternate of entry.alternates ?? []) {
164
+ if (locales.has(alternate.locale)) issues.push({
165
+ code: "DUPLICATE_HREFLANG",
166
+ message: `Duplicate hreflang locale: ${alternate.locale}.`,
167
+ url
168
+ });
169
+ locales.add(alternate.locale);
170
+ if (!alternate.locale.trim()) issues.push({
171
+ code: "INVALID_HREFLANG",
172
+ message: "Hreflang locale must not be empty.",
173
+ url
174
+ });
175
+ }
176
+ }
177
+ }
178
+ if (options.validateAlternates) for (const [url, entry] of urls) for (const alternate of entry.alternates ?? []) {
179
+ let alternateUrl;
180
+ try {
181
+ alternateUrl = require_normalize.normalizeSeoUrl(alternate.url, {
182
+ baseUrl: options.baseUrl,
183
+ allowExternal: options.allowExternalUrls ?? false
184
+ });
185
+ } catch {
186
+ continue;
187
+ }
188
+ const target = urls.get(alternateUrl);
189
+ if (!target) {
190
+ issues.push({
191
+ code: "MISSING_HREFLANG_TARGET",
192
+ message: `Hreflang target is not present in the sitemap: ${alternateUrl}.`,
193
+ url
194
+ });
195
+ continue;
196
+ }
197
+ if (!target.alternates?.some((item) => require_normalize.normalizeSeoUrl(item.url, {
198
+ baseUrl: options.baseUrl,
199
+ allowExternal: options.allowExternalUrls ?? false
200
+ }) === url)) issues.push({
201
+ code: "NON_RECIPROCAL_HREFLANG",
202
+ message: `Hreflang relationship with ${alternateUrl} is not reciprocal.`,
203
+ url
204
+ });
205
+ }
206
+ return issues;
207
+ };
208
+ exports.createSitemapXml = createSitemapXml;
209
+ exports.escapeXml = escapeXml;
210
+ exports.formatSitemapDate = formatSitemapDate;
211
+ exports.serializeSitemapEntry = serializeSitemapEntry;
212
+ exports.sitemapByteLength = sitemapByteLength;
213
+ exports.validateSitemapEntries = validateSitemapEntries;
214
+
215
+ //# sourceMappingURL=xml.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.cjs","names":["normalizeSeoUrl"],"sources":["../../src/sitemap/xml.ts"],"sourcesContent":["import type {\n SitemapEntry,\n SitemapImage,\n SitemapVideo,\n SitemapAlternate,\n SitemapValidationIssue,\n} from \"./types\";\n\nimport { normalizeSeoUrl } from \"../urls\";\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>';\n\nconst SITEMAP_NS = \"http://www.sitemaps.org/schemas/sitemap/0.9\";\nconst IMAGE_NS = \"http://www.google.com/schemas/sitemap-image/1.1\";\nconst VIDEO_NS = \"http://www.google.com/schemas/sitemap-video/1.1\";\nconst XHTML_NS = \"http://www.w3.org/1999/xhtml\";\n\nexport const escapeXml = (value: string): string =>\n value.replace(/[<>&'\"]/g, (character) => {\n switch (character) {\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case \"&\":\n return \"&amp;\";\n case \"'\":\n return \"&apos;\";\n case '\"':\n return \"&quot;\";\n default:\n return character;\n }\n });\n\nexport const formatSitemapDate = (value: Date | string): string => {\n const date = value instanceof Date ? value : new Date(value);\n\n if (Number.isNaN(date.getTime())) {\n throw new Error(`Invalid sitemap date: \"${value}\".`);\n }\n\n return date.toISOString();\n};\n\nconst tag = (name: string, value: string): string =>\n `<${name}>${escapeXml(value)}</${name}>`;\n\nconst serializeImage = (image: SitemapImage): string => {\n const elements = [tag(\"image:loc\", image.url)];\n\n if (image.title) {\n elements.push(tag(\"image:title\", image.title));\n }\n\n if (image.caption) {\n elements.push(tag(\"image:caption\", image.caption));\n }\n\n return `<image:image>${elements.join(\"\")}</image:image>`;\n};\n\nconst serializeVideo = (video: SitemapVideo): string => {\n const elements = [\n tag(\"video:thumbnail_loc\", video.thumbnailUrl),\n tag(\"video:title\", video.title),\n tag(\"video:description\", video.description),\n ];\n\n if (video.contentUrl) {\n elements.push(tag(\"video:content_loc\", video.contentUrl));\n }\n\n if (video.playerUrl) {\n elements.push(tag(\"video:player_loc\", video.playerUrl));\n }\n\n if (video.duration !== undefined) {\n elements.push(tag(\"video:duration\", String(video.duration)));\n }\n\n if (video.expirationDate) {\n elements.push(\n tag(\"video:expiration_date\", formatSitemapDate(video.expirationDate)),\n );\n }\n\n if (video.rating !== undefined) {\n elements.push(tag(\"video:rating\", String(video.rating)));\n }\n\n if (video.viewCount !== undefined) {\n elements.push(tag(\"video:view_count\", String(video.viewCount)));\n }\n\n if (video.publicationDate) {\n elements.push(\n tag(\"video:publication_date\", formatSitemapDate(video.publicationDate)),\n );\n }\n\n if (video.familyFriendly !== undefined) {\n elements.push(\n tag(\"video:family_friendly\", video.familyFriendly ? \"yes\" : \"no\"),\n );\n }\n\n if (video.restriction) {\n elements.push(\n `<video:restriction relationship=\"${video.restriction.relationship}\">${escapeXml(\n video.restriction.countries.join(\" \"),\n )}</video:restriction>`,\n );\n }\n\n if (video.requiresSubscription !== undefined) {\n elements.push(\n tag(\n \"video:requires_subscription\",\n video.requiresSubscription ? \"yes\" : \"no\",\n ),\n );\n }\n\n if (video.live !== undefined) {\n elements.push(tag(\"video:live\", video.live ? \"yes\" : \"no\"));\n }\n\n if (video.category) {\n elements.push(tag(\"video:category\", video.category));\n }\n\n for (const item of video.tags ?? []) {\n elements.push(tag(\"video:tag\", item));\n }\n\n return `<video:video>${elements.join(\"\")}</video:video>`;\n};\n\nconst serializeAlternate = (alternate: SitemapAlternate): string =>\n `<xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(\n alternate.locale,\n )}\" href=\"${escapeXml(alternate.url)}\" />`;\n\nexport interface SerializeSitemapOptions {\n baseUrl: string;\n includeLastModified?: boolean;\n allowExternalUrls?: boolean;\n}\n\nexport const serializeSitemapEntry = (\n entry: SitemapEntry,\n options: SerializeSitemapOptions,\n): string => {\n const url = normalizeSeoUrl(entry.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n });\n\n const elements = [tag(\"loc\", url)];\n\n if (options.includeLastModified !== false && entry.lastModified) {\n elements.push(tag(\"lastmod\", formatSitemapDate(entry.lastModified)));\n }\n\n if (entry.changeFrequency) {\n elements.push(tag(\"changefreq\", entry.changeFrequency));\n }\n\n if (entry.priority !== undefined) {\n elements.push(tag(\"priority\", entry.priority.toFixed(1)));\n }\n\n for (const alternate of entry.alternates ?? []) {\n elements.push(\n serializeAlternate({\n ...alternate,\n url: normalizeSeoUrl(alternate.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n }),\n }),\n );\n }\n\n for (const image of entry.images ?? []) {\n elements.push(\n serializeImage({\n ...image,\n url: normalizeSeoUrl(image.url, {\n baseUrl: options.baseUrl,\n allowExternal: true,\n }),\n }),\n );\n }\n\n for (const video of entry.videos ?? []) {\n elements.push(serializeVideo(video));\n }\n\n return `<url>${elements.join(\"\")}</url>`;\n};\n\nexport const createSitemapXml = (\n entries: readonly SitemapEntry[],\n options: SerializeSitemapOptions,\n): string => {\n const hasImages = entries.some((entry) => entry.images?.length);\n const hasVideos = entries.some((entry) => entry.videos?.length);\n const hasAlternates = entries.some((entry) => entry.alternates?.length);\n\n const namespaces = [\n `xmlns=\"${SITEMAP_NS}\"`,\n ...(hasImages ? [`xmlns:image=\"${IMAGE_NS}\"`] : []),\n ...(hasVideos ? [`xmlns:video=\"${VIDEO_NS}\"`] : []),\n ...(hasAlternates ? [`xmlns:xhtml=\"${XHTML_NS}\"`] : []),\n ];\n\n const content = entries\n .map((entry) => serializeSitemapEntry(entry, options))\n .join(\"\");\n\n return [\n XML_HEADER,\n `<urlset ${namespaces.join(\" \")}>`,\n content,\n \"</urlset>\",\n ].join(\"\");\n};\n\nexport const sitemapByteLength = (value: string): number =>\n new TextEncoder().encode(value).byteLength;\n\nexport const validateSitemapEntries = (\n entries: readonly SitemapEntry[],\n options: {\n baseUrl: string;\n allowExternalUrls?: boolean;\n validateAlternates?: boolean;\n },\n): SitemapValidationIssue[] => {\n const issues: SitemapValidationIssue[] = [];\n const urls = new Map<string, SitemapEntry>();\n\n for (const entry of entries) {\n let url: string;\n\n try {\n url = normalizeSeoUrl(entry.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n });\n } catch (error) {\n issues.push({\n code: \"INVALID_URL\",\n message: error instanceof Error ? error.message : \"Invalid URL.\",\n url: String(entry.url),\n });\n\n continue;\n }\n\n if (urls.has(url)) {\n issues.push({\n code: \"DUPLICATE_URL\",\n message: `Duplicate sitemap URL: ${url}.`,\n url,\n });\n } else {\n urls.set(url, entry);\n }\n\n if (\n entry.priority !== undefined &&\n (!Number.isFinite(entry.priority) ||\n entry.priority < 0 ||\n entry.priority > 1)\n ) {\n issues.push({\n code: \"INVALID_PRIORITY\",\n message: \"Sitemap priority must be between 0 and 1.\",\n url,\n });\n }\n\n if (entry.statusCode !== undefined && entry.statusCode !== 200) {\n issues.push({\n code: \"NON_SUCCESS_STATUS\",\n message: \"Only HTTP 200 pages belong in a sitemap.\",\n url,\n });\n }\n\n for (const video of entry.videos ?? []) {\n if (!video.title.trim() || !video.description.trim()) {\n issues.push({\n code: \"INVALID_VIDEO\",\n message: \"Video title and description are required.\",\n url,\n });\n }\n\n if (\n video.duration !== undefined &&\n (!Number.isInteger(video.duration) || video.duration < 0)\n ) {\n issues.push({\n code: \"INVALID_VIDEO_DURATION\",\n message: \"Video duration must be a non-negative integer.\",\n url,\n });\n }\n\n if (\n video.rating !== undefined &&\n (!Number.isFinite(video.rating) || video.rating < 0 || video.rating > 5)\n ) {\n issues.push({\n code: \"INVALID_VIDEO_RATING\",\n message: \"Video rating must be between 0 and 5.\",\n url,\n });\n }\n }\n\n if (\n entry.changeFrequency &&\n ![\n \"always\",\n \"hourly\",\n \"daily\",\n \"weekly\",\n \"monthly\",\n \"yearly\",\n \"never\",\n ].includes(entry.changeFrequency)\n ) {\n issues.push({\n code: \"INVALID_CHANGE_FREQUENCY\",\n message: \"Invalid sitemap change frequency.\",\n url,\n });\n }\n\n if (options.validateAlternates) {\n const locales = new Set<string>();\n\n for (const alternate of entry.alternates ?? []) {\n if (locales.has(alternate.locale)) {\n issues.push({\n code: \"DUPLICATE_HREFLANG\",\n message: `Duplicate hreflang locale: ${alternate.locale}.`,\n url,\n });\n }\n\n locales.add(alternate.locale);\n\n if (!alternate.locale.trim()) {\n issues.push({\n code: \"INVALID_HREFLANG\",\n message: \"Hreflang locale must not be empty.\",\n url,\n });\n }\n }\n }\n }\n\n if (options.validateAlternates) {\n for (const [url, entry] of urls) {\n for (const alternate of entry.alternates ?? []) {\n let alternateUrl: string;\n\n try {\n alternateUrl = normalizeSeoUrl(alternate.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n });\n } catch {\n continue;\n }\n\n const target = urls.get(alternateUrl);\n\n if (!target) {\n issues.push({\n code: \"MISSING_HREFLANG_TARGET\",\n message: `Hreflang target is not present in the sitemap: ${alternateUrl}.`,\n url,\n });\n\n continue;\n }\n\n const reciprocal = target.alternates?.some(\n (item) =>\n normalizeSeoUrl(item.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n }) === url,\n );\n\n if (!reciprocal) {\n issues.push({\n code: \"NON_RECIPROCAL_HREFLANG\",\n message: `Hreflang relationship with ${alternateUrl} is not reciprocal.`,\n url,\n });\n }\n }\n }\n }\n\n return issues;\n};\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { SitemapEntry, SitemapValidationIssue } from "./types.cjs";
2
+ export declare const escapeXml: (value: string) => string;
3
+ export declare const formatSitemapDate: (value: Date | string) => string;
4
+ export interface SerializeSitemapOptions {
5
+ baseUrl: string;
6
+ includeLastModified?: boolean;
7
+ allowExternalUrls?: boolean;
8
+ }
9
+ export declare const serializeSitemapEntry: (entry: SitemapEntry, options: SerializeSitemapOptions) => string;
10
+ export declare const createSitemapXml: (entries: readonly SitemapEntry[], options: SerializeSitemapOptions) => string;
11
+ export declare const sitemapByteLength: (value: string) => number;
12
+ export declare const validateSitemapEntries: (entries: readonly SitemapEntry[], options: {
13
+ baseUrl: string;
14
+ allowExternalUrls?: boolean;
15
+ validateAlternates?: boolean;
16
+ }) => SitemapValidationIssue[];
@@ -0,0 +1,16 @@
1
+ import { SitemapEntry, SitemapValidationIssue } from "./types.js";
2
+ export declare const escapeXml: (value: string) => string;
3
+ export declare const formatSitemapDate: (value: Date | string) => string;
4
+ export interface SerializeSitemapOptions {
5
+ baseUrl: string;
6
+ includeLastModified?: boolean;
7
+ allowExternalUrls?: boolean;
8
+ }
9
+ export declare const serializeSitemapEntry: (entry: SitemapEntry, options: SerializeSitemapOptions) => string;
10
+ export declare const createSitemapXml: (entries: readonly SitemapEntry[], options: SerializeSitemapOptions) => string;
11
+ export declare const sitemapByteLength: (value: string) => number;
12
+ export declare const validateSitemapEntries: (entries: readonly SitemapEntry[], options: {
13
+ baseUrl: string;
14
+ allowExternalUrls?: boolean;
15
+ validateAlternates?: boolean;
16
+ }) => SitemapValidationIssue[];
@@ -0,0 +1,210 @@
1
+ import { normalizeSeoUrl } from "../urls/normalize.js";
2
+ // src/sitemap/xml.ts
3
+ const XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
4
+ const SITEMAP_NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
5
+ const IMAGE_NS = "http://www.google.com/schemas/sitemap-image/1.1";
6
+ const VIDEO_NS = "http://www.google.com/schemas/sitemap-video/1.1";
7
+ const XHTML_NS = "http://www.w3.org/1999/xhtml";
8
+ const escapeXml = (value) => value.replace(/[<>&'"]/g, (character) => {
9
+ switch (character) {
10
+ case "<": return "&lt;";
11
+ case ">": return "&gt;";
12
+ case "&": return "&amp;";
13
+ case "'": return "&apos;";
14
+ case "\"": return "&quot;";
15
+ default: return character;
16
+ }
17
+ });
18
+ const formatSitemapDate = (value) => {
19
+ const date = value instanceof Date ? value : new Date(value);
20
+ if (Number.isNaN(date.getTime())) throw new Error(`Invalid sitemap date: "${value}".`);
21
+ return date.toISOString();
22
+ };
23
+ const tag = (name, value) => `<${name}>${escapeXml(value)}</${name}>`;
24
+ const serializeImage = (image) => {
25
+ const elements = [tag("image:loc", image.url)];
26
+ if (image.title) elements.push(tag("image:title", image.title));
27
+ if (image.caption) elements.push(tag("image:caption", image.caption));
28
+ return `<image:image>${elements.join("")}</image:image>`;
29
+ };
30
+ const serializeVideo = (video) => {
31
+ const elements = [
32
+ tag("video:thumbnail_loc", video.thumbnailUrl),
33
+ tag("video:title", video.title),
34
+ tag("video:description", video.description)
35
+ ];
36
+ if (video.contentUrl) elements.push(tag("video:content_loc", video.contentUrl));
37
+ if (video.playerUrl) elements.push(tag("video:player_loc", video.playerUrl));
38
+ if (video.duration !== void 0) elements.push(tag("video:duration", String(video.duration)));
39
+ if (video.expirationDate) elements.push(tag("video:expiration_date", formatSitemapDate(video.expirationDate)));
40
+ if (video.rating !== void 0) elements.push(tag("video:rating", String(video.rating)));
41
+ if (video.viewCount !== void 0) elements.push(tag("video:view_count", String(video.viewCount)));
42
+ if (video.publicationDate) elements.push(tag("video:publication_date", formatSitemapDate(video.publicationDate)));
43
+ if (video.familyFriendly !== void 0) elements.push(tag("video:family_friendly", video.familyFriendly ? "yes" : "no"));
44
+ if (video.restriction) elements.push(`<video:restriction relationship="${video.restriction.relationship}">${escapeXml(video.restriction.countries.join(" "))}</video:restriction>`);
45
+ if (video.requiresSubscription !== void 0) elements.push(tag("video:requires_subscription", video.requiresSubscription ? "yes" : "no"));
46
+ if (video.live !== void 0) elements.push(tag("video:live", video.live ? "yes" : "no"));
47
+ if (video.category) elements.push(tag("video:category", video.category));
48
+ for (const item of video.tags ?? []) elements.push(tag("video:tag", item));
49
+ return `<video:video>${elements.join("")}</video:video>`;
50
+ };
51
+ const serializeAlternate = (alternate) => `<xhtml:link rel="alternate" hreflang="${escapeXml(alternate.locale)}" href="${escapeXml(alternate.url)}" />`;
52
+ const serializeSitemapEntry = (entry, options) => {
53
+ const url = normalizeSeoUrl(entry.url, {
54
+ baseUrl: options.baseUrl,
55
+ allowExternal: options.allowExternalUrls ?? false
56
+ });
57
+ const elements = [tag("loc", url)];
58
+ if (options.includeLastModified !== false && entry.lastModified) elements.push(tag("lastmod", formatSitemapDate(entry.lastModified)));
59
+ if (entry.changeFrequency) elements.push(tag("changefreq", entry.changeFrequency));
60
+ if (entry.priority !== void 0) elements.push(tag("priority", entry.priority.toFixed(1)));
61
+ for (const alternate of entry.alternates ?? []) elements.push(serializeAlternate({
62
+ ...alternate,
63
+ url: normalizeSeoUrl(alternate.url, {
64
+ baseUrl: options.baseUrl,
65
+ allowExternal: options.allowExternalUrls ?? false
66
+ })
67
+ }));
68
+ for (const image of entry.images ?? []) elements.push(serializeImage({
69
+ ...image,
70
+ url: normalizeSeoUrl(image.url, {
71
+ baseUrl: options.baseUrl,
72
+ allowExternal: true
73
+ })
74
+ }));
75
+ for (const video of entry.videos ?? []) elements.push(serializeVideo(video));
76
+ return `<url>${elements.join("")}</url>`;
77
+ };
78
+ const createSitemapXml = (entries, options) => {
79
+ const hasImages = entries.some((entry) => entry.images?.length);
80
+ const hasVideos = entries.some((entry) => entry.videos?.length);
81
+ const hasAlternates = entries.some((entry) => entry.alternates?.length);
82
+ const namespaces = [
83
+ `xmlns="${SITEMAP_NS}"`,
84
+ ...hasImages ? [`xmlns:image="${IMAGE_NS}"`] : [],
85
+ ...hasVideos ? [`xmlns:video="${VIDEO_NS}"`] : [],
86
+ ...hasAlternates ? [`xmlns:xhtml="${XHTML_NS}"`] : []
87
+ ];
88
+ const content = entries.map((entry) => serializeSitemapEntry(entry, options)).join("");
89
+ return [
90
+ XML_HEADER,
91
+ `<urlset ${namespaces.join(" ")}>`,
92
+ content,
93
+ "</urlset>"
94
+ ].join("");
95
+ };
96
+ const sitemapByteLength = (value) => new TextEncoder().encode(value).byteLength;
97
+ const validateSitemapEntries = (entries, options) => {
98
+ const issues = [];
99
+ const urls = /* @__PURE__ */ new Map();
100
+ for (const entry of entries) {
101
+ let url;
102
+ try {
103
+ url = normalizeSeoUrl(entry.url, {
104
+ baseUrl: options.baseUrl,
105
+ allowExternal: options.allowExternalUrls ?? false
106
+ });
107
+ } catch (error) {
108
+ issues.push({
109
+ code: "INVALID_URL",
110
+ message: error instanceof Error ? error.message : "Invalid URL.",
111
+ url: String(entry.url)
112
+ });
113
+ continue;
114
+ }
115
+ if (urls.has(url)) issues.push({
116
+ code: "DUPLICATE_URL",
117
+ message: `Duplicate sitemap URL: ${url}.`,
118
+ url
119
+ });
120
+ else urls.set(url, entry);
121
+ if (entry.priority !== void 0 && (!Number.isFinite(entry.priority) || entry.priority < 0 || entry.priority > 1)) issues.push({
122
+ code: "INVALID_PRIORITY",
123
+ message: "Sitemap priority must be between 0 and 1.",
124
+ url
125
+ });
126
+ if (entry.statusCode !== void 0 && entry.statusCode !== 200) issues.push({
127
+ code: "NON_SUCCESS_STATUS",
128
+ message: "Only HTTP 200 pages belong in a sitemap.",
129
+ url
130
+ });
131
+ for (const video of entry.videos ?? []) {
132
+ if (!video.title.trim() || !video.description.trim()) issues.push({
133
+ code: "INVALID_VIDEO",
134
+ message: "Video title and description are required.",
135
+ url
136
+ });
137
+ if (video.duration !== void 0 && (!Number.isInteger(video.duration) || video.duration < 0)) issues.push({
138
+ code: "INVALID_VIDEO_DURATION",
139
+ message: "Video duration must be a non-negative integer.",
140
+ url
141
+ });
142
+ if (video.rating !== void 0 && (!Number.isFinite(video.rating) || video.rating < 0 || video.rating > 5)) issues.push({
143
+ code: "INVALID_VIDEO_RATING",
144
+ message: "Video rating must be between 0 and 5.",
145
+ url
146
+ });
147
+ }
148
+ if (entry.changeFrequency && ![
149
+ "always",
150
+ "hourly",
151
+ "daily",
152
+ "weekly",
153
+ "monthly",
154
+ "yearly",
155
+ "never"
156
+ ].includes(entry.changeFrequency)) issues.push({
157
+ code: "INVALID_CHANGE_FREQUENCY",
158
+ message: "Invalid sitemap change frequency.",
159
+ url
160
+ });
161
+ if (options.validateAlternates) {
162
+ const locales = /* @__PURE__ */ new Set();
163
+ for (const alternate of entry.alternates ?? []) {
164
+ if (locales.has(alternate.locale)) issues.push({
165
+ code: "DUPLICATE_HREFLANG",
166
+ message: `Duplicate hreflang locale: ${alternate.locale}.`,
167
+ url
168
+ });
169
+ locales.add(alternate.locale);
170
+ if (!alternate.locale.trim()) issues.push({
171
+ code: "INVALID_HREFLANG",
172
+ message: "Hreflang locale must not be empty.",
173
+ url
174
+ });
175
+ }
176
+ }
177
+ }
178
+ if (options.validateAlternates) for (const [url, entry] of urls) for (const alternate of entry.alternates ?? []) {
179
+ let alternateUrl;
180
+ try {
181
+ alternateUrl = normalizeSeoUrl(alternate.url, {
182
+ baseUrl: options.baseUrl,
183
+ allowExternal: options.allowExternalUrls ?? false
184
+ });
185
+ } catch {
186
+ continue;
187
+ }
188
+ const target = urls.get(alternateUrl);
189
+ if (!target) {
190
+ issues.push({
191
+ code: "MISSING_HREFLANG_TARGET",
192
+ message: `Hreflang target is not present in the sitemap: ${alternateUrl}.`,
193
+ url
194
+ });
195
+ continue;
196
+ }
197
+ if (!target.alternates?.some((item) => normalizeSeoUrl(item.url, {
198
+ baseUrl: options.baseUrl,
199
+ allowExternal: options.allowExternalUrls ?? false
200
+ }) === url)) issues.push({
201
+ code: "NON_RECIPROCAL_HREFLANG",
202
+ message: `Hreflang relationship with ${alternateUrl} is not reciprocal.`,
203
+ url
204
+ });
205
+ }
206
+ return issues;
207
+ };
208
+ export { createSitemapXml, escapeXml, formatSitemapDate, serializeSitemapEntry, sitemapByteLength, validateSitemapEntries };
209
+
210
+ //# sourceMappingURL=xml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.js","names":[],"sources":["../../src/sitemap/xml.ts"],"sourcesContent":["import type {\n SitemapEntry,\n SitemapImage,\n SitemapVideo,\n SitemapAlternate,\n SitemapValidationIssue,\n} from \"./types\";\n\nimport { normalizeSeoUrl } from \"../urls\";\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>';\n\nconst SITEMAP_NS = \"http://www.sitemaps.org/schemas/sitemap/0.9\";\nconst IMAGE_NS = \"http://www.google.com/schemas/sitemap-image/1.1\";\nconst VIDEO_NS = \"http://www.google.com/schemas/sitemap-video/1.1\";\nconst XHTML_NS = \"http://www.w3.org/1999/xhtml\";\n\nexport const escapeXml = (value: string): string =>\n value.replace(/[<>&'\"]/g, (character) => {\n switch (character) {\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case \"&\":\n return \"&amp;\";\n case \"'\":\n return \"&apos;\";\n case '\"':\n return \"&quot;\";\n default:\n return character;\n }\n });\n\nexport const formatSitemapDate = (value: Date | string): string => {\n const date = value instanceof Date ? value : new Date(value);\n\n if (Number.isNaN(date.getTime())) {\n throw new Error(`Invalid sitemap date: \"${value}\".`);\n }\n\n return date.toISOString();\n};\n\nconst tag = (name: string, value: string): string =>\n `<${name}>${escapeXml(value)}</${name}>`;\n\nconst serializeImage = (image: SitemapImage): string => {\n const elements = [tag(\"image:loc\", image.url)];\n\n if (image.title) {\n elements.push(tag(\"image:title\", image.title));\n }\n\n if (image.caption) {\n elements.push(tag(\"image:caption\", image.caption));\n }\n\n return `<image:image>${elements.join(\"\")}</image:image>`;\n};\n\nconst serializeVideo = (video: SitemapVideo): string => {\n const elements = [\n tag(\"video:thumbnail_loc\", video.thumbnailUrl),\n tag(\"video:title\", video.title),\n tag(\"video:description\", video.description),\n ];\n\n if (video.contentUrl) {\n elements.push(tag(\"video:content_loc\", video.contentUrl));\n }\n\n if (video.playerUrl) {\n elements.push(tag(\"video:player_loc\", video.playerUrl));\n }\n\n if (video.duration !== undefined) {\n elements.push(tag(\"video:duration\", String(video.duration)));\n }\n\n if (video.expirationDate) {\n elements.push(\n tag(\"video:expiration_date\", formatSitemapDate(video.expirationDate)),\n );\n }\n\n if (video.rating !== undefined) {\n elements.push(tag(\"video:rating\", String(video.rating)));\n }\n\n if (video.viewCount !== undefined) {\n elements.push(tag(\"video:view_count\", String(video.viewCount)));\n }\n\n if (video.publicationDate) {\n elements.push(\n tag(\"video:publication_date\", formatSitemapDate(video.publicationDate)),\n );\n }\n\n if (video.familyFriendly !== undefined) {\n elements.push(\n tag(\"video:family_friendly\", video.familyFriendly ? \"yes\" : \"no\"),\n );\n }\n\n if (video.restriction) {\n elements.push(\n `<video:restriction relationship=\"${video.restriction.relationship}\">${escapeXml(\n video.restriction.countries.join(\" \"),\n )}</video:restriction>`,\n );\n }\n\n if (video.requiresSubscription !== undefined) {\n elements.push(\n tag(\n \"video:requires_subscription\",\n video.requiresSubscription ? \"yes\" : \"no\",\n ),\n );\n }\n\n if (video.live !== undefined) {\n elements.push(tag(\"video:live\", video.live ? \"yes\" : \"no\"));\n }\n\n if (video.category) {\n elements.push(tag(\"video:category\", video.category));\n }\n\n for (const item of video.tags ?? []) {\n elements.push(tag(\"video:tag\", item));\n }\n\n return `<video:video>${elements.join(\"\")}</video:video>`;\n};\n\nconst serializeAlternate = (alternate: SitemapAlternate): string =>\n `<xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(\n alternate.locale,\n )}\" href=\"${escapeXml(alternate.url)}\" />`;\n\nexport interface SerializeSitemapOptions {\n baseUrl: string;\n includeLastModified?: boolean;\n allowExternalUrls?: boolean;\n}\n\nexport const serializeSitemapEntry = (\n entry: SitemapEntry,\n options: SerializeSitemapOptions,\n): string => {\n const url = normalizeSeoUrl(entry.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n });\n\n const elements = [tag(\"loc\", url)];\n\n if (options.includeLastModified !== false && entry.lastModified) {\n elements.push(tag(\"lastmod\", formatSitemapDate(entry.lastModified)));\n }\n\n if (entry.changeFrequency) {\n elements.push(tag(\"changefreq\", entry.changeFrequency));\n }\n\n if (entry.priority !== undefined) {\n elements.push(tag(\"priority\", entry.priority.toFixed(1)));\n }\n\n for (const alternate of entry.alternates ?? []) {\n elements.push(\n serializeAlternate({\n ...alternate,\n url: normalizeSeoUrl(alternate.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n }),\n }),\n );\n }\n\n for (const image of entry.images ?? []) {\n elements.push(\n serializeImage({\n ...image,\n url: normalizeSeoUrl(image.url, {\n baseUrl: options.baseUrl,\n allowExternal: true,\n }),\n }),\n );\n }\n\n for (const video of entry.videos ?? []) {\n elements.push(serializeVideo(video));\n }\n\n return `<url>${elements.join(\"\")}</url>`;\n};\n\nexport const createSitemapXml = (\n entries: readonly SitemapEntry[],\n options: SerializeSitemapOptions,\n): string => {\n const hasImages = entries.some((entry) => entry.images?.length);\n const hasVideos = entries.some((entry) => entry.videos?.length);\n const hasAlternates = entries.some((entry) => entry.alternates?.length);\n\n const namespaces = [\n `xmlns=\"${SITEMAP_NS}\"`,\n ...(hasImages ? [`xmlns:image=\"${IMAGE_NS}\"`] : []),\n ...(hasVideos ? [`xmlns:video=\"${VIDEO_NS}\"`] : []),\n ...(hasAlternates ? [`xmlns:xhtml=\"${XHTML_NS}\"`] : []),\n ];\n\n const content = entries\n .map((entry) => serializeSitemapEntry(entry, options))\n .join(\"\");\n\n return [\n XML_HEADER,\n `<urlset ${namespaces.join(\" \")}>`,\n content,\n \"</urlset>\",\n ].join(\"\");\n};\n\nexport const sitemapByteLength = (value: string): number =>\n new TextEncoder().encode(value).byteLength;\n\nexport const validateSitemapEntries = (\n entries: readonly SitemapEntry[],\n options: {\n baseUrl: string;\n allowExternalUrls?: boolean;\n validateAlternates?: boolean;\n },\n): SitemapValidationIssue[] => {\n const issues: SitemapValidationIssue[] = [];\n const urls = new Map<string, SitemapEntry>();\n\n for (const entry of entries) {\n let url: string;\n\n try {\n url = normalizeSeoUrl(entry.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n });\n } catch (error) {\n issues.push({\n code: \"INVALID_URL\",\n message: error instanceof Error ? error.message : \"Invalid URL.\",\n url: String(entry.url),\n });\n\n continue;\n }\n\n if (urls.has(url)) {\n issues.push({\n code: \"DUPLICATE_URL\",\n message: `Duplicate sitemap URL: ${url}.`,\n url,\n });\n } else {\n urls.set(url, entry);\n }\n\n if (\n entry.priority !== undefined &&\n (!Number.isFinite(entry.priority) ||\n entry.priority < 0 ||\n entry.priority > 1)\n ) {\n issues.push({\n code: \"INVALID_PRIORITY\",\n message: \"Sitemap priority must be between 0 and 1.\",\n url,\n });\n }\n\n if (entry.statusCode !== undefined && entry.statusCode !== 200) {\n issues.push({\n code: \"NON_SUCCESS_STATUS\",\n message: \"Only HTTP 200 pages belong in a sitemap.\",\n url,\n });\n }\n\n for (const video of entry.videos ?? []) {\n if (!video.title.trim() || !video.description.trim()) {\n issues.push({\n code: \"INVALID_VIDEO\",\n message: \"Video title and description are required.\",\n url,\n });\n }\n\n if (\n video.duration !== undefined &&\n (!Number.isInteger(video.duration) || video.duration < 0)\n ) {\n issues.push({\n code: \"INVALID_VIDEO_DURATION\",\n message: \"Video duration must be a non-negative integer.\",\n url,\n });\n }\n\n if (\n video.rating !== undefined &&\n (!Number.isFinite(video.rating) || video.rating < 0 || video.rating > 5)\n ) {\n issues.push({\n code: \"INVALID_VIDEO_RATING\",\n message: \"Video rating must be between 0 and 5.\",\n url,\n });\n }\n }\n\n if (\n entry.changeFrequency &&\n ![\n \"always\",\n \"hourly\",\n \"daily\",\n \"weekly\",\n \"monthly\",\n \"yearly\",\n \"never\",\n ].includes(entry.changeFrequency)\n ) {\n issues.push({\n code: \"INVALID_CHANGE_FREQUENCY\",\n message: \"Invalid sitemap change frequency.\",\n url,\n });\n }\n\n if (options.validateAlternates) {\n const locales = new Set<string>();\n\n for (const alternate of entry.alternates ?? []) {\n if (locales.has(alternate.locale)) {\n issues.push({\n code: \"DUPLICATE_HREFLANG\",\n message: `Duplicate hreflang locale: ${alternate.locale}.`,\n url,\n });\n }\n\n locales.add(alternate.locale);\n\n if (!alternate.locale.trim()) {\n issues.push({\n code: \"INVALID_HREFLANG\",\n message: \"Hreflang locale must not be empty.\",\n url,\n });\n }\n }\n }\n }\n\n if (options.validateAlternates) {\n for (const [url, entry] of urls) {\n for (const alternate of entry.alternates ?? []) {\n let alternateUrl: string;\n\n try {\n alternateUrl = normalizeSeoUrl(alternate.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n });\n } catch {\n continue;\n }\n\n const target = urls.get(alternateUrl);\n\n if (!target) {\n issues.push({\n code: \"MISSING_HREFLANG_TARGET\",\n message: `Hreflang target is not present in the sitemap: ${alternateUrl}.`,\n url,\n });\n\n continue;\n }\n\n const reciprocal = target.alternates?.some(\n (item) =>\n normalizeSeoUrl(item.url, {\n baseUrl: options.baseUrl,\n allowExternal: options.allowExternalUrls ?? false,\n }) === url,\n );\n\n if (!reciprocal) {\n issues.push({\n code: \"NON_RECIPROCAL_HREFLANG\",\n message: `Hreflang relationship with ${alternateUrl} is not reciprocal.`,\n url,\n });\n }\n }\n }\n }\n\n return issues;\n};\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_routes = require("../server/routes.cjs");
3
+ // src/tanstack/index.ts
4
+ const createTanStackSeoRoutes = (options) => {
5
+ const handlers = require_routes.createSeoRouteHandlers(options);
6
+ return {
7
+ sitemapGET: ({ request }) => handlers.sitemap(request),
8
+ robotsGET: ({ request }) => handlers.robots(request)
9
+ };
10
+ };
11
+ exports.createTanStackSeoRoutes = createTanStackSeoRoutes;
12
+
13
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["createSeoRouteHandlers"],"sources":["../../src/tanstack/index.ts"],"sourcesContent":["import { createSeoRouteHandlers } from \"../server\";\n\nimport type { SeoRouteOptions } from \"../server\";\n\nexport interface TanStackServerRouteContext {\n request: Request;\n}\n\nexport type TanStackSeoRouteHandler = (\n context: TanStackServerRouteContext,\n) => Promise<Response>;\n\nexport interface TanStackSeoRoutes {\n /**\n * Mount on a dynamic root route, e.g. /$seoFile.\n *\n * Handles the sitemap index and every generated sitemap file.\n */\n sitemapGET: TanStackSeoRouteHandler;\n\n /**\n * Mount on the static /robots.txt route.\n */\n robotsGET: TanStackSeoRouteHandler;\n}\n\nexport const createTanStackSeoRoutes = (\n options: SeoRouteOptions,\n): TanStackSeoRoutes => {\n const handlers = createSeoRouteHandlers(options);\n\n return {\n sitemapGET: ({ request }) => handlers.sitemap(request),\n robotsGET: ({ request }) => handlers.robots(request),\n };\n};\n"],"mappings":";;;AA0BA,MAAa,2BACX,YACsB;CACtB,MAAM,WAAWA,eAAAA,uBAAuB,OAAO;CAE/C,OAAO;EACL,aAAa,EAAE,cAAc,SAAS,QAAQ,OAAO;EACrD,YAAY,EAAE,cAAc,SAAS,OAAO,OAAO;CACrD;AACF"}
@@ -0,0 +1,19 @@
1
+ import { SeoRouteOptions } from "../server/types.cjs";
2
+ import "../server/index.cjs";
3
+ export interface TanStackServerRouteContext {
4
+ request: Request;
5
+ }
6
+ export type TanStackSeoRouteHandler = (context: TanStackServerRouteContext) => Promise<Response>;
7
+ export interface TanStackSeoRoutes {
8
+ /**
9
+ * Mount on a dynamic root route, e.g. /$seoFile.
10
+ *
11
+ * Handles the sitemap index and every generated sitemap file.
12
+ */
13
+ sitemapGET: TanStackSeoRouteHandler;
14
+ /**
15
+ * Mount on the static /robots.txt route.
16
+ */
17
+ robotsGET: TanStackSeoRouteHandler;
18
+ }
19
+ export declare const createTanStackSeoRoutes: (options: SeoRouteOptions) => TanStackSeoRoutes;