@pas7/nextjs-sitemap-hreflang 0.6.1 → 0.7.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/README.md +22 -0
- package/dist/cli.js +88 -5
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,12 @@ npx nextjs-sitemap-hreflang check --fail-on-missing --prefer out
|
|
|
94
94
|
npx nextjs-sitemap-hreflang inject --prefer public --out public/sitemap.xml
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
Generate machine-readable CI report:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx nextjs-sitemap-hreflang check --fail-on-missing --prefer out --json > report.json
|
|
101
|
+
```
|
|
102
|
+
|
|
97
103
|
## Next.js Full SEO Stack (App Router)
|
|
98
104
|
|
|
99
105
|
`app/sitemap.ts`:
|
|
@@ -211,6 +217,22 @@ npx nextjs-sitemap-hreflang check \
|
|
|
211
217
|
--fail-on-missing
|
|
212
218
|
```
|
|
213
219
|
|
|
220
|
+
### JSON report format
|
|
221
|
+
|
|
222
|
+
`--json` output is stable and includes:
|
|
223
|
+
- `ok`
|
|
224
|
+
- `issues[]` (with `code`, `entryUrl`, `message`, `suggestion`)
|
|
225
|
+
- `summary.byCode`
|
|
226
|
+
- `inputPath`
|
|
227
|
+
- `timingMs`
|
|
228
|
+
|
|
229
|
+
### Exit codes
|
|
230
|
+
|
|
231
|
+
- `0`: OK
|
|
232
|
+
- `2`: validation errors (`--fail-on-missing`)
|
|
233
|
+
- `4`: input not found
|
|
234
|
+
- `5`: invalid XML input
|
|
235
|
+
|
|
214
236
|
## Release and npm publish
|
|
215
237
|
|
|
216
238
|
Single workflow in `.github/workflows/ci.yml`:
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,73 @@
|
|
|
4
4
|
import fs2 from "fs";
|
|
5
5
|
import path2 from "path";
|
|
6
6
|
|
|
7
|
+
// src/cli/checkReport.ts
|
|
8
|
+
function detectXmlInputError(xml) {
|
|
9
|
+
if (!xml.trim()) return "Invalid XML: file is empty";
|
|
10
|
+
if (!xml.includes("<urlset")) return "Invalid XML: missing <urlset>";
|
|
11
|
+
if (!xml.includes("</urlset>")) return "Invalid XML: missing </urlset>";
|
|
12
|
+
const openingUrlTags = xml.match(/<url>/g)?.length ?? 0;
|
|
13
|
+
const closingUrlTags = xml.match(/<\/url>/g)?.length ?? 0;
|
|
14
|
+
if (openingUrlTags !== closingUrlTags) {
|
|
15
|
+
return "Invalid XML: mismatched <url> tags";
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function issueSuggestion(code) {
|
|
20
|
+
if (code === "MISSING_LANGUAGES") {
|
|
21
|
+
return "Add alternates.languages and ensure xmlns:xhtml exists on <urlset> when using xhtml:link.";
|
|
22
|
+
}
|
|
23
|
+
if (code === "MISSING_XDEFAULT") {
|
|
24
|
+
return "Add x-default hreflang (use `inject` with default strategy or configure xDefaultStrategy).";
|
|
25
|
+
}
|
|
26
|
+
if (code === "MISSING_SELF") {
|
|
27
|
+
return "Include canonical locale URL in alternates.languages for each entry.";
|
|
28
|
+
}
|
|
29
|
+
if (code === "NON_ABSOLUTE_URL") {
|
|
30
|
+
return "Convert all loc/hreflang href values to absolute URLs (https://...).";
|
|
31
|
+
}
|
|
32
|
+
if (code === "INVALID_LOCALE_KEY") {
|
|
33
|
+
return "Use valid locale keys like en or pt-BR, plus x-default.";
|
|
34
|
+
}
|
|
35
|
+
if (code === "DUPLICATE_HREFLANG_KEY") {
|
|
36
|
+
return "Remove duplicate hreflang keys in the same <url> block.";
|
|
37
|
+
}
|
|
38
|
+
if (code === "DUPLICATE_HREF") {
|
|
39
|
+
return "Ensure each non-x-default locale points to a unique localized URL.";
|
|
40
|
+
}
|
|
41
|
+
if (code === "INVALID_HREFLANG_CASING") {
|
|
42
|
+
return "Use lowercase language and uppercase region (en, pt-BR, x-default).";
|
|
43
|
+
}
|
|
44
|
+
if (code === "INCONSISTENT_ORIGIN") {
|
|
45
|
+
return "Align origins with loc or update allowed origins policy.";
|
|
46
|
+
}
|
|
47
|
+
return "Review sitemap hreflang configuration for this entry.";
|
|
48
|
+
}
|
|
49
|
+
function toCliIssues(issues) {
|
|
50
|
+
return issues.map((issue) => ({
|
|
51
|
+
...issue,
|
|
52
|
+
suggestion: issueSuggestion(issue.code)
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
function summarizeByCode(issues) {
|
|
56
|
+
const out = {};
|
|
57
|
+
for (const issue of issues) {
|
|
58
|
+
out[issue.code] = (out[issue.code] ?? 0) + 1;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function buildCliCheckJsonReport(args) {
|
|
63
|
+
return {
|
|
64
|
+
ok: args.ok,
|
|
65
|
+
issues: args.issues,
|
|
66
|
+
summary: {
|
|
67
|
+
byCode: summarizeByCode(args.issues)
|
|
68
|
+
},
|
|
69
|
+
inputPath: args.inputPath,
|
|
70
|
+
timingMs: args.timingMs
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
7
74
|
// src/cli/resolveSitemapInputPath.ts
|
|
8
75
|
import fs from "fs";
|
|
9
76
|
import path from "path";
|
|
@@ -540,6 +607,7 @@ function writeFileUtf8(p, content) {
|
|
|
540
607
|
fs2.writeFileSync(p, content, "utf8");
|
|
541
608
|
}
|
|
542
609
|
async function main() {
|
|
610
|
+
const startedAt = Date.now();
|
|
543
611
|
const args = parseArgs(process.argv.slice(2));
|
|
544
612
|
if (!args.command) {
|
|
545
613
|
printHelp();
|
|
@@ -560,9 +628,15 @@ async function main() {
|
|
|
560
628
|
const message = error instanceof Error ? error.message : "Failed to resolve sitemap input path";
|
|
561
629
|
process.stderr.write(`${message}
|
|
562
630
|
`);
|
|
563
|
-
process.exit(
|
|
631
|
+
process.exit(4);
|
|
564
632
|
}
|
|
565
633
|
const xml = readFileUtf8(resolvedInPath);
|
|
634
|
+
const xmlInputError = detectXmlInputError(xml);
|
|
635
|
+
if (xmlInputError) {
|
|
636
|
+
process.stderr.write(`${xmlInputError}
|
|
637
|
+
`);
|
|
638
|
+
process.exit(5);
|
|
639
|
+
}
|
|
566
640
|
if (args.command === "inject") {
|
|
567
641
|
const strategy = parseXDefaultStrategy(args.xDefault) ?? { type: "loc" };
|
|
568
642
|
const next = injectXDefaultIntoSitemapXml(xml, {
|
|
@@ -618,22 +692,31 @@ async function main() {
|
|
|
618
692
|
originPolicy: args.originPolicy,
|
|
619
693
|
allowedOrigins: args.allowedOrigins
|
|
620
694
|
});
|
|
695
|
+
const issues = toCliIssues(report.issues);
|
|
696
|
+
const jsonReport = buildCliCheckJsonReport({
|
|
697
|
+
ok: report.ok,
|
|
698
|
+
issues,
|
|
699
|
+
inputPath: resolvedInPath,
|
|
700
|
+
timingMs: Date.now() - startedAt
|
|
701
|
+
});
|
|
621
702
|
if (args.json) {
|
|
622
|
-
process.stdout.write(JSON.stringify(
|
|
703
|
+
process.stdout.write(JSON.stringify(jsonReport, null, 2));
|
|
623
704
|
process.stdout.write("\n");
|
|
624
705
|
} else {
|
|
625
706
|
if (report.ok) {
|
|
626
707
|
process.stdout.write("ok: sitemap hreflang check passed\n");
|
|
627
708
|
} else {
|
|
628
|
-
process.stdout.write(`fail: ${
|
|
709
|
+
process.stdout.write(`fail: ${issues.length} issue(s)
|
|
629
710
|
`);
|
|
630
|
-
for (const issue of
|
|
711
|
+
for (const issue of issues) {
|
|
631
712
|
process.stdout.write(`- ${issue.code} ${issue.entryUrl}: ${issue.message}
|
|
713
|
+
`);
|
|
714
|
+
process.stdout.write(` hint: ${issue.suggestion}
|
|
632
715
|
`);
|
|
633
716
|
}
|
|
634
717
|
}
|
|
635
718
|
}
|
|
636
|
-
if (!report.ok && args.failOnMissing) process.exit(
|
|
719
|
+
if (!report.ok && args.failOnMissing) process.exit(2);
|
|
637
720
|
}
|
|
638
721
|
void main();
|
|
639
722
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/cli/resolveSitemapInputPath.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 type { AutoDetectPreference } from \"./cli/resolveSitemapInputPath.js\";\nimport { resolveSitemapInputPath } from \"./cli/resolveSitemapInputPath.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 prefer: AutoDetectPreference;\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 prefer: \"public\",\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 === \"--prefer\" && v) {\n if (v === \"public\" || v === \"out\" || v === \"root\") {\n args.prefer = v;\n }\n i += 1;\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 \" --in is optional for inject/check (auto-detect: public/sitemap.xml, out/sitemap.xml, sitemap.xml)\",\n \" --prefer public|out|root Change auto-detect priority (default: public)\",\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 \" --prefer public|out|root Change auto-detect priority (default: public)\",\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 let resolvedInPath: string;\n try {\n if (args.command === \"transform\") {\n if (!args.inPath) {\n process.stderr.write(\"Missing --in for transform\\n\");\n process.exit(1);\n }\n resolvedInPath = args.inPath;\n } else {\n resolvedInPath = resolveSitemapInputPath({ inPath: args.inPath, prefer: args.prefer });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to resolve sitemap input path\";\n process.stderr.write(`${message}\\n`);\n process.exit(1);\n }\n\n const xml = readFileUtf8(resolvedInPath);\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 ?? resolvedInPath;\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","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type ResolveSitemapInputOptions = {\n inPath: string | null;\n prefer?: AutoDetectPreference;\n cwd?: string;\n exists?: (absolutePath: string) => boolean;\n};\n\nexport type AutoDetectPreference = \"public\" | \"out\" | \"root\";\n\nconst DEFAULT_CANDIDATES = [\n path.join(\"public\", \"sitemap.xml\"),\n path.join(\"out\", \"sitemap.xml\"),\n \"sitemap.xml\",\n] as const;\n\nexport function resolveSitemapInputPath(options: ResolveSitemapInputOptions): string {\n if (options.inPath) return options.inPath;\n\n const cwd = options.cwd ?? process.cwd();\n const exists = options.exists ?? fs.existsSync;\n const candidates = orderedCandidates(options.prefer ?? \"public\");\n\n for (const candidate of candidates) {\n const absolutePath = path.resolve(cwd, candidate);\n if (exists(absolutePath)) return absolutePath;\n }\n\n throw new Error(\n `Missing --in and sitemap file not found. Tried: ${candidates.join(\", \")}`,\n );\n}\n\nfunction orderedCandidates(prefer: AutoDetectPreference): readonly string[] {\n if (prefer === \"out\") {\n return [\n path.join(\"out\", \"sitemap.xml\"),\n path.join(\"public\", \"sitemap.xml\"),\n \"sitemap.xml\",\n ];\n }\n\n if (prefer === \"root\") {\n return [\n \"sitemap.xml\",\n path.join(\"public\", \"sitemap.xml\"),\n path.join(\"out\", \"sitemap.xml\"),\n ];\n }\n\n return DEFAULT_CANDIDATES;\n}\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(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\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":["fs","path","hasXDefault","isAbsolute","fs","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/cli/checkReport.ts","../src/cli/resolveSitemapInputPath.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 type { AutoDetectPreference } from \"./cli/resolveSitemapInputPath.js\";\nimport {\n buildCliCheckJsonReport,\n detectXmlInputError,\n toCliIssues,\n} from \"./cli/checkReport.js\";\nimport { resolveSitemapInputPath } from \"./cli/resolveSitemapInputPath.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 prefer: AutoDetectPreference;\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\ntype CliExitCode = 0 | 1 | 2 | 4 | 5;\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 prefer: \"public\",\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 === \"--prefer\" && v) {\n if (v === \"public\" || v === \"out\" || v === \"root\") {\n args.prefer = v;\n }\n i += 1;\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 \" --in is optional for inject/check (auto-detect: public/sitemap.xml, out/sitemap.xml, sitemap.xml)\",\n \" --prefer public|out|root Change auto-detect priority (default: public)\",\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 \" --prefer public|out|root Change auto-detect priority (default: public)\",\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 startedAt = Date.now();\n const args = parseArgs(process.argv.slice(2));\n\n if (!args.command) {\n printHelp();\n process.exit(1);\n }\n\n let resolvedInPath: string;\n try {\n if (args.command === \"transform\") {\n if (!args.inPath) {\n process.stderr.write(\"Missing --in for transform\\n\");\n process.exit(1);\n }\n resolvedInPath = args.inPath;\n } else {\n resolvedInPath = resolveSitemapInputPath({ inPath: args.inPath, prefer: args.prefer });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to resolve sitemap input path\";\n process.stderr.write(`${message}\\n`);\n process.exit(4 as CliExitCode);\n }\n\n const xml = readFileUtf8(resolvedInPath);\n const xmlInputError = detectXmlInputError(xml);\n if (xmlInputError) {\n process.stderr.write(`${xmlInputError}\\n`);\n process.exit(5 as CliExitCode);\n }\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 ?? resolvedInPath;\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 const issues = toCliIssues(report.issues);\n const jsonReport = buildCliCheckJsonReport({\n ok: report.ok,\n issues,\n inputPath: resolvedInPath,\n timingMs: Date.now() - startedAt,\n });\n\n if (args.json) {\n process.stdout.write(JSON.stringify(jsonReport, 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: ${issues.length} issue(s)\\n`);\n for (const issue of issues) {\n process.stdout.write(`- ${issue.code} ${issue.entryUrl}: ${issue.message}\\n`);\n process.stdout.write(` hint: ${issue.suggestion}\\n`);\n }\n }\n }\n\n if (!report.ok && args.failOnMissing) process.exit(2 as CliExitCode);\n}\n\nvoid main();\n","export type CliIssue = {\n code: string;\n entryUrl: string;\n message: string;\n suggestion: string;\n};\n\nexport type CliCheckJsonReport = {\n ok: boolean;\n issues: CliIssue[];\n summary: {\n byCode: Record<string, number>;\n };\n inputPath: string;\n timingMs: number;\n};\n\nexport function detectXmlInputError(xml: string): string | null {\n if (!xml.trim()) return \"Invalid XML: file is empty\";\n if (!xml.includes(\"<urlset\")) return \"Invalid XML: missing <urlset>\";\n if (!xml.includes(\"</urlset>\")) return \"Invalid XML: missing </urlset>\";\n\n const openingUrlTags = xml.match(/<url>/g)?.length ?? 0;\n const closingUrlTags = xml.match(/<\\/url>/g)?.length ?? 0;\n if (openingUrlTags !== closingUrlTags) {\n return \"Invalid XML: mismatched <url> tags\";\n }\n\n return null;\n}\n\nexport function issueSuggestion(code: string): string {\n if (code === \"MISSING_LANGUAGES\") {\n return \"Add alternates.languages and ensure xmlns:xhtml exists on <urlset> when using xhtml:link.\";\n }\n if (code === \"MISSING_XDEFAULT\") {\n return \"Add x-default hreflang (use `inject` with default strategy or configure xDefaultStrategy).\";\n }\n if (code === \"MISSING_SELF\") {\n return \"Include canonical locale URL in alternates.languages for each entry.\";\n }\n if (code === \"NON_ABSOLUTE_URL\") {\n return \"Convert all loc/hreflang href values to absolute URLs (https://...).\";\n }\n if (code === \"INVALID_LOCALE_KEY\") {\n return \"Use valid locale keys like en or pt-BR, plus x-default.\";\n }\n if (code === \"DUPLICATE_HREFLANG_KEY\") {\n return \"Remove duplicate hreflang keys in the same <url> block.\";\n }\n if (code === \"DUPLICATE_HREF\") {\n return \"Ensure each non-x-default locale points to a unique localized URL.\";\n }\n if (code === \"INVALID_HREFLANG_CASING\") {\n return \"Use lowercase language and uppercase region (en, pt-BR, x-default).\";\n }\n if (code === \"INCONSISTENT_ORIGIN\") {\n return \"Align origins with loc or update allowed origins policy.\";\n }\n return \"Review sitemap hreflang configuration for this entry.\";\n}\n\nexport function toCliIssues(\n issues: Array<{ code: string; entryUrl: string; message: string }>,\n): CliIssue[] {\n return issues.map((issue) => ({\n ...issue,\n suggestion: issueSuggestion(issue.code),\n }));\n}\n\nexport function summarizeByCode(issues: CliIssue[]): Record<string, number> {\n const out: Record<string, number> = {};\n for (const issue of issues) {\n out[issue.code] = (out[issue.code] ?? 0) + 1;\n }\n return out;\n}\n\nexport function buildCliCheckJsonReport(args: {\n ok: boolean;\n issues: CliIssue[];\n inputPath: string;\n timingMs: number;\n}): CliCheckJsonReport {\n return {\n ok: args.ok,\n issues: args.issues,\n summary: {\n byCode: summarizeByCode(args.issues),\n },\n inputPath: args.inputPath,\n timingMs: args.timingMs,\n };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type ResolveSitemapInputOptions = {\n inPath: string | null;\n prefer?: AutoDetectPreference;\n cwd?: string;\n exists?: (absolutePath: string) => boolean;\n};\n\nexport type AutoDetectPreference = \"public\" | \"out\" | \"root\";\n\nconst DEFAULT_CANDIDATES = [\n path.join(\"public\", \"sitemap.xml\"),\n path.join(\"out\", \"sitemap.xml\"),\n \"sitemap.xml\",\n] as const;\n\nexport function resolveSitemapInputPath(options: ResolveSitemapInputOptions): string {\n if (options.inPath) return options.inPath;\n\n const cwd = options.cwd ?? process.cwd();\n const exists = options.exists ?? fs.existsSync;\n const candidates = orderedCandidates(options.prefer ?? \"public\");\n\n for (const candidate of candidates) {\n const absolutePath = path.resolve(cwd, candidate);\n if (exists(absolutePath)) return absolutePath;\n }\n\n throw new Error(\n `Missing --in and sitemap file not found. Tried: ${candidates.join(\", \")}`,\n );\n}\n\nfunction orderedCandidates(prefer: AutoDetectPreference): readonly string[] {\n if (prefer === \"out\") {\n return [\n path.join(\"out\", \"sitemap.xml\"),\n path.join(\"public\", \"sitemap.xml\"),\n \"sitemap.xml\",\n ];\n }\n\n if (prefer === \"root\") {\n return [\n \"sitemap.xml\",\n path.join(\"public\", \"sitemap.xml\"),\n path.join(\"out\", \"sitemap.xml\"),\n ];\n }\n\n return DEFAULT_CANDIDATES;\n}\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(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\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":["fs","path","hasXDefault","isAbsolute","fs","path"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pas7/nextjs-sitemap-hreflang",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Add and validate hreflang alternates + x-default for Next.js sitemaps (App Router / MetadataRoute) with a tiny library + CLI postbuild fixer.",
|
|
5
5
|
"homepage": "https://github.com/pas7-studio/nextjs-sitemap-hreflang#readme",
|
|
6
6
|
"repository": {
|