@pas7/nextjs-sitemap-hreflang 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,576 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import fs from "fs";
5
+ import path from "path";
6
+
7
+ // src/lib/url.ts
8
+ function resolveAbsoluteUrl(input, baseUrl) {
9
+ if (input.startsWith("http://") || input.startsWith("https://")) return input;
10
+ return new URL(input.startsWith("/") ? input : `/${input}`, baseUrl).toString();
11
+ }
12
+ function getOriginFromAbsoluteUrl(absoluteUrl) {
13
+ const u = new URL(absoluteUrl);
14
+ return `${u.protocol}//${u.host}`;
15
+ }
16
+
17
+ // src/xml/xml.ts
18
+ function xmlEscape(input) {
19
+ return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
20
+ }
21
+ function hasXhtmlNamespace(xml) {
22
+ return /<urlset[^>]*\sxmlns:xhtml="http:\/\/www\.w3\.org\/1999\/xhtml"[^>]*>/.test(xml);
23
+ }
24
+ function ensureXhtmlNamespace(xml) {
25
+ if (hasXhtmlNamespace(xml)) return xml;
26
+ return xml.replace(
27
+ /<urlset(\s[^>]*?)?>/m,
28
+ (m) => m.includes("xmlns:xhtml=") ? m : m.replace("<urlset", '<urlset xmlns:xhtml="http://www.w3.org/1999/xhtml"')
29
+ );
30
+ }
31
+ function extractUrlBlocks(xml) {
32
+ return xml.match(/<url>[\s\S]*?<\/url>/g) ?? [];
33
+ }
34
+ function extractLoc(urlBlock) {
35
+ return urlBlock.match(/<loc>([^<]+)<\/loc>/)?.[1]?.trim() ?? null;
36
+ }
37
+ function extractXhtmlLinks(urlBlock) {
38
+ const out = [];
39
+ const re = /<xhtml:link[^>]*\shreflang="([^"]+)"[^>]*\shref="([^"]+)"[^>]*\/>/g;
40
+ for (const m of urlBlock.matchAll(re)) {
41
+ if (m[1] !== void 0 && m[2] !== void 0) {
42
+ out.push({ hreflang: m[1], href: m[2] });
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+ function hasXDefault(urlBlock) {
48
+ return /<xhtml:link[^>]*\shreflang="x-default"[^>]*\/>/.test(urlBlock);
49
+ }
50
+ function insertXhtmlLink(urlBlock, linkXml) {
51
+ const lastLinkIdx = urlBlock.lastIndexOf("<xhtml:link");
52
+ if (lastLinkIdx === -1) {
53
+ const locEnd = urlBlock.indexOf("</loc>");
54
+ if (locEnd === -1) return urlBlock;
55
+ const insertPos = locEnd + "</loc>".length;
56
+ return `${urlBlock.slice(0, insertPos)}
57
+ ${linkXml}${urlBlock.slice(insertPos)}`;
58
+ }
59
+ const lineEnd = urlBlock.indexOf("\n", lastLinkIdx);
60
+ if (lineEnd === -1) return `${urlBlock}
61
+ ${linkXml}`;
62
+ return `${urlBlock.slice(0, lineEnd)}
63
+ ${linkXml}${urlBlock.slice(lineEnd)}`;
64
+ }
65
+ function reorderXhtmlLinks(urlBlock, options) {
66
+ if (options.order === "preserve") return urlBlock;
67
+ const links = extractXhtmlLinks(urlBlock);
68
+ if (links.length === 0) return urlBlock;
69
+ const loc = extractLoc(urlBlock);
70
+ let canonical;
71
+ if (options.canonicalLocale) {
72
+ canonical = links.find((l) => l.hreflang === options.canonicalLocale);
73
+ }
74
+ if (!canonical && loc) {
75
+ canonical = links.find((l) => l.href === loc);
76
+ }
77
+ const canonicalLinks = canonical ? [canonical] : [];
78
+ const otherLinks = links.filter((l) => l !== canonical && l.hreflang !== "x-default");
79
+ const xDefaultLinks = links.filter((l) => l.hreflang === "x-default");
80
+ const orderedLinks = [...canonicalLinks, ...otherLinks, ...xDefaultLinks];
81
+ const newBlock = urlBlock.replace(/<xhtml:link[^>]*\/>\s*/g, "");
82
+ const locEnd = newBlock.indexOf("</loc>");
83
+ if (locEnd === -1) return urlBlock;
84
+ const insertPos = locEnd + "</loc>".length;
85
+ const linksXml = orderedLinks.map((l) => `
86
+ <xhtml:link rel="alternate" hreflang="${xmlEscape(l.hreflang)}" href="${xmlEscape(l.href)}" />`).join("");
87
+ return `${newBlock.slice(0, insertPos)}${linksXml}
88
+ ${newBlock.slice(insertPos).trimStart()}`;
89
+ }
90
+ function normalizeTrailingSlashInBlock(urlBlock, policy) {
91
+ if (policy === "preserve") return urlBlock;
92
+ let result = urlBlock.replace(/<loc>([^<]+)<\/loc>/g, (_, url) => {
93
+ return `<loc>${applyTrailingSlashPolicy(url, policy)}</loc>`;
94
+ });
95
+ result = result.replace(
96
+ /(<xhtml:link[^>]*href=")([^"]+)("[^>]*\/>)/g,
97
+ (_, prefix, url, suffix) => {
98
+ return `${prefix}${applyTrailingSlashPolicy(url, policy)}${suffix}`;
99
+ }
100
+ );
101
+ return result;
102
+ }
103
+ function applyTrailingSlashPolicy(url, policy) {
104
+ try {
105
+ const u = new URL(url);
106
+ if (policy === "always" && !u.pathname.endsWith("/")) {
107
+ u.pathname += "/";
108
+ } else if (policy === "never" && u.pathname !== "/" && u.pathname.endsWith("/")) {
109
+ u.pathname = u.pathname.slice(0, -1);
110
+ }
111
+ return u.toString();
112
+ } catch {
113
+ return url;
114
+ }
115
+ }
116
+
117
+ // src/xml/inject.ts
118
+ function injectXDefaultIntoSitemapXml(xml, options) {
119
+ const ensureNamespace = options.ensureNamespace ?? true;
120
+ const xDefaultStrategy = options.xDefaultStrategy ?? { type: "loc" };
121
+ const order = options.order ?? "preserve";
122
+ const trailingSlash = options.trailingSlash ?? "preserve";
123
+ const xmlWithNs = ensureNamespace ? ensureXhtmlNamespace(xml) : xml;
124
+ const blocks = extractUrlBlocks(xmlWithNs);
125
+ if (blocks.length === 0) return xmlWithNs;
126
+ let out = xmlWithNs;
127
+ for (const block of blocks) {
128
+ const loc = extractLoc(block);
129
+ if (!loc) continue;
130
+ const links = extractXhtmlLinks(block);
131
+ if (links.length === 0) continue;
132
+ let nextBlock = block;
133
+ if (!hasXDefault(block)) {
134
+ const href = resolveXDefaultHref({
135
+ loc,
136
+ links,
137
+ ...options.baseUrl ? { baseUrl: options.baseUrl } : {},
138
+ strategy: xDefaultStrategy
139
+ });
140
+ const linkXml = `<xhtml:link rel="alternate" hreflang="x-default" href="${xmlEscape(href)}" />`;
141
+ nextBlock = insertXhtmlLink(nextBlock, linkXml);
142
+ }
143
+ if (order === "canonical-first") {
144
+ nextBlock = reorderXhtmlLinks(nextBlock, {
145
+ ...options.canonicalLocale ? { canonicalLocale: options.canonicalLocale } : {},
146
+ order: "canonical-first"
147
+ });
148
+ }
149
+ if (trailingSlash !== "preserve") {
150
+ nextBlock = normalizeTrailingSlashInBlock(nextBlock, trailingSlash);
151
+ }
152
+ out = out.replace(block, nextBlock);
153
+ }
154
+ return out;
155
+ }
156
+ function resolveXDefaultHref(args) {
157
+ const { loc, links, baseUrl, strategy } = args;
158
+ const locAbs = baseUrl ? resolveAbsoluteUrl(loc, baseUrl) : loc;
159
+ const origin = isAbsolute(locAbs) ? getOriginFromAbsoluteUrl(locAbs) : baseUrl;
160
+ if (strategy.type === "loc") return locAbs;
161
+ if (strategy.type === "root") {
162
+ if (!origin) return locAbs;
163
+ return resolveAbsoluteUrl("/", origin);
164
+ }
165
+ if (strategy.type === "custom") {
166
+ if (!origin) return strategy.url;
167
+ return resolveAbsoluteUrl(strategy.url, origin);
168
+ }
169
+ if (strategy.type === "locale") {
170
+ const found = links.find((l) => l.hreflang === strategy.locale)?.href;
171
+ if (found) return baseUrl ? resolveAbsoluteUrl(found, baseUrl) : found;
172
+ return locAbs;
173
+ }
174
+ const computed = strategy.resolve({ url: locAbs });
175
+ if (!origin) return computed;
176
+ return resolveAbsoluteUrl(computed, origin);
177
+ }
178
+ function isAbsolute(u) {
179
+ return u.startsWith("http://") || u.startsWith("https://");
180
+ }
181
+
182
+ // src/xml/check.ts
183
+ function checkSitemapXmlHreflang(xml, options) {
184
+ const requireNamespace = options.requireNamespace ?? true;
185
+ const requireXDefaultWhenMultiple = options.requireXDefaultWhenMultiple ?? true;
186
+ const requireAbsolute = options.requireAbsolute ?? true;
187
+ const checkDuplicateKeys = options.checkDuplicateKeys ?? true;
188
+ const checkDuplicateHrefs = options.checkDuplicateHrefs ?? true;
189
+ const checkHreflangCasing = options.checkHreflangCasing ?? true;
190
+ const originPolicy = options.originPolicy ?? "off";
191
+ const issues = [];
192
+ const blocks = extractUrlBlocks(xml);
193
+ if (requireNamespace) {
194
+ const usesXhtml = /<xhtml:link\b/.test(xml);
195
+ if (usesXhtml && !hasXhtmlNamespace(xml)) {
196
+ issues.push({
197
+ code: "MISSING_LANGUAGES",
198
+ entryUrl: "sitemap.xml",
199
+ message: 'Missing xmlns:xhtml="http://www.w3.org/1999/xhtml" in <urlset>'
200
+ });
201
+ }
202
+ }
203
+ for (const block of blocks) {
204
+ const loc = extractLoc(block);
205
+ if (!loc) continue;
206
+ const links = extractXhtmlLinks(block);
207
+ if (links.length === 0) continue;
208
+ const hasXDefault2 = links.some((l) => l.hreflang === "x-default");
209
+ if (requireXDefaultWhenMultiple && links.length > 1 && !hasXDefault2) {
210
+ issues.push({
211
+ code: "MISSING_XDEFAULT",
212
+ entryUrl: loc,
213
+ message: "Missing x-default hreflang in sitemap url block"
214
+ });
215
+ }
216
+ if (checkDuplicateKeys) {
217
+ const seenKeys = /* @__PURE__ */ new Set();
218
+ for (const link of links) {
219
+ if (seenKeys.has(link.hreflang)) {
220
+ issues.push({
221
+ code: "DUPLICATE_HREFLANG_KEY",
222
+ entryUrl: loc,
223
+ message: `Duplicate hreflang key: ${link.hreflang}`
224
+ });
225
+ }
226
+ seenKeys.add(link.hreflang);
227
+ }
228
+ }
229
+ if (checkDuplicateHrefs) {
230
+ const hrefToLocales = /* @__PURE__ */ new Map();
231
+ for (const link of links) {
232
+ const locales = hrefToLocales.get(link.href) ?? [];
233
+ locales.push(link.hreflang);
234
+ hrefToLocales.set(link.href, locales);
235
+ }
236
+ for (const [href, locales] of hrefToLocales) {
237
+ const nonXDefaultLocales = locales.filter((l) => l !== "x-default");
238
+ if (nonXDefaultLocales.length > 1) {
239
+ issues.push({
240
+ code: "DUPLICATE_HREF",
241
+ entryUrl: loc,
242
+ message: `Duplicate hreflang href detected: ${href} (locales: ${nonXDefaultLocales.join(", ")})`
243
+ });
244
+ }
245
+ }
246
+ }
247
+ if (checkHreflangCasing) {
248
+ for (const link of links) {
249
+ if (!isValidHreflangCasing(link.hreflang)) {
250
+ issues.push({
251
+ code: "INVALID_HREFLANG_CASING",
252
+ entryUrl: loc,
253
+ message: `Invalid hreflang casing: ${link.hreflang}. Expected format: en, pt-BR, or x-default`
254
+ });
255
+ }
256
+ }
257
+ }
258
+ if (originPolicy === "same") {
259
+ const locOrigin = getOrigin(loc);
260
+ if (locOrigin) {
261
+ for (const link of links) {
262
+ const linkOrigin = getOrigin(link.href);
263
+ if (linkOrigin && linkOrigin !== locOrigin) {
264
+ issues.push({
265
+ code: "INCONSISTENT_ORIGIN",
266
+ entryUrl: loc,
267
+ message: `Inconsistent origin for ${link.hreflang}: expected ${locOrigin}, got ${linkOrigin}`
268
+ });
269
+ }
270
+ }
271
+ }
272
+ } else if (originPolicy === "allowlist") {
273
+ const allowedOrigins = options.allowedOrigins ?? [];
274
+ for (const link of links) {
275
+ const linkOrigin = getOrigin(link.href);
276
+ if (linkOrigin && !allowedOrigins.includes(linkOrigin)) {
277
+ issues.push({
278
+ code: "INCONSISTENT_ORIGIN",
279
+ entryUrl: loc,
280
+ message: `Origin not in allowlist for ${link.hreflang}: ${linkOrigin}`
281
+ });
282
+ }
283
+ }
284
+ const locOrigin = getOrigin(loc);
285
+ if (locOrigin && !allowedOrigins.includes(locOrigin)) {
286
+ issues.push({
287
+ code: "INCONSISTENT_ORIGIN",
288
+ entryUrl: loc,
289
+ message: `Origin not in allowlist for loc: ${locOrigin}`
290
+ });
291
+ }
292
+ }
293
+ if (requireAbsolute) {
294
+ for (const link of links) {
295
+ if (!isAbsolute2(link.href)) {
296
+ issues.push({
297
+ code: "NON_ABSOLUTE_URL",
298
+ entryUrl: loc,
299
+ message: `Non-absolute hreflang href for ${link.hreflang}: ${link.href}`
300
+ });
301
+ }
302
+ }
303
+ if (!isAbsolute2(loc)) {
304
+ issues.push({
305
+ code: "NON_ABSOLUTE_URL",
306
+ entryUrl: loc,
307
+ message: `Non-absolute <loc>: ${loc}`
308
+ });
309
+ }
310
+ }
311
+ }
312
+ return { ok: issues.length === 0, issues };
313
+ }
314
+ function isAbsolute2(u) {
315
+ return u.startsWith("http://") || u.startsWith("https://");
316
+ }
317
+ function getOrigin(url) {
318
+ try {
319
+ const u = new URL(url);
320
+ return u.origin;
321
+ } catch {
322
+ return null;
323
+ }
324
+ }
325
+ function isValidHreflangCasing(key) {
326
+ if (key === "x-default") return true;
327
+ if (/^[a-z]{2}$/.test(key)) return true;
328
+ if (/^[a-z]{2}-[A-Z]{2}$/.test(key)) return true;
329
+ return false;
330
+ }
331
+
332
+ // src/cli.ts
333
+ function parseArgs(argv) {
334
+ const args = {
335
+ command: null,
336
+ inPath: null,
337
+ outPath: null,
338
+ baseUrl: null,
339
+ xDefault: null,
340
+ ensureNamespace: true,
341
+ json: false,
342
+ failOnMissing: false,
343
+ canonicalLocale: null,
344
+ order: "preserve",
345
+ trailingSlash: "preserve",
346
+ checkDuplicateKeys: true,
347
+ checkDuplicateHrefs: true,
348
+ checkHreflangCasing: true,
349
+ originPolicy: "off",
350
+ allowedOrigins: [],
351
+ expandLocales: false
352
+ };
353
+ const [cmd, ...rest] = argv;
354
+ if (cmd === "inject" || cmd === "check" || cmd === "transform") args.command = cmd;
355
+ for (let i = 0; i < rest.length; i += 1) {
356
+ const a = rest[i];
357
+ const v = rest[i + 1];
358
+ if (a === "--in" && v) {
359
+ args.inPath = v;
360
+ i += 1;
361
+ continue;
362
+ }
363
+ if (a === "--out" && v) {
364
+ args.outPath = v;
365
+ i += 1;
366
+ continue;
367
+ }
368
+ if (a === "--base-url" && v) {
369
+ args.baseUrl = v;
370
+ i += 1;
371
+ continue;
372
+ }
373
+ if (a === "--x-default" && v) {
374
+ args.xDefault = v;
375
+ i += 1;
376
+ continue;
377
+ }
378
+ if (a === "--no-ensure-namespace") {
379
+ args.ensureNamespace = false;
380
+ continue;
381
+ }
382
+ if (a === "--json") {
383
+ args.json = true;
384
+ continue;
385
+ }
386
+ if (a === "--fail-on-missing") {
387
+ args.failOnMissing = true;
388
+ continue;
389
+ }
390
+ if (a === "--canonical-locale" && v) {
391
+ args.canonicalLocale = v;
392
+ i += 1;
393
+ continue;
394
+ }
395
+ if (a === "--order" && v) {
396
+ if (v === "canonical-first" || v === "preserve") {
397
+ args.order = v;
398
+ }
399
+ i += 1;
400
+ continue;
401
+ }
402
+ if (a === "--trailing-slash" && v) {
403
+ if (v === "preserve" || v === "always" || v === "never") {
404
+ args.trailingSlash = v;
405
+ }
406
+ i += 1;
407
+ continue;
408
+ }
409
+ if (a === "--no-check-duplicate-keys") {
410
+ args.checkDuplicateKeys = false;
411
+ continue;
412
+ }
413
+ if (a === "--no-check-duplicate-hrefs") {
414
+ args.checkDuplicateHrefs = false;
415
+ continue;
416
+ }
417
+ if (a === "--no-check-hreflang-casing") {
418
+ args.checkHreflangCasing = false;
419
+ continue;
420
+ }
421
+ if (a === "--origin-policy" && v) {
422
+ if (v === "same" || v === "allowlist" || v === "off") {
423
+ args.originPolicy = v;
424
+ }
425
+ i += 1;
426
+ continue;
427
+ }
428
+ if (a === "--allowed-origins" && v) {
429
+ args.allowedOrigins = v.split(",").map((s) => s.trim());
430
+ i += 1;
431
+ continue;
432
+ }
433
+ if (a === "--expand-locales") {
434
+ args.expandLocales = true;
435
+ continue;
436
+ }
437
+ if (a === "--help") {
438
+ printHelp();
439
+ process.exit(0);
440
+ }
441
+ }
442
+ return args;
443
+ }
444
+ function printHelp() {
445
+ const text = [
446
+ "nextjs-sitemap-hreflang",
447
+ "",
448
+ "Commands:",
449
+ " inject --in <path> [--out <path>] [options]",
450
+ " check --in <path> [--json] [--fail-on-missing] [options]",
451
+ " transform --in <path> --out <path> [options]",
452
+ "",
453
+ "Inject options:",
454
+ " --x-default loc|root|locale:en|custom:https://example.com/path",
455
+ " --base-url https://example.com",
456
+ " --canonical-locale <locale> Canonical locale for ordering",
457
+ " --order canonical-first|preserve",
458
+ " --trailing-slash preserve|always|never",
459
+ " --no-ensure-namespace",
460
+ "",
461
+ "Check options:",
462
+ " --no-check-duplicate-keys Disable duplicate key check",
463
+ " --no-check-duplicate-hrefs Disable duplicate href check",
464
+ " --no-check-hreflang-casing Disable hreflang casing check",
465
+ " --origin-policy same|allowlist|off",
466
+ " --allowed-origins <comma-separated>",
467
+ "",
468
+ "Transform options:",
469
+ " --expand-locales Expand locale entries",
470
+ " --trailing-slash preserve|always|never",
471
+ " --order canonical-first|preserve",
472
+ " --canonical-locale <locale>",
473
+ ""
474
+ ].join("\n");
475
+ process.stdout.write(text);
476
+ }
477
+ function parseXDefaultStrategy(raw) {
478
+ if (!raw) return void 0;
479
+ if (raw === "loc") return { type: "loc" };
480
+ if (raw === "root") return { type: "root" };
481
+ if (raw.startsWith("locale:")) return { type: "locale", locale: raw.slice("locale:".length) };
482
+ if (raw.startsWith("custom:")) return { type: "custom", url: raw.slice("custom:".length) };
483
+ return void 0;
484
+ }
485
+ function readFileUtf8(p) {
486
+ return fs.readFileSync(p, "utf8");
487
+ }
488
+ function writeFileUtf8(p, content) {
489
+ fs.mkdirSync(path.dirname(p), { recursive: true });
490
+ fs.writeFileSync(p, content, "utf8");
491
+ }
492
+ async function main() {
493
+ const args = parseArgs(process.argv.slice(2));
494
+ if (!args.command) {
495
+ printHelp();
496
+ process.exit(1);
497
+ }
498
+ if (!args.inPath) {
499
+ process.stderr.write("Missing --in\n");
500
+ process.exit(1);
501
+ }
502
+ const xml = readFileUtf8(args.inPath);
503
+ if (args.command === "inject") {
504
+ const strategy = parseXDefaultStrategy(args.xDefault) ?? { type: "loc" };
505
+ const next = injectXDefaultIntoSitemapXml(xml, {
506
+ ...args.baseUrl ? { baseUrl: args.baseUrl } : {},
507
+ xDefaultStrategy: strategy,
508
+ ensureNamespace: args.ensureNamespace,
509
+ ...args.canonicalLocale ? { canonicalLocale: args.canonicalLocale } : {},
510
+ order: args.order,
511
+ trailingSlash: args.trailingSlash
512
+ });
513
+ const outPath = args.outPath ?? args.inPath;
514
+ writeFileUtf8(outPath, next);
515
+ process.stdout.write(`ok: injected
516
+ `);
517
+ return;
518
+ }
519
+ if (args.command === "transform") {
520
+ let result = args.ensureNamespace ? ensureXhtmlNamespace(xml) : xml;
521
+ if (args.trailingSlash !== "preserve") {
522
+ const blocks = extractUrlBlocks(result);
523
+ for (const block of blocks) {
524
+ const transformed = normalizeTrailingSlashInBlock(block, args.trailingSlash);
525
+ result = result.replace(block, transformed);
526
+ }
527
+ }
528
+ if (args.order === "canonical-first") {
529
+ const blocks = extractUrlBlocks(result);
530
+ for (const block of blocks) {
531
+ const transformed = reorderXhtmlLinks(block, {
532
+ ...args.canonicalLocale ? { canonicalLocale: args.canonicalLocale } : {},
533
+ order: "canonical-first"
534
+ });
535
+ result = result.replace(block, transformed);
536
+ }
537
+ }
538
+ const outPath = args.outPath;
539
+ if (!outPath) {
540
+ process.stderr.write("Missing --out for transform\n");
541
+ process.exit(1);
542
+ }
543
+ writeFileUtf8(outPath, result);
544
+ process.stdout.write(`ok: transformed
545
+ `);
546
+ return;
547
+ }
548
+ const report = checkSitemapXmlHreflang(xml, {
549
+ requireNamespace: true,
550
+ requireAbsolute: true,
551
+ requireXDefaultWhenMultiple: true,
552
+ checkDuplicateKeys: args.checkDuplicateKeys,
553
+ checkDuplicateHrefs: args.checkDuplicateHrefs,
554
+ checkHreflangCasing: args.checkHreflangCasing,
555
+ originPolicy: args.originPolicy,
556
+ allowedOrigins: args.allowedOrigins
557
+ });
558
+ if (args.json) {
559
+ process.stdout.write(JSON.stringify(report, null, 2));
560
+ process.stdout.write("\n");
561
+ } else {
562
+ if (report.ok) {
563
+ process.stdout.write("ok: sitemap hreflang check passed\n");
564
+ } else {
565
+ process.stdout.write(`fail: ${report.issues.length} issue(s)
566
+ `);
567
+ for (const issue of report.issues) {
568
+ process.stdout.write(`- ${issue.code} ${issue.entryUrl}: ${issue.message}
569
+ `);
570
+ }
571
+ }
572
+ }
573
+ if (!report.ok && args.failOnMissing) process.exit(1);
574
+ }
575
+ void main();
576
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/lib/url.ts","../src/xml/xml.ts","../src/xml/inject.ts","../src/xml/check.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { XDefaultStrategy } from \"./lib/types.js\";\nimport { injectXDefaultIntoSitemapXml } from \"./xml/inject.js\";\nimport { checkSitemapXmlHreflang } from \"./xml/check.js\";\nimport {\n ensureXhtmlNamespace,\n extractUrlBlocks,\n normalizeTrailingSlashInBlock,\n reorderXhtmlLinks,\n} from \"./xml/xml.js\";\n\ntype Args = {\n command: \"inject\" | \"check\" | \"transform\" | null;\n inPath: string | null;\n outPath: string | null;\n baseUrl: string | null;\n xDefault: string | null;\n ensureNamespace: boolean;\n json: boolean;\n failOnMissing: boolean;\n // Inject options\n canonicalLocale: string | null;\n order: \"canonical-first\" | \"preserve\";\n trailingSlash: \"preserve\" | \"always\" | \"never\";\n // Check options\n checkDuplicateKeys: boolean;\n checkDuplicateHrefs: boolean;\n checkHreflangCasing: boolean;\n originPolicy: \"same\" | \"allowlist\" | \"off\";\n allowedOrigins: string[];\n // Transform options\n expandLocales: boolean;\n};\n\nfunction parseArgs(argv: string[]): Args {\n const args: Args = {\n command: null,\n inPath: null,\n outPath: null,\n baseUrl: null,\n xDefault: null,\n ensureNamespace: true,\n json: false,\n failOnMissing: false,\n canonicalLocale: null,\n order: \"preserve\",\n trailingSlash: \"preserve\",\n checkDuplicateKeys: true,\n checkDuplicateHrefs: true,\n checkHreflangCasing: true,\n originPolicy: \"off\",\n allowedOrigins: [],\n expandLocales: false,\n };\n\n const [cmd, ...rest] = argv;\n if (cmd === \"inject\" || cmd === \"check\" || cmd === \"transform\") args.command = cmd;\n\n for (let i = 0; i < rest.length; i += 1) {\n const a = rest[i];\n const v = rest[i + 1];\n\n if (a === \"--in\" && v) {\n args.inPath = v;\n i += 1;\n continue;\n }\n if (a === \"--out\" && v) {\n args.outPath = v;\n i += 1;\n continue;\n }\n if (a === \"--base-url\" && v) {\n args.baseUrl = v;\n i += 1;\n continue;\n }\n if (a === \"--x-default\" && v) {\n args.xDefault = v;\n i += 1;\n continue;\n }\n if (a === \"--no-ensure-namespace\") {\n args.ensureNamespace = false;\n continue;\n }\n if (a === \"--json\") {\n args.json = true;\n continue;\n }\n if (a === \"--fail-on-missing\") {\n args.failOnMissing = true;\n continue;\n }\n if (a === \"--canonical-locale\" && v) {\n args.canonicalLocale = v;\n i += 1;\n continue;\n }\n if (a === \"--order\" && v) {\n if (v === \"canonical-first\" || v === \"preserve\") {\n args.order = v;\n }\n i += 1;\n continue;\n }\n if (a === \"--trailing-slash\" && v) {\n if (v === \"preserve\" || v === \"always\" || v === \"never\") {\n args.trailingSlash = v;\n }\n i += 1;\n continue;\n }\n if (a === \"--no-check-duplicate-keys\") {\n args.checkDuplicateKeys = false;\n continue;\n }\n if (a === \"--no-check-duplicate-hrefs\") {\n args.checkDuplicateHrefs = false;\n continue;\n }\n if (a === \"--no-check-hreflang-casing\") {\n args.checkHreflangCasing = false;\n continue;\n }\n if (a === \"--origin-policy\" && v) {\n if (v === \"same\" || v === \"allowlist\" || v === \"off\") {\n args.originPolicy = v;\n }\n i += 1;\n continue;\n }\n if (a === \"--allowed-origins\" && v) {\n args.allowedOrigins = v.split(\",\").map((s) => s.trim());\n i += 1;\n continue;\n }\n if (a === \"--expand-locales\") {\n args.expandLocales = true;\n continue;\n }\n if (a === \"--help\") {\n printHelp();\n process.exit(0);\n }\n }\n\n return args;\n}\n\nfunction printHelp(): void {\n const text = [\n \"nextjs-sitemap-hreflang\",\n \"\",\n \"Commands:\",\n \" inject --in <path> [--out <path>] [options]\",\n \" check --in <path> [--json] [--fail-on-missing] [options]\",\n \" transform --in <path> --out <path> [options]\",\n \"\",\n \"Inject options:\",\n \" --x-default loc|root|locale:en|custom:https://example.com/path\",\n \" --base-url https://example.com\",\n \" --canonical-locale <locale> Canonical locale for ordering\",\n \" --order canonical-first|preserve\",\n \" --trailing-slash preserve|always|never\",\n \" --no-ensure-namespace\",\n \"\",\n \"Check options:\",\n \" --no-check-duplicate-keys Disable duplicate key check\",\n \" --no-check-duplicate-hrefs Disable duplicate href check\",\n \" --no-check-hreflang-casing Disable hreflang casing check\",\n \" --origin-policy same|allowlist|off\",\n \" --allowed-origins <comma-separated>\",\n \"\",\n \"Transform options:\",\n \" --expand-locales Expand locale entries\",\n \" --trailing-slash preserve|always|never\",\n \" --order canonical-first|preserve\",\n \" --canonical-locale <locale>\",\n \"\",\n ].join(\"\\n\");\n process.stdout.write(text);\n}\n\nfunction parseXDefaultStrategy(raw: string | null): XDefaultStrategy | undefined {\n if (!raw) return undefined;\n if (raw === \"loc\") return { type: \"loc\" };\n if (raw === \"root\") return { type: \"root\" };\n if (raw.startsWith(\"locale:\")) return { type: \"locale\", locale: raw.slice(\"locale:\".length) };\n if (raw.startsWith(\"custom:\")) return { type: \"custom\", url: raw.slice(\"custom:\".length) };\n return undefined;\n}\n\nfunction readFileUtf8(p: string): string {\n return fs.readFileSync(p, \"utf8\");\n}\n\nfunction writeFileUtf8(p: string, content: string): void {\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, content, \"utf8\");\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv.slice(2));\n\n if (!args.command) {\n printHelp();\n process.exit(1);\n }\n\n if (!args.inPath) {\n process.stderr.write(\"Missing --in\\n\");\n process.exit(1);\n }\n\n const xml = readFileUtf8(args.inPath);\n\n if (args.command === \"inject\") {\n const strategy = parseXDefaultStrategy(args.xDefault) ?? { type: \"loc\" };\n\n const next = injectXDefaultIntoSitemapXml(xml, {\n ...(args.baseUrl ? { baseUrl: args.baseUrl } : {}),\n xDefaultStrategy: strategy,\n ensureNamespace: args.ensureNamespace,\n ...(args.canonicalLocale ? { canonicalLocale: args.canonicalLocale } : {}),\n order: args.order,\n trailingSlash: args.trailingSlash,\n });\n\n const outPath = args.outPath ?? args.inPath;\n writeFileUtf8(outPath, next);\n process.stdout.write(`ok: injected\\n`);\n return;\n }\n\n if (args.command === \"transform\") {\n let result = args.ensureNamespace ? ensureXhtmlNamespace(xml) : xml;\n\n // Apply trailing slash normalization\n if (args.trailingSlash !== \"preserve\") {\n const blocks = extractUrlBlocks(result);\n for (const block of blocks) {\n const transformed = normalizeTrailingSlashInBlock(block, args.trailingSlash);\n result = result.replace(block, transformed);\n }\n }\n\n // Apply reordering\n if (args.order === \"canonical-first\") {\n const blocks = extractUrlBlocks(result);\n for (const block of blocks) {\n const transformed = reorderXhtmlLinks(block, {\n ...(args.canonicalLocale ? { canonicalLocale: args.canonicalLocale } : {}),\n order: \"canonical-first\",\n });\n result = result.replace(block, transformed);\n }\n }\n\n const outPath = args.outPath;\n if (!outPath) {\n process.stderr.write(\"Missing --out for transform\\n\");\n process.exit(1);\n }\n writeFileUtf8(outPath, result);\n process.stdout.write(`ok: transformed\\n`);\n return;\n }\n\n const report = checkSitemapXmlHreflang(xml, {\n requireNamespace: true,\n requireAbsolute: true,\n requireXDefaultWhenMultiple: true,\n checkDuplicateKeys: args.checkDuplicateKeys,\n checkDuplicateHrefs: args.checkDuplicateHrefs,\n checkHreflangCasing: args.checkHreflangCasing,\n originPolicy: args.originPolicy,\n allowedOrigins: args.allowedOrigins,\n });\n\n if (args.json) {\n process.stdout.write(JSON.stringify(report, null, 2));\n process.stdout.write(\"\\n\");\n } else {\n if (report.ok) {\n process.stdout.write(\"ok: sitemap hreflang check passed\\n\");\n } else {\n process.stdout.write(`fail: ${report.issues.length} issue(s)\\n`);\n for (const issue of report.issues) {\n process.stdout.write(`- ${issue.code} ${issue.entryUrl}: ${issue.message}\\n`);\n }\n }\n }\n\n if (!report.ok && args.failOnMissing) process.exit(1);\n}\n\nvoid main();\n","export type TrailingSlashPolicy = \"preserve\" | \"always\" | \"never\";\n\nexport function normalizeUrl(url: string, trailingSlash: TrailingSlashPolicy): string {\n const u = new URL(url);\n if (trailingSlash === \"preserve\") return u.toString();\n if (trailingSlash === \"always\") {\n if (!u.pathname.endsWith(\"/\")) u.pathname = `${u.pathname}/`;\n return u.toString();\n }\n if (u.pathname !== \"/\" && u.pathname.endsWith(\"/\")) u.pathname = u.pathname.slice(0, -1);\n return u.toString();\n}\n\nexport function resolveAbsoluteUrl(input: string, baseUrl: string): string {\n if (input.startsWith(\"http://\") || input.startsWith(\"https://\")) return input;\n return new URL(input.startsWith(\"/\") ? input : `/${input}`, baseUrl).toString();\n}\n\nexport function getOriginFromAbsoluteUrl(absoluteUrl: string): string {\n const u = new URL(absoluteUrl);\n return `${u.protocol}//${u.host}`;\n}\n","export function xmlEscape(input: string): string {\n return input\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&apos;\");\n}\n\nexport function hasXhtmlNamespace(xml: string): boolean {\n return /<urlset[^>]*\\sxmlns:xhtml=\"http:\\/\\/www\\.w3\\.org\\/1999\\/xhtml\"[^>]*>/.test(xml);\n}\n\nexport function ensureXhtmlNamespace(xml: string): string {\n if (hasXhtmlNamespace(xml)) return xml;\n return xml.replace(\n /<urlset(\\s[^>]*?)?>/m,\n (m) => (m.includes(\"xmlns:xhtml=\") ? m : m.replace(\"<urlset\", '<urlset xmlns:xhtml=\"http://www.w3.org/1999/xhtml\"')),\n );\n}\n\nexport function extractUrlBlocks(xml: string): string[] {\n return xml.match(/<url>[\\s\\S]*?<\\/url>/g) ?? [];\n}\n\nexport function extractLoc(urlBlock: string): string | null {\n return urlBlock.match(/<loc>([^<]+)<\\/loc>/)?.[1]?.trim() ?? null;\n}\n\nexport function extractXhtmlLinks(urlBlock: string): Array<{ hreflang: string; href: string }> {\n const out: Array<{ hreflang: string; href: string }> = [];\n const re = /<xhtml:link[^>]*\\shreflang=\"([^\"]+)\"[^>]*\\shref=\"([^\"]+)\"[^>]*\\/>/g;\n for (const m of urlBlock.matchAll(re)) {\n if (m[1] !== undefined && m[2] !== undefined) {\n out.push({ hreflang: m[1], href: m[2] });\n }\n }\n return out;\n}\n\nexport function hasXDefault(urlBlock: string): boolean {\n return /<xhtml:link[^>]*\\shreflang=\"x-default\"[^>]*\\/>/.test(urlBlock);\n}\n\nexport function insertXhtmlLink(urlBlock: string, linkXml: string): string {\n const lastLinkIdx = urlBlock.lastIndexOf(\"<xhtml:link\");\n if (lastLinkIdx === -1) {\n const locEnd = urlBlock.indexOf(\"</loc>\");\n if (locEnd === -1) return urlBlock;\n const insertPos = locEnd + \"</loc>\".length;\n return `${urlBlock.slice(0, insertPos)}\\n ${linkXml}${urlBlock.slice(insertPos)}`;\n }\n const lineEnd = urlBlock.indexOf(\"\\n\", lastLinkIdx);\n if (lineEnd === -1) return `${urlBlock}\\n ${linkXml}`;\n return `${urlBlock.slice(0, lineEnd)}\\n ${linkXml}${urlBlock.slice(lineEnd)}`;\n}\n\nexport type ReorderOptions = {\n canonicalLocale?: string;\n order: \"canonical-first\" | \"preserve\";\n};\n\nexport function reorderXhtmlLinks(urlBlock: string, options: ReorderOptions): string {\n if (options.order === \"preserve\") return urlBlock;\n\n const links = extractXhtmlLinks(urlBlock);\n if (links.length === 0) return urlBlock;\n\n const loc = extractLoc(urlBlock);\n\n // Determine canonical\n let canonical: { hreflang: string; href: string } | undefined;\n if (options.canonicalLocale) {\n canonical = links.find((l) => l.hreflang === options.canonicalLocale);\n }\n if (!canonical && loc) {\n canonical = links.find((l) => l.href === loc);\n }\n\n // Split into groups\n const canonicalLinks = canonical ? [canonical] : [];\n const otherLinks = links.filter((l) => l !== canonical && l.hreflang !== \"x-default\");\n const xDefaultLinks = links.filter((l) => l.hreflang === \"x-default\");\n\n // Build new order\n const orderedLinks = [...canonicalLinks, ...otherLinks, ...xDefaultLinks];\n\n // Remove all existing xhtml:link and insert in new order\n const newBlock = urlBlock.replace(/<xhtml:link[^>]*\\/>\\s*/g, \"\");\n\n // Find position after </loc>\n const locEnd = newBlock.indexOf(\"</loc>\");\n if (locEnd === -1) return urlBlock;\n\n const insertPos = locEnd + \"</loc>\".length;\n const linksXml = orderedLinks\n .map((l) => `\\n <xhtml:link rel=\"alternate\" hreflang=\"${xmlEscape(l.hreflang)}\" href=\"${xmlEscape(l.href)}\" />`)\n .join(\"\");\n\n return `${newBlock.slice(0, insertPos)}${linksXml}\\n ${newBlock.slice(insertPos).trimStart()}`;\n}\n\nexport function normalizeTrailingSlashInBlock(\n urlBlock: string,\n policy: \"preserve\" | \"always\" | \"never\",\n): string {\n if (policy === \"preserve\") return urlBlock;\n\n // Normalize <loc>\n let result = urlBlock.replace(/<loc>([^<]+)<\\/loc>/g, (_, url: string) => {\n return `<loc>${applyTrailingSlashPolicy(url, policy)}</loc>`;\n });\n\n // Normalize href in xhtml:link\n result = result.replace(\n /(<xhtml:link[^>]*href=\")([^\"]+)(\"[^>]*\\/>)/g,\n (_, prefix: string, url: string, suffix: string) => {\n return `${prefix}${applyTrailingSlashPolicy(url, policy)}${suffix}`;\n },\n );\n\n return result;\n}\n\nfunction applyTrailingSlashPolicy(url: string, policy: \"always\" | \"never\"): string {\n try {\n const u = new URL(url);\n if (policy === \"always\" && !u.pathname.endsWith(\"/\")) {\n u.pathname += \"/\";\n } else if (policy === \"never\" && u.pathname !== \"/\" && u.pathname.endsWith(\"/\")) {\n u.pathname = u.pathname.slice(0, -1);\n }\n return u.toString();\n } catch {\n return url;\n }\n}\n","import { getOriginFromAbsoluteUrl, resolveAbsoluteUrl } from \"../lib/url.js\";\nimport type { XDefaultStrategy } from \"../lib/types.js\";\nimport {\n ensureXhtmlNamespace,\n extractLoc,\n extractUrlBlocks,\n extractXhtmlLinks,\n hasXDefault,\n insertXhtmlLink,\n normalizeTrailingSlashInBlock,\n reorderXhtmlLinks,\n xmlEscape,\n} from \"./xml.js\";\n\nexport type InjectOptions = {\n baseUrl?: string;\n xDefaultStrategy?: XDefaultStrategy;\n ensureNamespace?: boolean;\n canonicalLocale?: string;\n order?: \"canonical-first\" | \"preserve\";\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n};\n\nexport function injectXDefaultIntoSitemapXml(xml: string, options: InjectOptions): string {\n const ensureNamespace = options.ensureNamespace ?? true;\n const xDefaultStrategy = options.xDefaultStrategy ?? { type: \"loc\" };\n const order = options.order ?? \"preserve\";\n const trailingSlash = options.trailingSlash ?? \"preserve\";\n\n const xmlWithNs = ensureNamespace ? ensureXhtmlNamespace(xml) : xml;\n\n const blocks = extractUrlBlocks(xmlWithNs);\n if (blocks.length === 0) return xmlWithNs;\n\n let out = xmlWithNs;\n\n for (const block of blocks) {\n const loc = extractLoc(block);\n if (!loc) continue;\n\n const links = extractXhtmlLinks(block);\n if (links.length === 0) continue;\n\n let nextBlock = block;\n\n if (!hasXDefault(block)) {\n const href = resolveXDefaultHref({\n loc,\n links,\n ...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),\n strategy: xDefaultStrategy,\n });\n\n const linkXml = `<xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"${xmlEscape(href)}\" />`;\n nextBlock = insertXhtmlLink(nextBlock, linkXml);\n }\n\n // Apply reordering if needed\n if (order === \"canonical-first\") {\n nextBlock = reorderXhtmlLinks(nextBlock, {\n ...(options.canonicalLocale ? { canonicalLocale: options.canonicalLocale } : {}),\n order: \"canonical-first\",\n });\n }\n\n // Apply trailing slash normalization\n if (trailingSlash !== \"preserve\") {\n nextBlock = normalizeTrailingSlashInBlock(nextBlock, trailingSlash);\n }\n\n out = out.replace(block, nextBlock);\n }\n\n return out;\n}\n\nfunction resolveXDefaultHref(args: {\n loc: string;\n links: Array<{ hreflang: string; href: string }>;\n baseUrl?: string;\n strategy: XDefaultStrategy;\n}): string {\n const { loc, links, baseUrl, strategy } = args;\n\n const locAbs = baseUrl ? resolveAbsoluteUrl(loc, baseUrl) : loc;\n const origin = isAbsolute(locAbs) ? getOriginFromAbsoluteUrl(locAbs) : baseUrl;\n\n if (strategy.type === \"loc\") return locAbs;\n if (strategy.type === \"root\") {\n if (!origin) return locAbs;\n return resolveAbsoluteUrl(\"/\", origin);\n }\n if (strategy.type === \"custom\") {\n if (!origin) return strategy.url;\n return resolveAbsoluteUrl(strategy.url, origin);\n }\n if (strategy.type === \"locale\") {\n const found = links.find((l) => l.hreflang === strategy.locale)?.href;\n if (found) return baseUrl ? resolveAbsoluteUrl(found, baseUrl) : found;\n return locAbs;\n }\n const computed = strategy.resolve({ url: locAbs });\n if (!origin) return computed;\n return resolveAbsoluteUrl(computed, origin);\n}\n\nfunction isAbsolute(u: string): boolean {\n return u.startsWith(\"http://\") || u.startsWith(\"https://\");\n}\n","import type { HreflangIssue, HreflangReport } from \"../lib/types.js\";\nimport { extractLoc, extractUrlBlocks, extractXhtmlLinks, hasXhtmlNamespace } from \"./xml.js\";\n\nexport type CheckXmlOptions = {\n requireNamespace?: boolean;\n requireXDefaultWhenMultiple?: boolean;\n requireAbsolute?: boolean;\n checkDuplicateKeys?: boolean;\n checkDuplicateHrefs?: boolean;\n checkHreflangCasing?: boolean;\n originPolicy?: \"same\" | \"allowlist\" | \"off\";\n allowedOrigins?: string[];\n};\n\nexport function checkSitemapXmlHreflang(xml: string, options: CheckXmlOptions): HreflangReport {\n const requireNamespace = options.requireNamespace ?? true;\n const requireXDefaultWhenMultiple = options.requireXDefaultWhenMultiple ?? true;\n const requireAbsolute = options.requireAbsolute ?? true;\n const checkDuplicateKeys = options.checkDuplicateKeys ?? true;\n const checkDuplicateHrefs = options.checkDuplicateHrefs ?? true;\n const checkHreflangCasing = options.checkHreflangCasing ?? true;\n const originPolicy = options.originPolicy ?? \"off\";\n\n const issues: HreflangIssue[] = [];\n\n const blocks = extractUrlBlocks(xml);\n\n if (requireNamespace) {\n const usesXhtml = /<xhtml:link\\b/.test(xml);\n if (usesXhtml && !hasXhtmlNamespace(xml)) {\n issues.push({\n code: \"MISSING_LANGUAGES\",\n entryUrl: \"sitemap.xml\",\n message: 'Missing xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" in <urlset>',\n });\n }\n }\n\n for (const block of blocks) {\n const loc = extractLoc(block);\n if (!loc) continue;\n\n const links = extractXhtmlLinks(block);\n if (links.length === 0) continue;\n\n const hasXDefault = links.some((l) => l.hreflang === \"x-default\");\n if (requireXDefaultWhenMultiple && links.length > 1 && !hasXDefault) {\n issues.push({\n code: \"MISSING_XDEFAULT\",\n entryUrl: loc,\n message: \"Missing x-default hreflang in sitemap url block\",\n });\n }\n\n // Check for duplicate hreflang keys\n if (checkDuplicateKeys) {\n const seenKeys = new Set<string>();\n for (const link of links) {\n if (seenKeys.has(link.hreflang)) {\n issues.push({\n code: \"DUPLICATE_HREFLANG_KEY\",\n entryUrl: loc,\n message: `Duplicate hreflang key: ${link.hreflang}`,\n });\n }\n seenKeys.add(link.hreflang);\n }\n }\n\n // Check for duplicate hrefs (excluding x-default which can share href with another locale)\n if (checkDuplicateHrefs) {\n const hrefToLocales = new Map<string, string[]>();\n for (const link of links) {\n const locales = hrefToLocales.get(link.href) ?? [];\n locales.push(link.hreflang);\n hrefToLocales.set(link.href, locales);\n }\n for (const [href, locales] of hrefToLocales) {\n // Filter out x-default - it's allowed to share href with another locale\n const nonXDefaultLocales = locales.filter((l) => l !== \"x-default\");\n if (nonXDefaultLocales.length > 1) {\n issues.push({\n code: \"DUPLICATE_HREF\",\n entryUrl: loc,\n message: `Duplicate hreflang href detected: ${href} (locales: ${nonXDefaultLocales.join(\", \")})`,\n });\n }\n }\n }\n\n // Check hreflang casing\n if (checkHreflangCasing) {\n for (const link of links) {\n if (!isValidHreflangCasing(link.hreflang)) {\n issues.push({\n code: \"INVALID_HREFLANG_CASING\",\n entryUrl: loc,\n message: `Invalid hreflang casing: ${link.hreflang}. Expected format: en, pt-BR, or x-default`,\n });\n }\n }\n }\n\n // Check origin policy\n if (originPolicy === \"same\") {\n const locOrigin = getOrigin(loc);\n if (locOrigin) {\n for (const link of links) {\n const linkOrigin = getOrigin(link.href);\n if (linkOrigin && linkOrigin !== locOrigin) {\n issues.push({\n code: \"INCONSISTENT_ORIGIN\",\n entryUrl: loc,\n message: `Inconsistent origin for ${link.hreflang}: expected ${locOrigin}, got ${linkOrigin}`,\n });\n }\n }\n }\n } else if (originPolicy === \"allowlist\") {\n const allowedOrigins = options.allowedOrigins ?? [];\n for (const link of links) {\n const linkOrigin = getOrigin(link.href);\n if (linkOrigin && !allowedOrigins.includes(linkOrigin)) {\n issues.push({\n code: \"INCONSISTENT_ORIGIN\",\n entryUrl: loc,\n message: `Origin not in allowlist for ${link.hreflang}: ${linkOrigin}`,\n });\n }\n }\n const locOrigin = getOrigin(loc);\n if (locOrigin && !allowedOrigins.includes(locOrigin)) {\n issues.push({\n code: \"INCONSISTENT_ORIGIN\",\n entryUrl: loc,\n message: `Origin not in allowlist for loc: ${locOrigin}`,\n });\n }\n }\n\n if (requireAbsolute) {\n for (const link of links) {\n if (!isAbsolute(link.href)) {\n issues.push({\n code: \"NON_ABSOLUTE_URL\",\n entryUrl: loc,\n message: `Non-absolute hreflang href for ${link.hreflang}: ${link.href}`,\n });\n }\n }\n if (!isAbsolute(loc)) {\n issues.push({\n code: \"NON_ABSOLUTE_URL\",\n entryUrl: loc,\n message: `Non-absolute <loc>: ${loc}`,\n });\n }\n }\n }\n\n return { ok: issues.length === 0, issues };\n}\n\nfunction isAbsolute(u: string): boolean {\n return u.startsWith(\"http://\") || u.startsWith(\"https://\");\n}\n\nfunction getOrigin(url: string): string | null {\n try {\n const u = new URL(url);\n return u.origin;\n } catch {\n return null;\n }\n}\n\nfunction isValidHreflangCasing(key: string): boolean {\n if (key === \"x-default\") return true;\n // en, de, uk - only lowercase\n if (/^[a-z]{2}$/.test(key)) return true;\n // pt-BR, en-US - lowercase-UPPERCASE\n if (/^[a-z]{2}-[A-Z]{2}$/.test(key)) return true;\n return false;\n}\n"],"mappings":"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","names":["hasXDefault","isAbsolute"]}