@pas7/nextjs-sitemap-hreflang 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,36 +1,53 @@
1
- # nextjs-sitemap-hreflang
1
+ # @pas7/nextjs-sitemap-hreflang
2
2
 
3
3
  [![CI](https://github.com/pas7-studio/nextjs-sitemap-hreflang/actions/workflows/ci.yml/badge.svg)](https://github.com/pas7-studio/nextjs-sitemap-hreflang/actions/workflows/ci.yml)
4
- [![npm version](https://img.shields.io/npm/v/nextjs-sitemap-hreflang.svg)](https://www.npmjs.com/package/nextjs-sitemap-hreflang)
5
- [![npm downloads](https://img.shields.io/npm/dm/nextjs-sitemap-hreflang.svg)](https://www.npmjs.com/package/nextjs-sitemap-hreflang)
4
+ [![npm version](https://img.shields.io/npm/v/%40pas7%2Fnextjs-sitemap-hreflang.svg)](https://www.npmjs.com/package/@pas7/nextjs-sitemap-hreflang)
5
+ [![npm downloads](https://img.shields.io/npm/dm/%40pas7%2Fnextjs-sitemap-hreflang.svg)](https://www.npmjs.com/package/@pas7/nextjs-sitemap-hreflang)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7
7
 
8
8
  Routing-agnostic hreflang toolkit for Next.js sitemaps:
9
- - Build hreflang alternates for App Router `MetadataRoute.Sitemap`
10
- - Inject/fix hreflang directly in generated XML
11
- - Validate sitemap hreflang in CI
12
- - Support different i18n URL patterns and content pipelines (`.ts`, `.json`, `.md/.mdx`, CMS)
9
+ - build hreflang alternates for App Router `MetadataRoute.Sitemap`
10
+ - inject/fix hreflang directly in generated XML
11
+ - validate sitemap hreflang in CI
12
+ - support mixed content pipelines (`.ts`, `.json`, `.md/.mdx`, CMS)
13
13
 
14
14
  ## Install
15
15
 
16
16
  ```bash
17
- npm i nextjs-sitemap-hreflang
17
+ npm i @pas7/nextjs-sitemap-hreflang
18
18
  ```
19
19
 
20
- ## Why this package
20
+ ## Migration guide (unscoped -> scoped)
21
21
 
22
- `nextjs-sitemap-hreflang` is built for real production SEO workflows where teams have mixed routing and mixed content storage:
23
- - `next-intl`, `next-i18next`, custom i18n
24
- - static/generated content from TypeScript, JSON manifests, Markdown/MDX, or external CMS
25
- - need both library-time and postbuild XML-time safety
22
+ ```bash
23
+ npm uninstall nextjs-sitemap-hreflang
24
+ npm i @pas7/nextjs-sitemap-hreflang
25
+ ```
26
+
27
+ Update imports:
26
28
 
27
- ## Quick Start
29
+ ```ts
30
+ // before
31
+ import { withHreflang } from "nextjs-sitemap-hreflang";
32
+
33
+ // after
34
+ import { withHreflang } from "@pas7/nextjs-sitemap-hreflang";
35
+ ```
36
+
37
+ CLI binary stays the same:
38
+
39
+ ```bash
40
+ npx nextjs-sitemap-hreflang check --in public/sitemap.xml --fail-on-missing
41
+ ```
28
42
 
29
- ### 1) App Router sitemap with routing strategy
43
+ ## Quick start: App Router
30
44
 
31
45
  ```ts
32
46
  import type { MetadataRoute } from "next";
33
- import { withHreflangFromRouting, routingPrefixAsNeeded } from "nextjs-sitemap-hreflang";
47
+ import {
48
+ withHreflangFromRouting,
49
+ routingPrefixAsNeeded,
50
+ } from "@pas7/nextjs-sitemap-hreflang";
34
51
 
35
52
  const routing = routingPrefixAsNeeded({
36
53
  defaultLocale: "en",
@@ -50,75 +67,62 @@ export default function sitemap(): MetadataRoute.Sitemap {
50
67
  }
51
68
  ```
52
69
 
53
- ### 2) Universal manifest helper (`.ts` / `.json` / `.md` compatible)
70
+ ## Next.js App Router + static export (`sitemap.xml`)
54
71
 
55
- If your pipeline already produces a manifest (`slug + locales + dates`), use:
72
+ Use library generation + XML validation in CI:
56
73
 
57
- ```ts
58
- import { createSitemapEntriesFromManifest } from "nextjs-sitemap-hreflang";
59
-
60
- const blogManifest = [
61
- {
62
- slug: "nestjs-request-context-als-2026",
63
- locales: ["en", "uk", "de", "it", "hr"],
64
- updatedAt: "2026-02-08",
65
- },
66
- ];
67
-
68
- const entries = createSitemapEntriesFromManifest(blogManifest, {
69
- baseUrl: "https://pas7.com.ua",
70
- sectionPath: "/blog",
71
- defaultLocale: "en",
72
- routeStyle: "locale-segment", // /blog/en/slug, /blog/uk/slug
73
- });
74
+ ```bash
75
+ next build
76
+ npx nextjs-sitemap-hreflang check --in public/sitemap.xml --fail-on-missing
74
77
  ```
75
78
 
76
- ### 3) Postbuild XML fixer + validator
79
+ Optional postbuild fix step:
77
80
 
78
81
  ```bash
79
82
  npx nextjs-sitemap-hreflang inject --in public/sitemap.xml --out public/sitemap.xml
80
- npx nextjs-sitemap-hreflang check --in public/sitemap.xml --fail-on-missing
81
83
  ```
82
84
 
83
- ## Routing Strategies
85
+ ## Universal manifest helper (`.ts` / `.json` / `.md`)
84
86
 
85
- Use built-in strategies or custom routing:
86
- - `routingPrefixAsNeeded`
87
- - `routingPrefixAlways`
88
- - `routingSuffixLocale`
89
- - `routingDomainBased`
90
- - `routingPAS7`
91
- - `routingCustom`
87
+ If your pipeline already outputs `slug + locales + date`, use:
92
88
 
93
- ## New Universal Manifest API
94
-
95
- `createSitemapEntriesFromManifest(items, options)` converts locale-aware manifest records into hreflang-ready entries.
96
-
97
- Supported route styles:
98
- - `prefix-as-needed`
99
- - `prefix-always`
100
- - `suffix-locale`
101
- - `locale-segment`
89
+ ```ts
90
+ import { createSitemapEntriesFromManifest } from "@pas7/nextjs-sitemap-hreflang";
102
91
 
103
- You can fully override URL generation with `pathnameFor(...)`.
92
+ const entries = createSitemapEntriesFromManifest(blogManifest, {
93
+ baseUrl: "https://pas7.com.ua",
94
+ sectionPath: "/blog",
95
+ defaultLocale: "en",
96
+ routeStyle: "locale-segment", // /blog/en/slug
97
+ });
98
+ ```
104
99
 
105
- ### Example: custom hybrid URLs
100
+ ## routingPAS7 with `suffixPaths` and `prefixPaths`
106
101
 
107
102
  ```ts
108
- const entries = createSitemapEntriesFromManifest(manifest, {
109
- baseUrl: "https://example.com",
110
- sectionPath: "/content",
103
+ import { routingPAS7 } from "@pas7/nextjs-sitemap-hreflang";
104
+
105
+ const routing = routingPAS7({
111
106
  defaultLocale: "en",
112
- pathnameFor: ({ slug, locale, defaultLocale }) =>
113
- locale === defaultLocale ? `/articles/${slug}` : `/articles/${slug}.${locale}`,
107
+ locales: ["en", "uk", "de"],
108
+ // /blog/uk, /contact/uk
109
+ suffixPaths: ["/blog", "/projects", "/services", "/cases", "/contact", "/about", "/privacy", "/terms"],
110
+ // /uk/about (if needed for some sections)
111
+ prefixPaths: ["/about"],
112
+ // keeps highest priority for locale-segment detail pages
113
+ detailPathPattern: /^\/(blog|projects|services|cases)\//,
114
114
  });
115
115
  ```
116
116
 
117
- ## CLI
117
+ Routing priority inside `routingPAS7`:
118
+ 1. `detailPathPattern`
119
+ 2. `suffixPaths` (or legacy `hubPaths`)
120
+ 3. `prefixPaths`
121
+ 4. fallback prefix-as-needed
118
122
 
119
- ### `inject`
123
+ ## CLI
120
124
 
121
- Add `x-default`, normalize order, and ensure namespace in sitemap XML.
125
+ ### inject
122
126
 
123
127
  ```bash
124
128
  npx nextjs-sitemap-hreflang inject --in public/sitemap.xml \
@@ -128,9 +132,7 @@ npx nextjs-sitemap-hreflang inject --in public/sitemap.xml \
128
132
  --trailing-slash never
129
133
  ```
130
134
 
131
- ### `check`
132
-
133
- Validate hreflang in sitemap XML.
135
+ ### check
134
136
 
135
137
  ```bash
136
138
  npx nextjs-sitemap-hreflang check --in public/sitemap.xml \
@@ -138,35 +140,21 @@ npx nextjs-sitemap-hreflang check --in public/sitemap.xml \
138
140
  --fail-on-missing
139
141
  ```
140
142
 
141
- ## CI Integration
142
-
143
- ```yaml
144
- - name: Check sitemap hreflang
145
- run: npx nextjs-sitemap-hreflang check --in public/sitemap.xml --fail-on-missing
146
- ```
147
-
148
- ## Release and npm Publish
149
-
150
- This repository uses a single CI workflow (`.github/workflows/ci.yml`) with a direct release flow:
151
-
152
- 1. Add a changeset when behavior/API changes:
153
-
154
- ```bash
155
- npm run changeset
156
- ```
143
+ ## Release and npm publish
157
144
 
158
- 2. Push/merge to `main`.
159
- 3. Workflow runs `ci -> release`:
160
- - applies `changeset version`
161
- - commits version/changelog to `main`
162
- - creates git tag + GitHub release
163
- - publishes to npm
145
+ Single workflow in `.github/workflows/ci.yml`:
146
+ 1. add changeset (`npm run changeset`)
147
+ 2. push to `main`
148
+ 3. workflow runs `ci -> release`
149
+ 4. version/tag/release/npm publish are automated
164
150
 
165
151
  Required secret: `NPM_TOKEN`.
166
152
 
167
- ## Project Roadmap
153
+ ## Contribution policy (required)
168
154
 
169
- See `docs/OPEN_SOURCE_VISION.md` for roadmap and architecture direction.
155
+ - Any user-facing code change must include a changeset (`.changeset/*.md`).
156
+ - Any API/feature behavior change must include `README.md` updates in the same PR.
157
+ - CI enforces both rules on pull requests.
170
158
 
171
159
  ## Maintained by PAS7 Studio
172
160
 
package/dist/index.d.ts CHANGED
@@ -1,16 +1,21 @@
1
1
  type ChangeFrequency = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
2
2
  type SitemapAlternates = {
3
- languages?: Record<string, string>;
3
+ languages?: Record<string, string | undefined> | undefined;
4
4
  };
5
5
  type SitemapEntryLike = {
6
6
  url: string;
7
- lastModified?: string | Date;
8
- changeFrequency?: ChangeFrequency;
9
- priority?: number;
10
- alternates?: SitemapAlternates;
11
- images?: string[];
7
+ lastModified?: string | Date | undefined;
8
+ changeFrequency?: ChangeFrequency | undefined;
9
+ priority?: number | undefined;
10
+ alternates?: SitemapAlternates | undefined;
11
+ images?: string[] | undefined;
12
12
  };
13
13
  type SitemapEntry = SitemapEntryLike;
14
+ /**
15
+ * Compatibility alias for Next.js App Router MetadataRoute.Sitemap entries.
16
+ * Keeps alternates.languages permissive for optional values.
17
+ */
18
+ type NextMetadataSitemapEntryCompatible = SitemapEntryLike;
14
19
  type XDefaultStrategy = {
15
20
  type: "loc";
16
21
  } | {
@@ -90,7 +95,12 @@ type RoutingSuffixLocaleOptions = {
90
95
  type RoutingPAS7Options = {
91
96
  readonly defaultLocale: string;
92
97
  readonly locales: readonly string[];
98
+ /**
99
+ * @deprecated Use suffixPaths. Kept for backward compatibility.
100
+ */
93
101
  readonly hubPaths?: readonly string[];
102
+ readonly suffixPaths?: readonly string[];
103
+ readonly prefixPaths?: readonly string[];
94
104
  readonly detailPathPattern?: RegExp;
95
105
  };
96
106
 
@@ -218,14 +228,12 @@ declare function routingCustom(options: {
218
228
  }): RoutingStrategy;
219
229
  /**
220
230
  * PAS7 custom routing strategy:
221
- * - Home: / (en), /uk, /de, /it, /hr
222
- * - Hubs: /blog (en), /blog/uk, /blog/de
223
- * - Details: /blog/en/<slug>, /blog/uk/<slug>
231
+ * - Home: / (en), /uk, /de
232
+ * - Suffix pages: /blog (en), /blog/uk
233
+ * - Detail pages: /blog/en/slug, /blog/uk/slug
234
+ * - Prefix pages: /about (en), /uk/about
224
235
  *
225
- * This is a hybrid routing scheme where:
226
- * - Home pages use prefix pattern for non-default locales
227
- * - Hub pages (listing pages) use suffix pattern for non-default locales
228
- * - Detail pages have locale as a segment after the section
236
+ * Priority: detailPathPattern > suffixPaths > prefixPaths > fallback.
229
237
  */
230
238
  declare function routingPAS7(options: RoutingPAS7Options): RoutingStrategy;
231
239
 
@@ -258,4 +266,4 @@ type CheckXmlOptions = {
258
266
  };
259
267
  declare function checkSitemapXmlHreflang(xml: string, options: CheckXmlOptions): HreflangReport;
260
268
 
261
- export { type ChangeFrequency, type CheckXmlOptions, type CreateSitemapEntriesFromManifestOptions, type ExpandLocalesOptions, type HreflangIssue, type HreflangIssueCode, type HreflangReport, type InjectOptions, type LocalizedManifestItem, type ManifestRouteStyle, type RoutingDomainBasedOptions, type RoutingPAS7Options, type RoutingPrefixAlwaysOptions, type RoutingPrefixAsNeededOptions, type RoutingStrategy, type RoutingSuffixLocaleOptions, type SitemapAlternates, type SitemapEntry, type SitemapEntryLike, type XDefaultStrategy, assertHreflangExport as assertHreflang, buildLanguagesMap, checkSitemapXmlHreflang, createSitemapEntriesFromManifest, expandLocaleEntries, injectXDefaultIntoSitemapXml, normalizeUrl, resolveAbsoluteUrl, routingCustom, routingDomainBased, routingPAS7, routingPrefixAlways, routingPrefixAsNeeded, routingSuffixLocale, withHreflang, withHreflangFromRouting };
269
+ export { type ChangeFrequency, type CheckXmlOptions, type CreateSitemapEntriesFromManifestOptions, type ExpandLocalesOptions, type HreflangIssue, type HreflangIssueCode, type HreflangReport, type InjectOptions, type LocalizedManifestItem, type ManifestRouteStyle, type NextMetadataSitemapEntryCompatible, type RoutingDomainBasedOptions, type RoutingPAS7Options, type RoutingPrefixAlwaysOptions, type RoutingPrefixAsNeededOptions, type RoutingStrategy, type RoutingSuffixLocaleOptions, type SitemapAlternates, type SitemapEntry, type SitemapEntryLike, type XDefaultStrategy, assertHreflangExport as assertHreflang, buildLanguagesMap, checkSitemapXmlHreflang, createSitemapEntriesFromManifest, expandLocaleEntries, injectXDefaultIntoSitemapXml, normalizeUrl, resolveAbsoluteUrl, routingCustom, routingDomainBased, routingPAS7, routingPrefixAlways, routingPrefixAsNeeded, routingSuffixLocale, withHreflang, withHreflangFromRouting };
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ function assertHreflang(entries, options) {
7
7
  for (const entry of entries) {
8
8
  const languages = entry.alternates?.languages;
9
9
  if (!languages) continue;
10
- const pairs = Object.entries(languages);
10
+ const pairs = Object.entries(languages).filter(([, href]) => Boolean(href));
11
11
  if (pairs.length === 0) {
12
12
  issues.push({
13
13
  code: "MISSING_LANGUAGES",
@@ -164,6 +164,7 @@ function normalizeEntryUrl(url, baseUrl, ensureAbsolute, trailingSlash) {
164
164
  function normalizeLanguages(languages, baseUrl, ensureAbsolute, trailingSlash) {
165
165
  const out = {};
166
166
  for (const [k, v] of Object.entries(languages)) {
167
+ if (!v) continue;
167
168
  const abs = ensureAbsolute ? resolveAbsoluteUrl(v, baseUrl ?? v) : v;
168
169
  out[k] = normalizeUrl(abs, trailingSlash);
169
170
  }
@@ -293,39 +294,79 @@ function routingPAS7(options) {
293
294
  defaultLocale,
294
295
  locales,
295
296
  hubPaths = ["/blog", "/projects", "/services", "/cases"],
297
+ suffixPaths,
298
+ prefixPaths = [],
296
299
  detailPathPattern = /^\/(blog|projects|services|cases)\//
297
300
  } = options;
301
+ const normalizedSuffixPaths = uniquePaths(suffixPaths ?? hubPaths);
302
+ const normalizedPrefixPaths = uniquePaths(prefixPaths);
303
+ const localeSet = new Set(locales);
298
304
  return {
299
305
  locales,
300
306
  canonicalLocale: defaultLocale,
301
307
  hrefFor: ({ pathname, locale }) => {
302
- const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
303
- if (normalizedPath === "/" || normalizedPath === "") {
308
+ const normalizedPath = normalizeInputPath(pathname);
309
+ if (normalizedPath === "/") {
304
310
  if (locale === defaultLocale) return "/";
305
311
  return `/${locale}`;
306
312
  }
307
313
  if (detailPathPattern.test(normalizedPath)) {
308
314
  const parts = normalizedPath.split("/").filter(Boolean);
309
- if (parts.length >= 3) {
315
+ if (parts.length >= 3 && localeSet.has(parts[1] ?? "")) {
310
316
  const section = parts[0];
311
317
  const slug = parts.slice(2).join("/");
312
318
  return `/${section}/${locale}/${slug}`;
313
319
  }
314
320
  }
315
- const matchingHub = hubPaths.find((hub) => {
316
- const normalizedHub = hub.startsWith("/") ? hub : `/${hub}`;
317
- return normalizedPath === normalizedHub || normalizedPath === `${normalizedHub}/`;
318
- });
319
- if (matchingHub) {
320
- const normalizedHub = matchingHub.startsWith("/") ? matchingHub.replace(/\/$/, "") : `/${matchingHub.replace(/\/$/, "")}`;
321
- if (locale === defaultLocale) return normalizedHub;
322
- return `${normalizedHub}/${locale}`;
321
+ const suffixBase = findSuffixBasePath(normalizedPath, normalizedSuffixPaths, localeSet);
322
+ if (suffixBase) {
323
+ if (locale === defaultLocale) return suffixBase;
324
+ return `${suffixBase}/${locale}`;
325
+ }
326
+ const prefixBase = findPrefixBasePath(normalizedPath, normalizedPrefixPaths, localeSet);
327
+ if (prefixBase) {
328
+ if (locale === defaultLocale) return prefixBase;
329
+ return `/${locale}${prefixBase}`;
323
330
  }
324
331
  if (locale === defaultLocale) return normalizedPath;
325
332
  return `/${locale}${normalizedPath}`;
326
333
  }
327
334
  };
328
335
  }
336
+ function normalizeInputPath(pathname) {
337
+ const withLeadingSlash = pathname.startsWith("/") ? pathname : `/${pathname}`;
338
+ if (withLeadingSlash.length > 1 && withLeadingSlash.endsWith("/")) {
339
+ return withLeadingSlash.slice(0, -1);
340
+ }
341
+ return withLeadingSlash;
342
+ }
343
+ function uniquePaths(paths) {
344
+ return [...new Set(paths.map(normalizeInputPath))];
345
+ }
346
+ function findSuffixBasePath(pathname, suffixPaths, localeSet) {
347
+ for (const suffixPath of suffixPaths) {
348
+ if (pathname === suffixPath) return suffixPath;
349
+ if (!pathname.startsWith(`${suffixPath}/`)) continue;
350
+ const rest = pathname.slice(`${suffixPath}/`.length);
351
+ if (!rest) continue;
352
+ const [maybeLocale, ...tail] = rest.split("/");
353
+ if (tail.length === 0 && localeSet.has(maybeLocale ?? "")) {
354
+ return suffixPath;
355
+ }
356
+ }
357
+ return null;
358
+ }
359
+ function findPrefixBasePath(pathname, prefixPaths, localeSet) {
360
+ for (const prefixPath of prefixPaths) {
361
+ if (pathname === prefixPath) return prefixPath;
362
+ const withoutLeadingSlash = pathname.replace(/^\/+/, "");
363
+ const [maybeLocale, ...tail] = withoutLeadingSlash.split("/");
364
+ if (!localeSet.has(maybeLocale ?? "")) continue;
365
+ const candidate = normalizeInputPath(tail.join("/"));
366
+ if (candidate === prefixPath) return prefixPath;
367
+ }
368
+ return null;
369
+ }
329
370
 
330
371
  // src/lib/expandLocales.ts
331
372
  function expandLocaleEntries(entries, options) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/assertHreflang.ts","../src/lib/url.ts","../src/lib/buildLanguagesMap.ts","../src/lib/withHreflang.ts","../src/lib/routing.ts","../src/lib/expandLocales.ts","../src/lib/fromManifest.ts","../src/xml/xml.ts","../src/xml/inject.ts","../src/xml/check.ts"],"sourcesContent":["import type { HreflangIssue, HreflangReport, SitemapEntryLike } from \"./types.js\";\n\nexport type AssertHreflangOptions = {\n requireAbsolute?: boolean;\n requireXDefaultWhenMultiple?: boolean;\n requireSelf?: boolean;\n canonicalLocale?: string;\n};\n\nexport function assertHreflang(\n entries: readonly SitemapEntryLike[],\n options: AssertHreflangOptions,\n): HreflangReport {\n const requireAbsolute = options.requireAbsolute ?? true;\n const requireXDefaultWhenMultiple = options.requireXDefaultWhenMultiple ?? true;\n const requireSelf = options.requireSelf ?? false;\n\n const issues: HreflangIssue[] = [];\n\n for (const entry of entries) {\n const languages = entry.alternates?.languages;\n if (!languages) continue;\n\n const pairs = Object.entries(languages);\n\n if (pairs.length === 0) {\n issues.push({\n code: \"MISSING_LANGUAGES\",\n entryUrl: entry.url,\n message: \"alternates.languages is empty\",\n });\n continue;\n }\n\n if (requireXDefaultWhenMultiple && pairs.length > 1 && !languages[\"x-default\"]) {\n issues.push({\n code: \"MISSING_XDEFAULT\",\n entryUrl: entry.url,\n message: \"Missing x-default hreflang for a multilingual entry\",\n });\n }\n\n if (requireSelf && options.canonicalLocale) {\n if (!languages[options.canonicalLocale]) {\n issues.push({\n code: \"MISSING_SELF\",\n entryUrl: entry.url,\n message: `Missing self hreflang for canonicalLocale=${options.canonicalLocale}`,\n });\n }\n }\n\n const hrefSeen = new Set<string>();\n for (const [locale, href] of pairs) {\n if (!isValidLocaleKey(locale)) {\n issues.push({\n code: \"INVALID_LOCALE_KEY\",\n entryUrl: entry.url,\n message: `Invalid locale key: ${locale}`,\n });\n }\n if (requireAbsolute && !isAbsoluteUrl(href)) {\n issues.push({\n code: \"NON_ABSOLUTE_URL\",\n entryUrl: entry.url,\n message: `Non-absolute hreflang href for ${locale}: ${href}`,\n });\n }\n if (hrefSeen.has(href)) {\n issues.push({\n code: \"DUPLICATE_HREF\",\n entryUrl: entry.url,\n message: `Duplicate hreflang href detected: ${href}`,\n });\n }\n hrefSeen.add(href);\n }\n }\n\n return { ok: issues.length === 0, issues };\n}\n\nfunction isAbsoluteUrl(url: string): boolean {\n return url.startsWith(\"http://\") || url.startsWith(\"https://\");\n}\n\nfunction isValidLocaleKey(key: string): boolean {\n if (key === \"x-default\") return true;\n return /^[a-z]{2}(-[A-Z]{2})?$/.test(key);\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","import type { XDefaultStrategy } from \"./types.js\";\nimport { normalizeUrl, resolveAbsoluteUrl } from \"./url.js\";\n\nexport type BuildLanguagesMapInput = {\n baseUrl: string;\n locales: readonly string[];\n canonicalLocale: string;\n resolveHref: (locale: string) => string;\n includeXDefault: boolean;\n xDefaultStrategy?: XDefaultStrategy;\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n};\n\nexport type BuildLanguagesMapOutput = {\n canonical: string;\n languages: Record<string, string>;\n};\n\nexport function buildLanguagesMap(input: BuildLanguagesMapInput): BuildLanguagesMapOutput {\n const trailingSlash = input.trailingSlash ?? \"preserve\";\n const languages: Record<string, string> = {};\n\n for (const locale of input.locales) {\n const href = input.resolveHref(locale);\n const abs = resolveAbsoluteUrl(href, input.baseUrl);\n languages[locale] = normalizeUrl(abs, trailingSlash);\n }\n\n const canonical = languages[input.canonicalLocale] ?? normalizeUrl(input.baseUrl, trailingSlash);\n\n if (input.includeXDefault) {\n const xDefault = resolveXDefaultForLanguages({\n canonical,\n languages,\n baseUrl: input.baseUrl,\n strategy: input.xDefaultStrategy ?? { type: \"loc\" },\n trailingSlash,\n });\n languages[\"x-default\"] = xDefault;\n }\n\n return { canonical, languages };\n}\n\nexport function resolveXDefaultForLanguages(args: {\n canonical: string;\n languages: Record<string, string>;\n baseUrl: string;\n strategy: XDefaultStrategy;\n trailingSlash: \"preserve\" | \"always\" | \"never\";\n}): string {\n const { canonical, languages, baseUrl, strategy, trailingSlash } = args;\n\n if (strategy.type === \"loc\") return normalizeUrl(canonical, trailingSlash);\n if (strategy.type === \"root\") return normalizeUrl(resolveAbsoluteUrl(\"/\", baseUrl), trailingSlash);\n if (strategy.type === \"custom\") return normalizeUrl(resolveAbsoluteUrl(strategy.url, baseUrl), trailingSlash);\n if (strategy.type === \"locale\") {\n const href = languages[strategy.locale];\n if (href) return normalizeUrl(href, trailingSlash);\n return normalizeUrl(canonical, trailingSlash);\n }\n const href = strategy.resolve({ url: canonical });\n return normalizeUrl(resolveAbsoluteUrl(href, baseUrl), trailingSlash);\n}\n","import type {\n HreflangReport,\n SitemapEntryLike,\n XDefaultStrategy,\n RoutingStrategy,\n} from \"./types.js\";\nimport { assertHreflang as assertHreflangImpl } from \"./assertHreflang.js\";\nimport { buildLanguagesMap, resolveXDefaultForLanguages } from \"./buildLanguagesMap.js\";\nimport { normalizeUrl, resolveAbsoluteUrl } from \"./url.js\";\n\nexport { normalizeUrl, resolveAbsoluteUrl };\nexport { buildLanguagesMap };\n\nexport type WithHreflangOptions = {\n baseUrl?: string;\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n ensureAbsolute?: boolean;\n ensureXDefault?: boolean;\n ensureSelf?: boolean;\n canonicalLocale?: string;\n xDefaultStrategy?: XDefaultStrategy;\n shouldApply?: (entry: SitemapEntryLike) => boolean;\n};\n\nexport function withHreflang<T extends SitemapEntryLike>(\n entries: readonly T[],\n options: WithHreflangOptions,\n): T[] {\n const trailingSlash = options.trailingSlash ?? \"preserve\";\n const ensureAbsolute = options.ensureAbsolute ?? true;\n const ensureXDefault = options.ensureXDefault ?? true;\n const ensureSelf = options.ensureSelf ?? false;\n\n return entries.map((entry) => {\n if (options.shouldApply && !options.shouldApply(entry)) return entry;\n\n const url = normalizeEntryUrl(entry.url, options.baseUrl, ensureAbsolute, trailingSlash);\n\n const languages = entry.alternates?.languages\n ? normalizeLanguages(entry.alternates.languages, options.baseUrl, ensureAbsolute, trailingSlash)\n : undefined;\n\n if (!languages) {\n return { ...entry, url } as T;\n }\n\n const nextLanguages = { ...languages };\n\n if (ensureSelf && options.canonicalLocale) {\n if (!nextLanguages[options.canonicalLocale]) nextLanguages[options.canonicalLocale] = url;\n }\n\n if (ensureXDefault) {\n if (!nextLanguages[\"x-default\"]) {\n const xDefault = resolveXDefaultForLanguages({\n canonical: url,\n languages: nextLanguages,\n baseUrl: options.baseUrl ?? url,\n strategy: options.xDefaultStrategy ?? { type: \"loc\" },\n trailingSlash,\n });\n nextLanguages[\"x-default\"] = xDefault;\n }\n }\n\n const alternates = { ...(entry.alternates ?? {}), languages: nextLanguages };\n\n return { ...entry, url, alternates } as T;\n });\n}\n\nfunction normalizeEntryUrl(\n url: string,\n baseUrl: string | undefined,\n ensureAbsolute: boolean,\n trailingSlash: \"preserve\" | \"always\" | \"never\",\n): string {\n const abs = ensureAbsolute ? resolveAbsoluteUrl(url, baseUrl ?? url) : url;\n return normalizeUrl(abs, trailingSlash);\n}\n\nfunction normalizeLanguages(\n languages: Record<string, string>,\n baseUrl: string | undefined,\n ensureAbsolute: boolean,\n trailingSlash: \"preserve\" | \"always\" | \"never\",\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(languages)) {\n const abs = ensureAbsolute ? resolveAbsoluteUrl(v, baseUrl ?? v) : v;\n out[k] = normalizeUrl(abs, trailingSlash);\n }\n return out;\n}\n\nexport type AssertHreflangOptions = {\n requireAbsolute?: boolean;\n requireXDefaultWhenMultiple?: boolean;\n requireSelf?: boolean;\n canonicalLocale?: string;\n};\n\nexport function assertHreflangExport(entries: readonly SitemapEntryLike[], options: AssertHreflangOptions): HreflangReport {\n return assertHreflangImpl(entries, options);\n}\n\n/**\n * Build hreflang entries using a routing strategy.\n * This is the recommended way to use withHreflang with i18n routing.\n */\nexport function withHreflangFromRouting<T extends SitemapEntryLike>(\n entries: readonly T[],\n strategy: RoutingStrategy,\n options: {\n baseUrl: string;\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n ensureAbsolute?: boolean;\n ensureXDefault?: boolean;\n ensureSelf?: boolean;\n shouldApply?: (entry: SitemapEntryLike) => boolean;\n },\n): T[] {\n const trailingSlash = options.trailingSlash ?? \"preserve\";\n const ensureAbsolute = options.ensureAbsolute ?? true;\n const ensureXDefault = options.ensureXDefault ?? true;\n const ensureSelf = options.ensureSelf ?? false;\n\n return entries.map((entry) => {\n if (options.shouldApply && !options.shouldApply(entry)) return entry;\n\n // Extract pathname from URL\n let pathname: string;\n try {\n const url = new URL(entry.url, options.baseUrl);\n pathname = url.pathname;\n } catch {\n pathname = entry.url;\n }\n\n // Build languages map from routing strategy\n const languages: Record<string, string> = {};\n for (const locale of strategy.locales) {\n const href = strategy.hrefFor({ pathname, locale });\n const abs = ensureAbsolute ? resolveAbsoluteUrl(href, options.baseUrl) : href;\n languages[locale] = normalizeUrl(abs, trailingSlash);\n }\n\n // Add x-default if needed\n if (ensureXDefault && !languages[\"x-default\"]) {\n const canonical =\n languages[strategy.canonicalLocale] ?? normalizeUrl(entry.url, trailingSlash);\n const xDefault = resolveXDefaultForLanguages({\n canonical,\n languages,\n baseUrl: options.baseUrl,\n strategy: strategy.xDefault ?? { type: \"loc\" },\n trailingSlash,\n });\n languages[\"x-default\"] = xDefault;\n }\n\n // Ensure self for canonical locale\n if (ensureSelf && !languages[strategy.canonicalLocale]) {\n const url = normalizeEntryUrl(entry.url, options.baseUrl, ensureAbsolute, trailingSlash);\n languages[strategy.canonicalLocale] = url;\n }\n\n const alternates = { ...(entry.alternates ?? {}), languages };\n\n return { ...entry, alternates } as T;\n });\n}\n","import type {\n RoutingStrategy,\n RoutingPrefixAsNeededOptions,\n RoutingPrefixAlwaysOptions,\n RoutingDomainBasedOptions,\n RoutingSuffixLocaleOptions,\n RoutingPAS7Options,\n} from \"./types.js\";\n\n/**\n * Prefix-as-needed routing: default locale has no prefix, others have /locale\n * Example: /about (en), /uk/about (uk), /de/about (de)\n *\n * Compatible with next-intl's prefix-as-needed strategy.\n */\nexport function routingPrefixAsNeeded(\n options: RoutingPrefixAsNeededOptions,\n): RoutingStrategy {\n const { defaultLocale, locales, basePath = \"\" } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n const base = basePath || \"\";\n\n if (locale === defaultLocale) {\n // Default locale - no prefix\n return `${base}${normalizedPath}`;\n }\n // Other locales - add prefix\n return `${base}/${locale}${normalizedPath}`;\n },\n };\n}\n\n/**\n * Prefix-always routing: all locales have /locale prefix\n * Example: /en/about, /uk/about, /de/about\n *\n * Compatible with next-intl's prefix-always strategy.\n */\nexport function routingPrefixAlways(\n options: RoutingPrefixAlwaysOptions,\n): RoutingStrategy {\n const { defaultLocale, locales, basePath = \"\" } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n const base = basePath || \"\";\n return `${base}/${locale}${normalizedPath}`;\n },\n };\n}\n\n/**\n * Domain-based routing: each locale has its own domain\n * Example: example.com (en), example.de (de), example.ua (uk)\n *\n * Compatible with next-intl's domain-based routing.\n */\nexport function routingDomainBased(\n options: RoutingDomainBasedOptions,\n): RoutingStrategy {\n const { defaultLocale, locales, localeToDomain } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const domain = localeToDomain[locale];\n if (!domain) {\n // Fallback to default domain with prefix\n const defaultDomain = localeToDomain[defaultLocale] || \"\";\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n return `${defaultDomain}/${locale}${normalizedPath}`;\n }\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n return `${domain}${normalizedPath}`;\n },\n };\n}\n\n/**\n * Suffix-locale routing: locale added as suffix to path\n * Example: /blog (en), /blog/uk (uk), /blog/de (de)\n *\n * Common for blogs and content sections.\n */\nexport function routingSuffixLocale(\n options: RoutingSuffixLocaleOptions,\n): RoutingStrategy {\n const { defaultLocale, locales, basePath = \"\" } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n // Remove trailing slash for consistent handling\n const path =\n normalizedPath.endsWith(\"/\") && normalizedPath !== \"/\"\n ? normalizedPath.slice(0, -1)\n : normalizedPath;\n const base = basePath || \"\";\n\n if (locale === defaultLocale) {\n // Default locale - no suffix\n return `${base}${path}`;\n }\n // Other locales - add suffix\n return `${base}${path}/${locale}`;\n },\n };\n}\n\n/**\n * Custom routing with user-defined function\n */\nexport function routingCustom(options: {\n locales: readonly string[];\n canonicalLocale: string;\n hrefFor: (args: { pathname: string; locale: string }) => string;\n xDefault?: RoutingStrategy[\"xDefault\"];\n}): RoutingStrategy {\n if (options.xDefault !== undefined) {\n return {\n locales: options.locales,\n canonicalLocale: options.canonicalLocale,\n hrefFor: options.hrefFor,\n xDefault: options.xDefault,\n };\n }\n return {\n locales: options.locales,\n canonicalLocale: options.canonicalLocale,\n hrefFor: options.hrefFor,\n };\n}\n\n/**\n * PAS7 custom routing strategy:\n * - Home: / (en), /uk, /de, /it, /hr\n * - Hubs: /blog (en), /blog/uk, /blog/de\n * - Details: /blog/en/<slug>, /blog/uk/<slug>\n *\n * This is a hybrid routing scheme where:\n * - Home pages use prefix pattern for non-default locales\n * - Hub pages (listing pages) use suffix pattern for non-default locales\n * - Detail pages have locale as a segment after the section\n */\nexport function routingPAS7(options: RoutingPAS7Options): RoutingStrategy {\n const {\n defaultLocale,\n locales,\n hubPaths = [\"/blog\", \"/projects\", \"/services\", \"/cases\"],\n detailPathPattern = /^\\/(blog|projects|services|cases)\\//,\n } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n // Normalize pathname\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n\n // Home page\n if (normalizedPath === \"/\" || normalizedPath === \"\") {\n if (locale === defaultLocale) return \"/\";\n return `/${locale}`;\n }\n\n // Detail pages - locale is segment after section\n // /blog/en/slug → /blog/uk/slug\n if (detailPathPattern.test(normalizedPath)) {\n const parts = normalizedPath.split(\"/\").filter(Boolean);\n if (parts.length >= 3) {\n // parts[0] = section, parts[1] = locale, parts[2+] = slug\n const section = parts[0];\n const slug = parts.slice(2).join(\"/\");\n return `/${section}/${locale}/${slug}`;\n }\n }\n\n // Hub pages - locale as suffix\n // /blog → /blog/uk\n const matchingHub = hubPaths.find((hub) => {\n const normalizedHub = hub.startsWith(\"/\") ? hub : `/${hub}`;\n return normalizedPath === normalizedHub || normalizedPath === `${normalizedHub}/`;\n });\n if (matchingHub) {\n const normalizedHub = matchingHub.startsWith(\"/\")\n ? matchingHub.replace(/\\/$/, \"\")\n : `/${matchingHub.replace(/\\/$/, \"\")}`;\n if (locale === defaultLocale) return normalizedHub;\n return `${normalizedHub}/${locale}`;\n }\n\n // Fallback: prefix-as-needed style\n if (locale === defaultLocale) return normalizedPath;\n return `/${locale}${normalizedPath}`;\n },\n };\n}\n","import type { SitemapEntryLike } from \"./types.js\";\n\nexport type ExpandLocalesOptions = {\n mode: \"cluster-only\" | \"expanded\";\n includeXDefault?: boolean;\n locales?: readonly string[];\n};\n\nexport function expandLocaleEntries<T extends SitemapEntryLike>(\n entries: readonly T[],\n options: ExpandLocalesOptions,\n): T[] {\n if (options.mode === \"cluster-only\") {\n return [...entries];\n }\n\n const result: T[] = [];\n\n for (const entry of entries) {\n const languages = entry.alternates?.languages;\n if (!languages || Object.keys(languages).length === 0) {\n result.push(entry);\n continue;\n }\n\n // Create a separate entry for each locale\n for (const [locale, href] of Object.entries(languages)) {\n if (locale === \"x-default\" && !options.includeXDefault) continue;\n\n // Filter by specific locales if provided\n if (options.locales && !options.locales.includes(locale)) continue;\n\n const expandedEntry: T = {\n ...entry,\n url: href,\n // alternates.languages remains the full cluster\n };\n result.push(expandedEntry);\n }\n }\n\n // Dedupe by url\n const seen = new Set<string>();\n return result.filter((e) => {\n if (seen.has(e.url)) return false;\n seen.add(e.url);\n return true;\n });\n}\n","import type { ChangeFrequency, SitemapEntry, XDefaultStrategy } from \"./types.js\";\nimport { withHreflang } from \"./withHreflang.js\";\nimport { normalizeUrl, resolveAbsoluteUrl } from \"./url.js\";\n\nexport type ManifestRouteStyle =\n | \"prefix-as-needed\"\n | \"prefix-always\"\n | \"suffix-locale\"\n | \"locale-segment\";\n\nexport type LocalizedManifestItem = {\n slug: string;\n locales: readonly string[];\n updatedAt?: string | Date;\n publishedAt?: string | Date;\n lastModified?: string | Date;\n date?: string | Date;\n changeFrequency?: ChangeFrequency;\n priority?: number;\n images?: string[];\n};\n\nexport type CreateSitemapEntriesFromManifestOptions = {\n baseUrl: string;\n sectionPath: string;\n defaultLocale: string;\n canonicalLocale?: string;\n routeStyle?: ManifestRouteStyle;\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n ensureXDefault?: boolean;\n xDefaultStrategy?: XDefaultStrategy;\n includeLocales?: readonly string[];\n pathnameFor?: (args: {\n slug: string;\n locale: string;\n defaultLocale: string;\n sectionPath: string;\n routeStyle: ManifestRouteStyle;\n }) => string;\n};\n\nexport function createSitemapEntriesFromManifest<T extends LocalizedManifestItem>(\n items: readonly T[],\n options: CreateSitemapEntriesFromManifestOptions,\n): SitemapEntry[] {\n const routeStyle = options.routeStyle ?? \"locale-segment\";\n const includeXDefault = options.ensureXDefault ?? true;\n const trailingSlash = options.trailingSlash ?? \"preserve\";\n const includeLocales = options.includeLocales ? new Set(options.includeLocales) : null;\n\n const entries: SitemapEntry[] = [];\n\n for (const item of items) {\n const slug = normalizeSlug(item.slug);\n if (!slug) continue;\n\n const locales = unique(item.locales).filter((locale) => (includeLocales ? includeLocales.has(locale) : true));\n if (locales.length === 0) continue;\n\n const configuredCanonical = options.canonicalLocale ?? options.defaultLocale;\n const effectiveCanonicalLocale =\n locales.includes(configuredCanonical) ? configuredCanonical : locales[0] ?? configuredCanonical;\n\n const languages: Record<string, string> = {};\n for (const locale of locales) {\n const pathname =\n options.pathnameFor?.({\n slug,\n locale,\n defaultLocale: options.defaultLocale,\n sectionPath: options.sectionPath,\n routeStyle,\n }) ??\n buildPathname({\n slug,\n locale,\n defaultLocale: options.defaultLocale,\n sectionPath: options.sectionPath,\n routeStyle,\n });\n\n const href = normalizeUrl(resolveAbsoluteUrl(pathname, options.baseUrl), trailingSlash);\n languages[locale] = href;\n }\n\n const canonicalUrl =\n languages[effectiveCanonicalLocale] ??\n normalizeUrl(resolveAbsoluteUrl(\"/\", options.baseUrl), trailingSlash);\n\n const lastModified = pickLastModified(item);\n\n const baseEntry: SitemapEntry = {\n url: canonicalUrl,\n alternates: { languages },\n ...(lastModified ? { lastModified } : {}),\n ...(item.changeFrequency ? { changeFrequency: item.changeFrequency } : {}),\n ...(typeof item.priority === \"number\" ? { priority: item.priority } : {}),\n ...(item.images ? { images: [...item.images] } : {}),\n };\n\n const [entryWithHreflang] = withHreflang([baseEntry], {\n baseUrl: options.baseUrl,\n trailingSlash,\n ensureAbsolute: true,\n ensureXDefault: includeXDefault,\n ensureSelf: true,\n canonicalLocale: effectiveCanonicalLocale,\n ...(options.xDefaultStrategy ? { xDefaultStrategy: options.xDefaultStrategy } : {}),\n });\n\n if (entryWithHreflang) entries.push(entryWithHreflang);\n }\n\n return entries;\n}\n\nfunction buildPathname(args: {\n slug: string;\n locale: string;\n defaultLocale: string;\n sectionPath: string;\n routeStyle: ManifestRouteStyle;\n}): string {\n const section = normalizeSection(args.sectionPath);\n\n if (args.routeStyle === \"prefix-as-needed\") {\n if (args.locale === args.defaultLocale) return joinPath(section, args.slug);\n return joinPath(`/${args.locale}`, section, args.slug);\n }\n\n if (args.routeStyle === \"prefix-always\") {\n return joinPath(`/${args.locale}`, section, args.slug);\n }\n\n if (args.routeStyle === \"suffix-locale\") {\n if (args.locale === args.defaultLocale) return joinPath(section, args.slug);\n return joinPath(section, args.slug, args.locale);\n }\n\n return joinPath(section, args.locale, args.slug);\n}\n\nfunction normalizeSlug(slug: string): string {\n return slug.trim().replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n}\n\nfunction normalizeSection(sectionPath: string): string {\n const normalized = sectionPath.trim();\n if (!normalized || normalized === \"/\") return \"\";\n return `/${normalized.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\")}`;\n}\n\nfunction joinPath(...parts: string[]): string {\n const cleaned = parts\n .map((part) => part.trim())\n .filter(Boolean)\n .map((part) => part.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\"));\n\n if (cleaned.length === 0) return \"/\";\n return `/${cleaned.join(\"/\")}`;\n}\n\nfunction unique(values: readonly string[]): string[] {\n return [...new Set(values)];\n}\n\nfunction pickLastModified(item: LocalizedManifestItem): string | Date | undefined {\n return item.lastModified ?? item.updatedAt ?? item.publishedAt ?? item.date;\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":";AASO,SAAS,eACd,SACA,SACgB;AAChB,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,8BAA8B,QAAQ,+BAA+B;AAC3E,QAAM,cAAc,QAAQ,eAAe;AAE3C,QAAM,SAA0B,CAAC;AAEjC,aAAW,SAAS,SAAS;AAC3B,UAAM,YAAY,MAAM,YAAY;AACpC,QAAI,CAAC,UAAW;AAEhB,UAAM,QAAQ,OAAO,QAAQ,SAAS;AAEtC,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,UAAU,MAAM;AAAA,QAChB,SAAS;AAAA,MACX,CAAC;AACD;AAAA,IACF;AAEA,QAAI,+BAA+B,MAAM,SAAS,KAAK,CAAC,UAAU,WAAW,GAAG;AAC9E,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,UAAU,MAAM;AAAA,QAChB,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAEA,QAAI,eAAe,QAAQ,iBAAiB;AAC1C,UAAI,CAAC,UAAU,QAAQ,eAAe,GAAG;AACvC,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU,MAAM;AAAA,UAChB,SAAS,6CAA6C,QAAQ,eAAe;AAAA,QAC/E,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,WAAW,oBAAI,IAAY;AACjC,eAAW,CAAC,QAAQ,IAAI,KAAK,OAAO;AAClC,UAAI,CAAC,iBAAiB,MAAM,GAAG;AAC7B,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU,MAAM;AAAA,UAChB,SAAS,uBAAuB,MAAM;AAAA,QACxC,CAAC;AAAA,MACH;AACA,UAAI,mBAAmB,CAAC,cAAc,IAAI,GAAG;AAC3C,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU,MAAM;AAAA,UAChB,SAAS,kCAAkC,MAAM,KAAK,IAAI;AAAA,QAC5D,CAAC;AAAA,MACH;AACA,UAAI,SAAS,IAAI,IAAI,GAAG;AACtB,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU,MAAM;AAAA,UAChB,SAAS,qCAAqC,IAAI;AAAA,QACpD,CAAC;AAAA,MACH;AACA,eAAS,IAAI,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,OAAO,WAAW,GAAG,OAAO;AAC3C;AAEA,SAAS,cAAc,KAAsB;AAC3C,SAAO,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU;AAC/D;AAEA,SAAS,iBAAiB,KAAsB;AAC9C,MAAI,QAAQ,YAAa,QAAO;AAChC,SAAO,yBAAyB,KAAK,GAAG;AAC1C;;;ACvFO,SAAS,aAAa,KAAa,eAA4C;AACpF,QAAM,IAAI,IAAI,IAAI,GAAG;AACrB,MAAI,kBAAkB,WAAY,QAAO,EAAE,SAAS;AACpD,MAAI,kBAAkB,UAAU;AAC9B,QAAI,CAAC,EAAE,SAAS,SAAS,GAAG,EAAG,GAAE,WAAW,GAAG,EAAE,QAAQ;AACzD,WAAO,EAAE,SAAS;AAAA,EACpB;AACA,MAAI,EAAE,aAAa,OAAO,EAAE,SAAS,SAAS,GAAG,EAAG,GAAE,WAAW,EAAE,SAAS,MAAM,GAAG,EAAE;AACvF,SAAO,EAAE,SAAS;AACpB;AAEO,SAAS,mBAAmB,OAAe,SAAyB;AACzE,MAAI,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,UAAU,EAAG,QAAO;AACxE,SAAO,IAAI,IAAI,MAAM,WAAW,GAAG,IAAI,QAAQ,IAAI,KAAK,IAAI,OAAO,EAAE,SAAS;AAChF;AAEO,SAAS,yBAAyB,aAA6B;AACpE,QAAM,IAAI,IAAI,IAAI,WAAW;AAC7B,SAAO,GAAG,EAAE,QAAQ,KAAK,EAAE,IAAI;AACjC;;;ACHO,SAAS,kBAAkB,OAAwD;AACxF,QAAM,gBAAgB,MAAM,iBAAiB;AAC7C,QAAM,YAAoC,CAAC;AAE3C,aAAW,UAAU,MAAM,SAAS;AAClC,UAAM,OAAO,MAAM,YAAY,MAAM;AACrC,UAAM,MAAM,mBAAmB,MAAM,MAAM,OAAO;AAClD,cAAU,MAAM,IAAI,aAAa,KAAK,aAAa;AAAA,EACrD;AAEA,QAAM,YAAY,UAAU,MAAM,eAAe,KAAK,aAAa,MAAM,SAAS,aAAa;AAE/F,MAAI,MAAM,iBAAiB;AACzB,UAAM,WAAW,4BAA4B;AAAA,MAC3C;AAAA,MACA;AAAA,MACA,SAAS,MAAM;AAAA,MACf,UAAU,MAAM,oBAAoB,EAAE,MAAM,MAAM;AAAA,MAClD;AAAA,IACF,CAAC;AACD,cAAU,WAAW,IAAI;AAAA,EAC3B;AAEA,SAAO,EAAE,WAAW,UAAU;AAChC;AAEO,SAAS,4BAA4B,MAMjC;AACT,QAAM,EAAE,WAAW,WAAW,SAAS,UAAU,cAAc,IAAI;AAEnE,MAAI,SAAS,SAAS,MAAO,QAAO,aAAa,WAAW,aAAa;AACzE,MAAI,SAAS,SAAS,OAAQ,QAAO,aAAa,mBAAmB,KAAK,OAAO,GAAG,aAAa;AACjG,MAAI,SAAS,SAAS,SAAU,QAAO,aAAa,mBAAmB,SAAS,KAAK,OAAO,GAAG,aAAa;AAC5G,MAAI,SAAS,SAAS,UAAU;AAC9B,UAAMA,QAAO,UAAU,SAAS,MAAM;AACtC,QAAIA,MAAM,QAAO,aAAaA,OAAM,aAAa;AACjD,WAAO,aAAa,WAAW,aAAa;AAAA,EAC9C;AACA,QAAM,OAAO,SAAS,QAAQ,EAAE,KAAK,UAAU,CAAC;AAChD,SAAO,aAAa,mBAAmB,MAAM,OAAO,GAAG,aAAa;AACtE;;;ACvCO,SAAS,aACd,SACA,SACK;AACL,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,iBAAiB,QAAQ,kBAAkB;AACjD,QAAM,iBAAiB,QAAQ,kBAAkB;AACjD,QAAM,aAAa,QAAQ,cAAc;AAEzC,SAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,QAAI,QAAQ,eAAe,CAAC,QAAQ,YAAY,KAAK,EAAG,QAAO;AAE/D,UAAM,MAAM,kBAAkB,MAAM,KAAK,QAAQ,SAAS,gBAAgB,aAAa;AAEvF,UAAM,YAAY,MAAM,YAAY,YAChC,mBAAmB,MAAM,WAAW,WAAW,QAAQ,SAAS,gBAAgB,aAAa,IAC7F;AAEJ,QAAI,CAAC,WAAW;AACd,aAAO,EAAE,GAAG,OAAO,IAAI;AAAA,IACzB;AAEA,UAAM,gBAAgB,EAAE,GAAG,UAAU;AAErC,QAAI,cAAc,QAAQ,iBAAiB;AACzC,UAAI,CAAC,cAAc,QAAQ,eAAe,EAAG,eAAc,QAAQ,eAAe,IAAI;AAAA,IACxF;AAEA,QAAI,gBAAgB;AAClB,UAAI,CAAC,cAAc,WAAW,GAAG;AAC/B,cAAM,WAAW,4BAA4B;AAAA,UAC3C,WAAW;AAAA,UACX,WAAW;AAAA,UACX,SAAS,QAAQ,WAAW;AAAA,UAC5B,UAAU,QAAQ,oBAAoB,EAAE,MAAM,MAAM;AAAA,UACpD;AAAA,QACF,CAAC;AACD,sBAAc,WAAW,IAAI;AAAA,MAC/B;AAAA,IACF;AAEA,UAAM,aAAa,EAAE,GAAI,MAAM,cAAc,CAAC,GAAI,WAAW,cAAc;AAE3E,WAAO,EAAE,GAAG,OAAO,KAAK,WAAW;AAAA,EACrC,CAAC;AACH;AAEA,SAAS,kBACP,KACA,SACA,gBACA,eACQ;AACR,QAAM,MAAM,iBAAiB,mBAAmB,KAAK,WAAW,GAAG,IAAI;AACvE,SAAO,aAAa,KAAK,aAAa;AACxC;AAEA,SAAS,mBACP,WACA,SACA,gBACA,eACwB;AACxB,QAAM,MAA8B,CAAC;AACrC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC9C,UAAM,MAAM,iBAAiB,mBAAmB,GAAG,WAAW,CAAC,IAAI;AACnE,QAAI,CAAC,IAAI,aAAa,KAAK,aAAa;AAAA,EAC1C;AACA,SAAO;AACT;AASO,SAAS,qBAAqB,SAAsC,SAAgD;AACzH,SAAO,eAAmB,SAAS,OAAO;AAC5C;AAMO,SAAS,wBACd,SACA,UACA,SAQK;AACL,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,iBAAiB,QAAQ,kBAAkB;AACjD,QAAM,iBAAiB,QAAQ,kBAAkB;AACjD,QAAM,aAAa,QAAQ,cAAc;AAEzC,SAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,QAAI,QAAQ,eAAe,CAAC,QAAQ,YAAY,KAAK,EAAG,QAAO;AAG/D,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,MAAM,KAAK,QAAQ,OAAO;AAC9C,iBAAW,IAAI;AAAA,IACjB,QAAQ;AACN,iBAAW,MAAM;AAAA,IACnB;AAGA,UAAM,YAAoC,CAAC;AAC3C,eAAW,UAAU,SAAS,SAAS;AACrC,YAAM,OAAO,SAAS,QAAQ,EAAE,UAAU,OAAO,CAAC;AAClD,YAAM,MAAM,iBAAiB,mBAAmB,MAAM,QAAQ,OAAO,IAAI;AACzE,gBAAU,MAAM,IAAI,aAAa,KAAK,aAAa;AAAA,IACrD;AAGA,QAAI,kBAAkB,CAAC,UAAU,WAAW,GAAG;AAC7C,YAAM,YACJ,UAAU,SAAS,eAAe,KAAK,aAAa,MAAM,KAAK,aAAa;AAC9E,YAAM,WAAW,4BAA4B;AAAA,QAC3C;AAAA,QACA;AAAA,QACA,SAAS,QAAQ;AAAA,QACjB,UAAU,SAAS,YAAY,EAAE,MAAM,MAAM;AAAA,QAC7C;AAAA,MACF,CAAC;AACD,gBAAU,WAAW,IAAI;AAAA,IAC3B;AAGA,QAAI,cAAc,CAAC,UAAU,SAAS,eAAe,GAAG;AACtD,YAAM,MAAM,kBAAkB,MAAM,KAAK,QAAQ,SAAS,gBAAgB,aAAa;AACvF,gBAAU,SAAS,eAAe,IAAI;AAAA,IACxC;AAEA,UAAM,aAAa,EAAE,GAAI,MAAM,cAAc,CAAC,GAAI,UAAU;AAE5D,WAAO,EAAE,GAAG,OAAO,WAAW;AAAA,EAChC,CAAC;AACH;;;AC5JO,SAAS,sBACd,SACiB;AACjB,QAAM,EAAE,eAAe,SAAS,WAAW,GAAG,IAAI;AAElD,SAAO;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,IACjB,SAAS,CAAC,EAAE,UAAU,OAAO,MAAM;AACjC,YAAM,iBAAiB,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AACzE,YAAM,OAAO,YAAY;AAEzB,UAAI,WAAW,eAAe;AAE5B,eAAO,GAAG,IAAI,GAAG,cAAc;AAAA,MACjC;AAEA,aAAO,GAAG,IAAI,IAAI,MAAM,GAAG,cAAc;AAAA,IAC3C;AAAA,EACF;AACF;AAQO,SAAS,oBACd,SACiB;AACjB,QAAM,EAAE,eAAe,SAAS,WAAW,GAAG,IAAI;AAElD,SAAO;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,IACjB,SAAS,CAAC,EAAE,UAAU,OAAO,MAAM;AACjC,YAAM,iBAAiB,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AACzE,YAAM,OAAO,YAAY;AACzB,aAAO,GAAG,IAAI,IAAI,MAAM,GAAG,cAAc;AAAA,IAC3C;AAAA,EACF;AACF;AAQO,SAAS,mBACd,SACiB;AACjB,QAAM,EAAE,eAAe,SAAS,eAAe,IAAI;AAEnD,SAAO;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,IACjB,SAAS,CAAC,EAAE,UAAU,OAAO,MAAM;AACjC,YAAM,SAAS,eAAe,MAAM;AACpC,UAAI,CAAC,QAAQ;AAEX,cAAM,gBAAgB,eAAe,aAAa,KAAK;AACvD,cAAMC,kBAAiB,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AACzE,eAAO,GAAG,aAAa,IAAI,MAAM,GAAGA,eAAc;AAAA,MACpD;AACA,YAAM,iBAAiB,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AACzE,aAAO,GAAG,MAAM,GAAG,cAAc;AAAA,IACnC;AAAA,EACF;AACF;AAQO,SAAS,oBACd,SACiB;AACjB,QAAM,EAAE,eAAe,SAAS,WAAW,GAAG,IAAI;AAElD,SAAO;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,IACjB,SAAS,CAAC,EAAE,UAAU,OAAO,MAAM;AACjC,YAAM,iBAAiB,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AAEzE,YAAM,OACJ,eAAe,SAAS,GAAG,KAAK,mBAAmB,MAC/C,eAAe,MAAM,GAAG,EAAE,IAC1B;AACN,YAAM,OAAO,YAAY;AAEzB,UAAI,WAAW,eAAe;AAE5B,eAAO,GAAG,IAAI,GAAG,IAAI;AAAA,MACvB;AAEA,aAAO,GAAG,IAAI,GAAG,IAAI,IAAI,MAAM;AAAA,IACjC;AAAA,EACF;AACF;AAKO,SAAS,cAAc,SAKV;AAClB,MAAI,QAAQ,aAAa,QAAW;AAClC,WAAO;AAAA,MACL,SAAS,QAAQ;AAAA,MACjB,iBAAiB,QAAQ;AAAA,MACzB,SAAS,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS,QAAQ;AAAA,IACjB,iBAAiB,QAAQ;AAAA,IACzB,SAAS,QAAQ;AAAA,EACnB;AACF;AAaO,SAAS,YAAY,SAA8C;AACxE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,WAAW,CAAC,SAAS,aAAa,aAAa,QAAQ;AAAA,IACvD,oBAAoB;AAAA,EACtB,IAAI;AAEJ,SAAO;AAAA,IACL;AAAA,IACA,iBAAiB;AAAA,IACjB,SAAS,CAAC,EAAE,UAAU,OAAO,MAAM;AAEjC,YAAM,iBAAiB,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AAGzE,UAAI,mBAAmB,OAAO,mBAAmB,IAAI;AACnD,YAAI,WAAW,cAAe,QAAO;AACrC,eAAO,IAAI,MAAM;AAAA,MACnB;AAIA,UAAI,kBAAkB,KAAK,cAAc,GAAG;AAC1C,cAAM,QAAQ,eAAe,MAAM,GAAG,EAAE,OAAO,OAAO;AACtD,YAAI,MAAM,UAAU,GAAG;AAErB,gBAAM,UAAU,MAAM,CAAC;AACvB,gBAAM,OAAO,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG;AACpC,iBAAO,IAAI,OAAO,IAAI,MAAM,IAAI,IAAI;AAAA,QACtC;AAAA,MACF;AAIA,YAAM,cAAc,SAAS,KAAK,CAAC,QAAQ;AACzC,cAAM,gBAAgB,IAAI,WAAW,GAAG,IAAI,MAAM,IAAI,GAAG;AACzD,eAAO,mBAAmB,iBAAiB,mBAAmB,GAAG,aAAa;AAAA,MAChF,CAAC;AACD,UAAI,aAAa;AACf,cAAM,gBAAgB,YAAY,WAAW,GAAG,IAC5C,YAAY,QAAQ,OAAO,EAAE,IAC7B,IAAI,YAAY,QAAQ,OAAO,EAAE,CAAC;AACtC,YAAI,WAAW,cAAe,QAAO;AACrC,eAAO,GAAG,aAAa,IAAI,MAAM;AAAA,MACnC;AAGA,UAAI,WAAW,cAAe,QAAO;AACrC,aAAO,IAAI,MAAM,GAAG,cAAc;AAAA,IACpC;AAAA,EACF;AACF;;;ACvMO,SAAS,oBACd,SACA,SACK;AACL,MAAI,QAAQ,SAAS,gBAAgB;AACnC,WAAO,CAAC,GAAG,OAAO;AAAA,EACpB;AAEA,QAAM,SAAc,CAAC;AAErB,aAAW,SAAS,SAAS;AAC3B,UAAM,YAAY,MAAM,YAAY;AACpC,QAAI,CAAC,aAAa,OAAO,KAAK,SAAS,EAAE,WAAW,GAAG;AACrD,aAAO,KAAK,KAAK;AACjB;AAAA,IACF;AAGA,eAAW,CAAC,QAAQ,IAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AACtD,UAAI,WAAW,eAAe,CAAC,QAAQ,gBAAiB;AAGxD,UAAI,QAAQ,WAAW,CAAC,QAAQ,QAAQ,SAAS,MAAM,EAAG;AAE1D,YAAM,gBAAmB;AAAA,QACvB,GAAG;AAAA,QACH,KAAK;AAAA;AAAA,MAEP;AACA,aAAO,KAAK,aAAa;AAAA,IAC3B;AAAA,EACF;AAGA,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,OAAO,OAAO,CAAC,MAAM;AAC1B,QAAI,KAAK,IAAI,EAAE,GAAG,EAAG,QAAO;AAC5B,SAAK,IAAI,EAAE,GAAG;AACd,WAAO;AAAA,EACT,CAAC;AACH;;;ACPO,SAAS,iCACd,OACA,SACgB;AAChB,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,kBAAkB,QAAQ,kBAAkB;AAClD,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,iBAAiB,QAAQ,iBAAiB,IAAI,IAAI,QAAQ,cAAc,IAAI;AAElF,QAAM,UAA0B,CAAC;AAEjC,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,cAAc,KAAK,IAAI;AACpC,QAAI,CAAC,KAAM;AAEX,UAAM,UAAU,OAAO,KAAK,OAAO,EAAE,OAAO,CAAC,WAAY,iBAAiB,eAAe,IAAI,MAAM,IAAI,IAAK;AAC5G,QAAI,QAAQ,WAAW,EAAG;AAE1B,UAAM,sBAAsB,QAAQ,mBAAmB,QAAQ;AAC/D,UAAM,2BACJ,QAAQ,SAAS,mBAAmB,IAAI,sBAAsB,QAAQ,CAAC,KAAK;AAE9E,UAAM,YAAoC,CAAC;AAC3C,eAAW,UAAU,SAAS;AAC5B,YAAM,WACJ,QAAQ,cAAc;AAAA,QACpB;AAAA,QACA;AAAA,QACA,eAAe,QAAQ;AAAA,QACvB,aAAa,QAAQ;AAAA,QACrB;AAAA,MACF,CAAC,KACD,cAAc;AAAA,QACZ;AAAA,QACA;AAAA,QACA,eAAe,QAAQ;AAAA,QACvB,aAAa,QAAQ;AAAA,QACrB;AAAA,MACF,CAAC;AAEH,YAAM,OAAO,aAAa,mBAAmB,UAAU,QAAQ,OAAO,GAAG,aAAa;AACtF,gBAAU,MAAM,IAAI;AAAA,IACtB;AAEA,UAAM,eACJ,UAAU,wBAAwB,KAClC,aAAa,mBAAmB,KAAK,QAAQ,OAAO,GAAG,aAAa;AAEtE,UAAM,eAAe,iBAAiB,IAAI;AAE1C,UAAM,YAA0B;AAAA,MAC9B,KAAK;AAAA,MACL,YAAY,EAAE,UAAU;AAAA,MACxB,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,MACvC,GAAI,KAAK,kBAAkB,EAAE,iBAAiB,KAAK,gBAAgB,IAAI,CAAC;AAAA,MACxE,GAAI,OAAO,KAAK,aAAa,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,MACvE,GAAI,KAAK,SAAS,EAAE,QAAQ,CAAC,GAAG,KAAK,MAAM,EAAE,IAAI,CAAC;AAAA,IACpD;AAEA,UAAM,CAAC,iBAAiB,IAAI,aAAa,CAAC,SAAS,GAAG;AAAA,MACpD,SAAS,QAAQ;AAAA,MACjB;AAAA,MACA,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,GAAI,QAAQ,mBAAmB,EAAE,kBAAkB,QAAQ,iBAAiB,IAAI,CAAC;AAAA,IACnF,CAAC;AAED,QAAI,kBAAmB,SAAQ,KAAK,iBAAiB;AAAA,EACvD;AAEA,SAAO;AACT;AAEA,SAAS,cAAc,MAMZ;AACT,QAAM,UAAU,iBAAiB,KAAK,WAAW;AAEjD,MAAI,KAAK,eAAe,oBAAoB;AAC1C,QAAI,KAAK,WAAW,KAAK,cAAe,QAAO,SAAS,SAAS,KAAK,IAAI;AAC1E,WAAO,SAAS,IAAI,KAAK,MAAM,IAAI,SAAS,KAAK,IAAI;AAAA,EACvD;AAEA,MAAI,KAAK,eAAe,iBAAiB;AACvC,WAAO,SAAS,IAAI,KAAK,MAAM,IAAI,SAAS,KAAK,IAAI;AAAA,EACvD;AAEA,MAAI,KAAK,eAAe,iBAAiB;AACvC,QAAI,KAAK,WAAW,KAAK,cAAe,QAAO,SAAS,SAAS,KAAK,IAAI;AAC1E,WAAO,SAAS,SAAS,KAAK,MAAM,KAAK,MAAM;AAAA,EACjD;AAEA,SAAO,SAAS,SAAS,KAAK,QAAQ,KAAK,IAAI;AACjD;AAEA,SAAS,cAAc,MAAsB;AAC3C,SAAO,KAAK,KAAK,EAAE,QAAQ,QAAQ,EAAE,EAAE,QAAQ,QAAQ,EAAE;AAC3D;AAEA,SAAS,iBAAiB,aAA6B;AACrD,QAAM,aAAa,YAAY,KAAK;AACpC,MAAI,CAAC,cAAc,eAAe,IAAK,QAAO;AAC9C,SAAO,IAAI,WAAW,QAAQ,QAAQ,EAAE,EAAE,QAAQ,QAAQ,EAAE,CAAC;AAC/D;AAEA,SAAS,YAAY,OAAyB;AAC5C,QAAM,UAAU,MACb,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,QAAQ,QAAQ,EAAE,EAAE,QAAQ,QAAQ,EAAE,CAAC;AAE7D,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,IAAI,QAAQ,KAAK,GAAG,CAAC;AAC9B;AAEA,SAAS,OAAO,QAAqC;AACnD,SAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;AAC5B;AAEA,SAAS,iBAAiB,MAAwD;AAChF,SAAO,KAAK,gBAAgB,KAAK,aAAa,KAAK,eAAe,KAAK;AACzE;;;ACxKO,SAAS,UAAU,OAAuB;AAC/C,SAAO,MACJ,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,QAAQ;AAC7B;AAEO,SAAS,kBAAkB,KAAsB;AACtD,SAAO,uEAAuE,KAAK,GAAG;AACxF;AAEO,SAAS,qBAAqB,KAAqB;AACxD,MAAI,kBAAkB,GAAG,EAAG,QAAO;AACnC,SAAO,IAAI;AAAA,IACT;AAAA,IACA,CAAC,MAAO,EAAE,SAAS,cAAc,IAAI,IAAI,EAAE,QAAQ,WAAW,oDAAoD;AAAA,EACpH;AACF;AAEO,SAAS,iBAAiB,KAAuB;AACtD,SAAO,IAAI,MAAM,uBAAuB,KAAK,CAAC;AAChD;AAEO,SAAS,WAAW,UAAiC;AAC1D,SAAO,SAAS,MAAM,qBAAqB,IAAI,CAAC,GAAG,KAAK,KAAK;AAC/D;AAEO,SAAS,kBAAkB,UAA6D;AAC7F,QAAM,MAAiD,CAAC;AACxD,QAAM,KAAK;AACX,aAAW,KAAK,SAAS,SAAS,EAAE,GAAG;AACrC,QAAI,EAAE,CAAC,MAAM,UAAa,EAAE,CAAC,MAAM,QAAW;AAC5C,UAAI,KAAK,EAAE,UAAU,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC;AAAA,IACzC;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,YAAY,UAA2B;AACrD,SAAO,iDAAiD,KAAK,QAAQ;AACvE;AAEO,SAAS,gBAAgB,UAAkB,SAAyB;AACzE,QAAM,cAAc,SAAS,YAAY,aAAa;AACtD,MAAI,gBAAgB,IAAI;AACtB,UAAM,SAAS,SAAS,QAAQ,QAAQ;AACxC,QAAI,WAAW,GAAI,QAAO;AAC1B,UAAM,YAAY,SAAS,SAAS;AACpC,WAAO,GAAG,SAAS,MAAM,GAAG,SAAS,CAAC;AAAA,MAAS,OAAO,GAAG,SAAS,MAAM,SAAS,CAAC;AAAA,EACpF;AACA,QAAM,UAAU,SAAS,QAAQ,MAAM,WAAW;AAClD,MAAI,YAAY,GAAI,QAAO,GAAG,QAAQ;AAAA,MAAS,OAAO;AACtD,SAAO,GAAG,SAAS,MAAM,GAAG,OAAO,CAAC;AAAA,MAAS,OAAO,GAAG,SAAS,MAAM,OAAO,CAAC;AAChF;AAOO,SAAS,kBAAkB,UAAkB,SAAiC;AACnF,MAAI,QAAQ,UAAU,WAAY,QAAO;AAEzC,QAAM,QAAQ,kBAAkB,QAAQ;AACxC,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,MAAM,WAAW,QAAQ;AAG/B,MAAI;AACJ,MAAI,QAAQ,iBAAiB;AAC3B,gBAAY,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ,eAAe;AAAA,EACtE;AACA,MAAI,CAAC,aAAa,KAAK;AACrB,gBAAY,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG;AAAA,EAC9C;AAGA,QAAM,iBAAiB,YAAY,CAAC,SAAS,IAAI,CAAC;AAClD,QAAM,aAAa,MAAM,OAAO,CAAC,MAAM,MAAM,aAAa,EAAE,aAAa,WAAW;AACpF,QAAM,gBAAgB,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,WAAW;AAGpE,QAAM,eAAe,CAAC,GAAG,gBAAgB,GAAG,YAAY,GAAG,aAAa;AAGxE,QAAM,WAAW,SAAS,QAAQ,2BAA2B,EAAE;AAG/D,QAAM,SAAS,SAAS,QAAQ,QAAQ;AACxC,MAAI,WAAW,GAAI,QAAO;AAE1B,QAAM,YAAY,SAAS,SAAS;AACpC,QAAM,WAAW,aACd,IAAI,CAAC,MAAM;AAAA,4CAA+C,UAAU,EAAE,QAAQ,CAAC,WAAW,UAAU,EAAE,IAAI,CAAC,MAAM,EACjH,KAAK,EAAE;AAEV,SAAO,GAAG,SAAS,MAAM,GAAG,SAAS,CAAC,GAAG,QAAQ;AAAA,IAAO,SAAS,MAAM,SAAS,EAAE,UAAU,CAAC;AAC/F;AAEO,SAAS,8BACd,UACA,QACQ;AACR,MAAI,WAAW,WAAY,QAAO;AAGlC,MAAI,SAAS,SAAS,QAAQ,wBAAwB,CAAC,GAAG,QAAgB;AACxE,WAAO,QAAQ,yBAAyB,KAAK,MAAM,CAAC;AAAA,EACtD,CAAC;AAGD,WAAS,OAAO;AAAA,IACd;AAAA,IACA,CAAC,GAAG,QAAgB,KAAa,WAAmB;AAClD,aAAO,GAAG,MAAM,GAAG,yBAAyB,KAAK,MAAM,CAAC,GAAG,MAAM;AAAA,IACnE;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,yBAAyB,KAAa,QAAoC;AACjF,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,QAAI,WAAW,YAAY,CAAC,EAAE,SAAS,SAAS,GAAG,GAAG;AACpD,QAAE,YAAY;AAAA,IAChB,WAAW,WAAW,WAAW,EAAE,aAAa,OAAO,EAAE,SAAS,SAAS,GAAG,GAAG;AAC/E,QAAE,WAAW,EAAE,SAAS,MAAM,GAAG,EAAE;AAAA,IACrC;AACA,WAAO,EAAE,SAAS;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACjHO,SAAS,6BAA6B,KAAa,SAAgC;AACxF,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,mBAAmB,QAAQ,oBAAoB,EAAE,MAAM,MAAM;AACnE,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,gBAAgB,QAAQ,iBAAiB;AAE/C,QAAM,YAAY,kBAAkB,qBAAqB,GAAG,IAAI;AAEhE,QAAM,SAAS,iBAAiB,SAAS;AACzC,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,MAAI,MAAM;AAEV,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,WAAW,KAAK;AAC5B,QAAI,CAAC,IAAK;AAEV,UAAM,QAAQ,kBAAkB,KAAK;AACrC,QAAI,MAAM,WAAW,EAAG;AAExB,QAAI,YAAY;AAEhB,QAAI,CAAC,YAAY,KAAK,GAAG;AACvB,YAAM,OAAO,oBAAoB;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,QACtD,UAAU;AAAA,MACZ,CAAC;AAED,YAAM,UAAU,0DAA0D,UAAU,IAAI,CAAC;AACzF,kBAAY,gBAAgB,WAAW,OAAO;AAAA,IAChD;AAGA,QAAI,UAAU,mBAAmB;AAC/B,kBAAY,kBAAkB,WAAW;AAAA,QACvC,GAAI,QAAQ,kBAAkB,EAAE,iBAAiB,QAAQ,gBAAgB,IAAI,CAAC;AAAA,QAC9E,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAGA,QAAI,kBAAkB,YAAY;AAChC,kBAAY,8BAA8B,WAAW,aAAa;AAAA,IACpE;AAEA,UAAM,IAAI,QAAQ,OAAO,SAAS;AAAA,EACpC;AAEA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAKlB;AACT,QAAM,EAAE,KAAK,OAAO,SAAS,SAAS,IAAI;AAE1C,QAAM,SAAS,UAAU,mBAAmB,KAAK,OAAO,IAAI;AAC5D,QAAM,SAAS,WAAW,MAAM,IAAI,yBAAyB,MAAM,IAAI;AAEvE,MAAI,SAAS,SAAS,MAAO,QAAO;AACpC,MAAI,SAAS,SAAS,QAAQ;AAC5B,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,mBAAmB,KAAK,MAAM;AAAA,EACvC;AACA,MAAI,SAAS,SAAS,UAAU;AAC9B,QAAI,CAAC,OAAQ,QAAO,SAAS;AAC7B,WAAO,mBAAmB,SAAS,KAAK,MAAM;AAAA,EAChD;AACA,MAAI,SAAS,SAAS,UAAU;AAC9B,UAAM,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,SAAS,MAAM,GAAG;AACjE,QAAI,MAAO,QAAO,UAAU,mBAAmB,OAAO,OAAO,IAAI;AACjE,WAAO;AAAA,EACT;AACA,QAAM,WAAW,SAAS,QAAQ,EAAE,KAAK,OAAO,CAAC;AACjD,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,mBAAmB,UAAU,MAAM;AAC5C;AAEA,SAAS,WAAW,GAAoB;AACtC,SAAO,EAAE,WAAW,SAAS,KAAK,EAAE,WAAW,UAAU;AAC3D;;;AC9FO,SAAS,wBAAwB,KAAa,SAA0C;AAC7F,QAAM,mBAAmB,QAAQ,oBAAoB;AACrD,QAAM,8BAA8B,QAAQ,+BAA+B;AAC3E,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,QAAM,sBAAsB,QAAQ,uBAAuB;AAC3D,QAAM,sBAAsB,QAAQ,uBAAuB;AAC3D,QAAM,eAAe,QAAQ,gBAAgB;AAE7C,QAAM,SAA0B,CAAC;AAEjC,QAAM,SAAS,iBAAiB,GAAG;AAEnC,MAAI,kBAAkB;AACpB,UAAM,YAAY,gBAAgB,KAAK,GAAG;AAC1C,QAAI,aAAa,CAAC,kBAAkB,GAAG,GAAG;AACxC,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAEA,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,WAAW,KAAK;AAC5B,QAAI,CAAC,IAAK;AAEV,UAAM,QAAQ,kBAAkB,KAAK;AACrC,QAAI,MAAM,WAAW,EAAG;AAExB,UAAMC,eAAc,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,WAAW;AAChE,QAAI,+BAA+B,MAAM,SAAS,KAAK,CAACA,cAAa;AACnE,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAGA,QAAI,oBAAoB;AACtB,YAAM,WAAW,oBAAI,IAAY;AACjC,iBAAW,QAAQ,OAAO;AACxB,YAAI,SAAS,IAAI,KAAK,QAAQ,GAAG;AAC/B,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,UAAU;AAAA,YACV,SAAS,2BAA2B,KAAK,QAAQ;AAAA,UACnD,CAAC;AAAA,QACH;AACA,iBAAS,IAAI,KAAK,QAAQ;AAAA,MAC5B;AAAA,IACF;AAGA,QAAI,qBAAqB;AACvB,YAAM,gBAAgB,oBAAI,IAAsB;AAChD,iBAAW,QAAQ,OAAO;AACxB,cAAM,UAAU,cAAc,IAAI,KAAK,IAAI,KAAK,CAAC;AACjD,gBAAQ,KAAK,KAAK,QAAQ;AAC1B,sBAAc,IAAI,KAAK,MAAM,OAAO;AAAA,MACtC;AACA,iBAAW,CAAC,MAAM,OAAO,KAAK,eAAe;AAE3C,cAAM,qBAAqB,QAAQ,OAAO,CAAC,MAAM,MAAM,WAAW;AAClE,YAAI,mBAAmB,SAAS,GAAG;AACjC,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,UAAU;AAAA,YACV,SAAS,qCAAqC,IAAI,cAAc,mBAAmB,KAAK,IAAI,CAAC;AAAA,UAC/F,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAGA,QAAI,qBAAqB;AACvB,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,sBAAsB,KAAK,QAAQ,GAAG;AACzC,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,UAAU;AAAA,YACV,SAAS,4BAA4B,KAAK,QAAQ;AAAA,UACpD,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAGA,QAAI,iBAAiB,QAAQ;AAC3B,YAAM,YAAY,UAAU,GAAG;AAC/B,UAAI,WAAW;AACb,mBAAW,QAAQ,OAAO;AACxB,gBAAM,aAAa,UAAU,KAAK,IAAI;AACtC,cAAI,cAAc,eAAe,WAAW;AAC1C,mBAAO,KAAK;AAAA,cACV,MAAM;AAAA,cACN,UAAU;AAAA,cACV,SAAS,2BAA2B,KAAK,QAAQ,cAAc,SAAS,SAAS,UAAU;AAAA,YAC7F,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF,WAAW,iBAAiB,aAAa;AACvC,YAAM,iBAAiB,QAAQ,kBAAkB,CAAC;AAClD,iBAAW,QAAQ,OAAO;AACxB,cAAM,aAAa,UAAU,KAAK,IAAI;AACtC,YAAI,cAAc,CAAC,eAAe,SAAS,UAAU,GAAG;AACtD,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,UAAU;AAAA,YACV,SAAS,+BAA+B,KAAK,QAAQ,KAAK,UAAU;AAAA,UACtE,CAAC;AAAA,QACH;AAAA,MACF;AACA,YAAM,YAAY,UAAU,GAAG;AAC/B,UAAI,aAAa,CAAC,eAAe,SAAS,SAAS,GAAG;AACpD,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS,oCAAoC,SAAS;AAAA,QACxD,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,iBAAiB;AACnB,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAACC,YAAW,KAAK,IAAI,GAAG;AAC1B,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,UAAU;AAAA,YACV,SAAS,kCAAkC,KAAK,QAAQ,KAAK,KAAK,IAAI;AAAA,UACxE,CAAC;AAAA,QACH;AAAA,MACF;AACA,UAAI,CAACA,YAAW,GAAG,GAAG;AACpB,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS,uBAAuB,GAAG;AAAA,QACrC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,OAAO,WAAW,GAAG,OAAO;AAC3C;AAEA,SAASA,YAAW,GAAoB;AACtC,SAAO,EAAE,WAAW,SAAS,KAAK,EAAE,WAAW,UAAU;AAC3D;AAEA,SAAS,UAAU,KAA4B;AAC7C,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,WAAO,EAAE;AAAA,EACX,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,sBAAsB,KAAsB;AACnD,MAAI,QAAQ,YAAa,QAAO;AAEhC,MAAI,aAAa,KAAK,GAAG,EAAG,QAAO;AAEnC,MAAI,sBAAsB,KAAK,GAAG,EAAG,QAAO;AAC5C,SAAO;AACT;","names":["href","normalizedPath","hasXDefault","isAbsolute"]}
1
+ {"version":3,"sources":["../src/lib/assertHreflang.ts","../src/lib/url.ts","../src/lib/buildLanguagesMap.ts","../src/lib/withHreflang.ts","../src/lib/routing.ts","../src/lib/expandLocales.ts","../src/lib/fromManifest.ts","../src/xml/xml.ts","../src/xml/inject.ts","../src/xml/check.ts"],"sourcesContent":["import type { HreflangIssue, HreflangReport, SitemapEntryLike } from \"./types.js\";\n\nexport type AssertHreflangOptions = {\n requireAbsolute?: boolean;\n requireXDefaultWhenMultiple?: boolean;\n requireSelf?: boolean;\n canonicalLocale?: string;\n};\n\nexport function assertHreflang(\n entries: readonly SitemapEntryLike[],\n options: AssertHreflangOptions,\n): HreflangReport {\n const requireAbsolute = options.requireAbsolute ?? true;\n const requireXDefaultWhenMultiple = options.requireXDefaultWhenMultiple ?? true;\n const requireSelf = options.requireSelf ?? false;\n\n const issues: HreflangIssue[] = [];\n\n for (const entry of entries) {\n const languages = entry.alternates?.languages;\n if (!languages) continue;\n\n const pairs = Object.entries(languages).filter(([, href]) => Boolean(href)) as Array<[\n string,\n string,\n ]>;\n\n if (pairs.length === 0) {\n issues.push({\n code: \"MISSING_LANGUAGES\",\n entryUrl: entry.url,\n message: \"alternates.languages is empty\",\n });\n continue;\n }\n\n if (requireXDefaultWhenMultiple && pairs.length > 1 && !languages[\"x-default\"]) {\n issues.push({\n code: \"MISSING_XDEFAULT\",\n entryUrl: entry.url,\n message: \"Missing x-default hreflang for a multilingual entry\",\n });\n }\n\n if (requireSelf && options.canonicalLocale) {\n if (!languages[options.canonicalLocale]) {\n issues.push({\n code: \"MISSING_SELF\",\n entryUrl: entry.url,\n message: `Missing self hreflang for canonicalLocale=${options.canonicalLocale}`,\n });\n }\n }\n\n const hrefSeen = new Set<string>();\n for (const [locale, href] of pairs) {\n if (!isValidLocaleKey(locale)) {\n issues.push({\n code: \"INVALID_LOCALE_KEY\",\n entryUrl: entry.url,\n message: `Invalid locale key: ${locale}`,\n });\n }\n if (requireAbsolute && !isAbsoluteUrl(href)) {\n issues.push({\n code: \"NON_ABSOLUTE_URL\",\n entryUrl: entry.url,\n message: `Non-absolute hreflang href for ${locale}: ${href}`,\n });\n }\n if (hrefSeen.has(href)) {\n issues.push({\n code: \"DUPLICATE_HREF\",\n entryUrl: entry.url,\n message: `Duplicate hreflang href detected: ${href}`,\n });\n }\n hrefSeen.add(href);\n }\n }\n\n return { ok: issues.length === 0, issues };\n}\n\nfunction isAbsoluteUrl(url: string): boolean {\n return url.startsWith(\"http://\") || url.startsWith(\"https://\");\n}\n\nfunction isValidLocaleKey(key: string): boolean {\n if (key === \"x-default\") return true;\n return /^[a-z]{2}(-[A-Z]{2})?$/.test(key);\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","import type { XDefaultStrategy } from \"./types.js\";\nimport { normalizeUrl, resolveAbsoluteUrl } from \"./url.js\";\n\nexport type BuildLanguagesMapInput = {\n baseUrl: string;\n locales: readonly string[];\n canonicalLocale: string;\n resolveHref: (locale: string) => string;\n includeXDefault: boolean;\n xDefaultStrategy?: XDefaultStrategy;\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n};\n\nexport type BuildLanguagesMapOutput = {\n canonical: string;\n languages: Record<string, string>;\n};\n\nexport function buildLanguagesMap(input: BuildLanguagesMapInput): BuildLanguagesMapOutput {\n const trailingSlash = input.trailingSlash ?? \"preserve\";\n const languages: Record<string, string> = {};\n\n for (const locale of input.locales) {\n const href = input.resolveHref(locale);\n const abs = resolveAbsoluteUrl(href, input.baseUrl);\n languages[locale] = normalizeUrl(abs, trailingSlash);\n }\n\n const canonical = languages[input.canonicalLocale] ?? normalizeUrl(input.baseUrl, trailingSlash);\n\n if (input.includeXDefault) {\n const xDefault = resolveXDefaultForLanguages({\n canonical,\n languages,\n baseUrl: input.baseUrl,\n strategy: input.xDefaultStrategy ?? { type: \"loc\" },\n trailingSlash,\n });\n languages[\"x-default\"] = xDefault;\n }\n\n return { canonical, languages };\n}\n\nexport function resolveXDefaultForLanguages(args: {\n canonical: string;\n languages: Record<string, string>;\n baseUrl: string;\n strategy: XDefaultStrategy;\n trailingSlash: \"preserve\" | \"always\" | \"never\";\n}): string {\n const { canonical, languages, baseUrl, strategy, trailingSlash } = args;\n\n if (strategy.type === \"loc\") return normalizeUrl(canonical, trailingSlash);\n if (strategy.type === \"root\") return normalizeUrl(resolveAbsoluteUrl(\"/\", baseUrl), trailingSlash);\n if (strategy.type === \"custom\") return normalizeUrl(resolveAbsoluteUrl(strategy.url, baseUrl), trailingSlash);\n if (strategy.type === \"locale\") {\n const href = languages[strategy.locale];\n if (href) return normalizeUrl(href, trailingSlash);\n return normalizeUrl(canonical, trailingSlash);\n }\n const href = strategy.resolve({ url: canonical });\n return normalizeUrl(resolveAbsoluteUrl(href, baseUrl), trailingSlash);\n}\n","import type {\n HreflangReport,\n SitemapEntryLike,\n XDefaultStrategy,\n RoutingStrategy,\n} from \"./types.js\";\nimport { assertHreflang as assertHreflangImpl } from \"./assertHreflang.js\";\nimport { buildLanguagesMap, resolveXDefaultForLanguages } from \"./buildLanguagesMap.js\";\nimport { normalizeUrl, resolveAbsoluteUrl } from \"./url.js\";\n\nexport { normalizeUrl, resolveAbsoluteUrl };\nexport { buildLanguagesMap };\n\nexport type WithHreflangOptions = {\n baseUrl?: string;\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n ensureAbsolute?: boolean;\n ensureXDefault?: boolean;\n ensureSelf?: boolean;\n canonicalLocale?: string;\n xDefaultStrategy?: XDefaultStrategy;\n shouldApply?: (entry: SitemapEntryLike) => boolean;\n};\n\nexport function withHreflang<T extends SitemapEntryLike>(\n entries: readonly T[],\n options: WithHreflangOptions,\n): T[] {\n const trailingSlash = options.trailingSlash ?? \"preserve\";\n const ensureAbsolute = options.ensureAbsolute ?? true;\n const ensureXDefault = options.ensureXDefault ?? true;\n const ensureSelf = options.ensureSelf ?? false;\n\n return entries.map((entry) => {\n if (options.shouldApply && !options.shouldApply(entry)) return entry;\n\n const url = normalizeEntryUrl(entry.url, options.baseUrl, ensureAbsolute, trailingSlash);\n\n const languages = entry.alternates?.languages\n ? normalizeLanguages(entry.alternates.languages, options.baseUrl, ensureAbsolute, trailingSlash)\n : undefined;\n\n if (!languages) {\n return { ...entry, url } as T;\n }\n\n const nextLanguages = { ...languages };\n\n if (ensureSelf && options.canonicalLocale) {\n if (!nextLanguages[options.canonicalLocale]) nextLanguages[options.canonicalLocale] = url;\n }\n\n if (ensureXDefault) {\n if (!nextLanguages[\"x-default\"]) {\n const xDefault = resolveXDefaultForLanguages({\n canonical: url,\n languages: nextLanguages,\n baseUrl: options.baseUrl ?? url,\n strategy: options.xDefaultStrategy ?? { type: \"loc\" },\n trailingSlash,\n });\n nextLanguages[\"x-default\"] = xDefault;\n }\n }\n\n const alternates = { ...(entry.alternates ?? {}), languages: nextLanguages };\n\n return { ...entry, url, alternates } as T;\n });\n}\n\nfunction normalizeEntryUrl(\n url: string,\n baseUrl: string | undefined,\n ensureAbsolute: boolean,\n trailingSlash: \"preserve\" | \"always\" | \"never\",\n): string {\n const abs = ensureAbsolute ? resolveAbsoluteUrl(url, baseUrl ?? url) : url;\n return normalizeUrl(abs, trailingSlash);\n}\n\nfunction normalizeLanguages(\n languages: Record<string, string | undefined>,\n baseUrl: string | undefined,\n ensureAbsolute: boolean,\n trailingSlash: \"preserve\" | \"always\" | \"never\",\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(languages)) {\n if (!v) continue;\n const abs = ensureAbsolute ? resolveAbsoluteUrl(v, baseUrl ?? v) : v;\n out[k] = normalizeUrl(abs, trailingSlash);\n }\n return out;\n}\n\nexport type AssertHreflangOptions = {\n requireAbsolute?: boolean;\n requireXDefaultWhenMultiple?: boolean;\n requireSelf?: boolean;\n canonicalLocale?: string;\n};\n\nexport function assertHreflangExport(entries: readonly SitemapEntryLike[], options: AssertHreflangOptions): HreflangReport {\n return assertHreflangImpl(entries, options);\n}\n\n/**\n * Build hreflang entries using a routing strategy.\n * This is the recommended way to use withHreflang with i18n routing.\n */\nexport function withHreflangFromRouting<T extends SitemapEntryLike>(\n entries: readonly T[],\n strategy: RoutingStrategy,\n options: {\n baseUrl: string;\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n ensureAbsolute?: boolean;\n ensureXDefault?: boolean;\n ensureSelf?: boolean;\n shouldApply?: (entry: SitemapEntryLike) => boolean;\n },\n): T[] {\n const trailingSlash = options.trailingSlash ?? \"preserve\";\n const ensureAbsolute = options.ensureAbsolute ?? true;\n const ensureXDefault = options.ensureXDefault ?? true;\n const ensureSelf = options.ensureSelf ?? false;\n\n return entries.map((entry) => {\n if (options.shouldApply && !options.shouldApply(entry)) return entry;\n\n // Extract pathname from URL\n let pathname: string;\n try {\n const url = new URL(entry.url, options.baseUrl);\n pathname = url.pathname;\n } catch {\n pathname = entry.url;\n }\n\n // Build languages map from routing strategy\n const languages: Record<string, string> = {};\n for (const locale of strategy.locales) {\n const href = strategy.hrefFor({ pathname, locale });\n const abs = ensureAbsolute ? resolveAbsoluteUrl(href, options.baseUrl) : href;\n languages[locale] = normalizeUrl(abs, trailingSlash);\n }\n\n // Add x-default if needed\n if (ensureXDefault && !languages[\"x-default\"]) {\n const canonical =\n languages[strategy.canonicalLocale] ?? normalizeUrl(entry.url, trailingSlash);\n const xDefault = resolveXDefaultForLanguages({\n canonical,\n languages,\n baseUrl: options.baseUrl,\n strategy: strategy.xDefault ?? { type: \"loc\" },\n trailingSlash,\n });\n languages[\"x-default\"] = xDefault;\n }\n\n // Ensure self for canonical locale\n if (ensureSelf && !languages[strategy.canonicalLocale]) {\n const url = normalizeEntryUrl(entry.url, options.baseUrl, ensureAbsolute, trailingSlash);\n languages[strategy.canonicalLocale] = url;\n }\n\n const alternates = { ...(entry.alternates ?? {}), languages };\n\n return { ...entry, alternates } as T;\n });\n}\n","import type {\n RoutingStrategy,\n RoutingPrefixAsNeededOptions,\n RoutingPrefixAlwaysOptions,\n RoutingDomainBasedOptions,\n RoutingSuffixLocaleOptions,\n RoutingPAS7Options,\n} from \"./types.js\";\n\n/**\n * Prefix-as-needed routing: default locale has no prefix, others have /locale\n * Example: /about (en), /uk/about (uk), /de/about (de)\n *\n * Compatible with next-intl's prefix-as-needed strategy.\n */\nexport function routingPrefixAsNeeded(\n options: RoutingPrefixAsNeededOptions,\n): RoutingStrategy {\n const { defaultLocale, locales, basePath = \"\" } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n const base = basePath || \"\";\n\n if (locale === defaultLocale) {\n return `${base}${normalizedPath}`;\n }\n return `${base}/${locale}${normalizedPath}`;\n },\n };\n}\n\n/**\n * Prefix-always routing: all locales have /locale prefix\n * Example: /en/about, /uk/about, /de/about\n *\n * Compatible with next-intl's prefix-always strategy.\n */\nexport function routingPrefixAlways(\n options: RoutingPrefixAlwaysOptions,\n): RoutingStrategy {\n const { defaultLocale, locales, basePath = \"\" } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n const base = basePath || \"\";\n return `${base}/${locale}${normalizedPath}`;\n },\n };\n}\n\n/**\n * Domain-based routing: each locale has its own domain\n * Example: example.com (en), example.de (de), example.ua (uk)\n *\n * Compatible with next-intl's domain-based routing.\n */\nexport function routingDomainBased(\n options: RoutingDomainBasedOptions,\n): RoutingStrategy {\n const { defaultLocale, locales, localeToDomain } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const domain = localeToDomain[locale];\n if (!domain) {\n const defaultDomain = localeToDomain[defaultLocale] || \"\";\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n return `${defaultDomain}/${locale}${normalizedPath}`;\n }\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n return `${domain}${normalizedPath}`;\n },\n };\n}\n\n/**\n * Suffix-locale routing: locale added as suffix to path\n * Example: /blog (en), /blog/uk (uk), /blog/de (de)\n *\n * Common for blogs and content sections.\n */\nexport function routingSuffixLocale(\n options: RoutingSuffixLocaleOptions,\n): RoutingStrategy {\n const { defaultLocale, locales, basePath = \"\" } = options;\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const normalizedPath = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n const path =\n normalizedPath.endsWith(\"/\") && normalizedPath !== \"/\"\n ? normalizedPath.slice(0, -1)\n : normalizedPath;\n const base = basePath || \"\";\n\n if (locale === defaultLocale) {\n return `${base}${path}`;\n }\n return `${base}${path}/${locale}`;\n },\n };\n}\n\n/**\n * Custom routing with user-defined function\n */\nexport function routingCustom(options: {\n locales: readonly string[];\n canonicalLocale: string;\n hrefFor: (args: { pathname: string; locale: string }) => string;\n xDefault?: RoutingStrategy[\"xDefault\"];\n}): RoutingStrategy {\n if (options.xDefault !== undefined) {\n return {\n locales: options.locales,\n canonicalLocale: options.canonicalLocale,\n hrefFor: options.hrefFor,\n xDefault: options.xDefault,\n };\n }\n return {\n locales: options.locales,\n canonicalLocale: options.canonicalLocale,\n hrefFor: options.hrefFor,\n };\n}\n\n/**\n * PAS7 custom routing strategy:\n * - Home: / (en), /uk, /de\n * - Suffix pages: /blog (en), /blog/uk\n * - Detail pages: /blog/en/slug, /blog/uk/slug\n * - Prefix pages: /about (en), /uk/about\n *\n * Priority: detailPathPattern > suffixPaths > prefixPaths > fallback.\n */\nexport function routingPAS7(options: RoutingPAS7Options): RoutingStrategy {\n const {\n defaultLocale,\n locales,\n hubPaths = [\"/blog\", \"/projects\", \"/services\", \"/cases\"],\n suffixPaths,\n prefixPaths = [],\n detailPathPattern = /^\\/(blog|projects|services|cases)\\//,\n } = options;\n\n const normalizedSuffixPaths = uniquePaths(suffixPaths ?? hubPaths);\n const normalizedPrefixPaths = uniquePaths(prefixPaths);\n const localeSet = new Set(locales);\n\n return {\n locales,\n canonicalLocale: defaultLocale,\n hrefFor: ({ pathname, locale }) => {\n const normalizedPath = normalizeInputPath(pathname);\n\n if (normalizedPath === \"/\") {\n if (locale === defaultLocale) return \"/\";\n return `/${locale}`;\n }\n\n if (detailPathPattern.test(normalizedPath)) {\n const parts = normalizedPath.split(\"/\").filter(Boolean);\n if (parts.length >= 3 && localeSet.has(parts[1] ?? \"\")) {\n const section = parts[0];\n const slug = parts.slice(2).join(\"/\");\n return `/${section}/${locale}/${slug}`;\n }\n }\n\n const suffixBase = findSuffixBasePath(normalizedPath, normalizedSuffixPaths, localeSet);\n if (suffixBase) {\n if (locale === defaultLocale) return suffixBase;\n return `${suffixBase}/${locale}`;\n }\n\n const prefixBase = findPrefixBasePath(normalizedPath, normalizedPrefixPaths, localeSet);\n if (prefixBase) {\n if (locale === defaultLocale) return prefixBase;\n return `/${locale}${prefixBase}`;\n }\n\n if (locale === defaultLocale) return normalizedPath;\n return `/${locale}${normalizedPath}`;\n },\n };\n}\n\nfunction normalizeInputPath(pathname: string): string {\n const withLeadingSlash = pathname.startsWith(\"/\") ? pathname : `/${pathname}`;\n if (withLeadingSlash.length > 1 && withLeadingSlash.endsWith(\"/\")) {\n return withLeadingSlash.slice(0, -1);\n }\n return withLeadingSlash;\n}\n\nfunction uniquePaths(paths: readonly string[]): string[] {\n return [...new Set(paths.map(normalizeInputPath))];\n}\n\nfunction findSuffixBasePath(\n pathname: string,\n suffixPaths: readonly string[],\n localeSet: ReadonlySet<string>,\n): string | null {\n for (const suffixPath of suffixPaths) {\n if (pathname === suffixPath) return suffixPath;\n\n if (!pathname.startsWith(`${suffixPath}/`)) continue;\n const rest = pathname.slice(`${suffixPath}/`.length);\n if (!rest) continue;\n\n const [maybeLocale, ...tail] = rest.split(\"/\");\n if (tail.length === 0 && localeSet.has(maybeLocale ?? \"\")) {\n return suffixPath;\n }\n }\n return null;\n}\n\nfunction findPrefixBasePath(\n pathname: string,\n prefixPaths: readonly string[],\n localeSet: ReadonlySet<string>,\n): string | null {\n for (const prefixPath of prefixPaths) {\n if (pathname === prefixPath) return prefixPath;\n\n const withoutLeadingSlash = pathname.replace(/^\\/+/, \"\");\n const [maybeLocale, ...tail] = withoutLeadingSlash.split(\"/\");\n if (!localeSet.has(maybeLocale ?? \"\")) continue;\n\n const candidate = normalizeInputPath(tail.join(\"/\"));\n if (candidate === prefixPath) return prefixPath;\n }\n return null;\n}\n","import type { SitemapEntryLike } from \"./types.js\";\n\nexport type ExpandLocalesOptions = {\n mode: \"cluster-only\" | \"expanded\";\n includeXDefault?: boolean;\n locales?: readonly string[];\n};\n\nexport function expandLocaleEntries<T extends SitemapEntryLike>(\n entries: readonly T[],\n options: ExpandLocalesOptions,\n): T[] {\n if (options.mode === \"cluster-only\") {\n return [...entries];\n }\n\n const result: T[] = [];\n\n for (const entry of entries) {\n const languages = entry.alternates?.languages;\n if (!languages || Object.keys(languages).length === 0) {\n result.push(entry);\n continue;\n }\n\n // Create a separate entry for each locale\n for (const [locale, href] of Object.entries(languages)) {\n if (locale === \"x-default\" && !options.includeXDefault) continue;\n\n // Filter by specific locales if provided\n if (options.locales && !options.locales.includes(locale)) continue;\n\n const expandedEntry: T = {\n ...entry,\n url: href,\n // alternates.languages remains the full cluster\n };\n result.push(expandedEntry);\n }\n }\n\n // Dedupe by url\n const seen = new Set<string>();\n return result.filter((e) => {\n if (seen.has(e.url)) return false;\n seen.add(e.url);\n return true;\n });\n}\n","import type { ChangeFrequency, SitemapEntry, XDefaultStrategy } from \"./types.js\";\nimport { withHreflang } from \"./withHreflang.js\";\nimport { normalizeUrl, resolveAbsoluteUrl } from \"./url.js\";\n\nexport type ManifestRouteStyle =\n | \"prefix-as-needed\"\n | \"prefix-always\"\n | \"suffix-locale\"\n | \"locale-segment\";\n\nexport type LocalizedManifestItem = {\n slug: string;\n locales: readonly string[];\n updatedAt?: string | Date;\n publishedAt?: string | Date;\n lastModified?: string | Date;\n date?: string | Date;\n changeFrequency?: ChangeFrequency;\n priority?: number;\n images?: string[];\n};\n\nexport type CreateSitemapEntriesFromManifestOptions = {\n baseUrl: string;\n sectionPath: string;\n defaultLocale: string;\n canonicalLocale?: string;\n routeStyle?: ManifestRouteStyle;\n trailingSlash?: \"preserve\" | \"always\" | \"never\";\n ensureXDefault?: boolean;\n xDefaultStrategy?: XDefaultStrategy;\n includeLocales?: readonly string[];\n pathnameFor?: (args: {\n slug: string;\n locale: string;\n defaultLocale: string;\n sectionPath: string;\n routeStyle: ManifestRouteStyle;\n }) => string;\n};\n\nexport function createSitemapEntriesFromManifest<T extends LocalizedManifestItem>(\n items: readonly T[],\n options: CreateSitemapEntriesFromManifestOptions,\n): SitemapEntry[] {\n const routeStyle = options.routeStyle ?? \"locale-segment\";\n const includeXDefault = options.ensureXDefault ?? true;\n const trailingSlash = options.trailingSlash ?? \"preserve\";\n const includeLocales = options.includeLocales ? new Set(options.includeLocales) : null;\n\n const entries: SitemapEntry[] = [];\n\n for (const item of items) {\n const slug = normalizeSlug(item.slug);\n if (!slug) continue;\n\n const locales = unique(item.locales).filter((locale) => (includeLocales ? includeLocales.has(locale) : true));\n if (locales.length === 0) continue;\n\n const configuredCanonical = options.canonicalLocale ?? options.defaultLocale;\n const effectiveCanonicalLocale =\n locales.includes(configuredCanonical) ? configuredCanonical : locales[0] ?? configuredCanonical;\n\n const languages: Record<string, string> = {};\n for (const locale of locales) {\n const pathname =\n options.pathnameFor?.({\n slug,\n locale,\n defaultLocale: options.defaultLocale,\n sectionPath: options.sectionPath,\n routeStyle,\n }) ??\n buildPathname({\n slug,\n locale,\n defaultLocale: options.defaultLocale,\n sectionPath: options.sectionPath,\n routeStyle,\n });\n\n const href = normalizeUrl(resolveAbsoluteUrl(pathname, options.baseUrl), trailingSlash);\n languages[locale] = href;\n }\n\n const canonicalUrl =\n languages[effectiveCanonicalLocale] ??\n normalizeUrl(resolveAbsoluteUrl(\"/\", options.baseUrl), trailingSlash);\n\n const lastModified = pickLastModified(item);\n\n const baseEntry: SitemapEntry = {\n url: canonicalUrl,\n alternates: { languages },\n ...(lastModified ? { lastModified } : {}),\n ...(item.changeFrequency ? { changeFrequency: item.changeFrequency } : {}),\n ...(typeof item.priority === \"number\" ? { priority: item.priority } : {}),\n ...(item.images ? { images: [...item.images] } : {}),\n };\n\n const [entryWithHreflang] = withHreflang([baseEntry], {\n baseUrl: options.baseUrl,\n trailingSlash,\n ensureAbsolute: true,\n ensureXDefault: includeXDefault,\n ensureSelf: true,\n canonicalLocale: effectiveCanonicalLocale,\n ...(options.xDefaultStrategy ? { xDefaultStrategy: options.xDefaultStrategy } : {}),\n });\n\n if (entryWithHreflang) entries.push(entryWithHreflang);\n }\n\n return entries;\n}\n\nfunction buildPathname(args: {\n slug: string;\n locale: string;\n defaultLocale: string;\n sectionPath: string;\n routeStyle: ManifestRouteStyle;\n}): string {\n const section = normalizeSection(args.sectionPath);\n\n if (args.routeStyle === \"prefix-as-needed\") {\n if (args.locale === args.defaultLocale) return joinPath(section, args.slug);\n return joinPath(`/${args.locale}`, section, args.slug);\n }\n\n if (args.routeStyle === \"prefix-always\") {\n return joinPath(`/${args.locale}`, section, args.slug);\n }\n\n if (args.routeStyle === \"suffix-locale\") {\n if (args.locale === args.defaultLocale) return joinPath(section, args.slug);\n return joinPath(section, args.slug, args.locale);\n }\n\n return joinPath(section, args.locale, args.slug);\n}\n\nfunction normalizeSlug(slug: string): string {\n return slug.trim().replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n}\n\nfunction normalizeSection(sectionPath: string): string {\n const normalized = sectionPath.trim();\n if (!normalized || normalized === \"/\") return \"\";\n return `/${normalized.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\")}`;\n}\n\nfunction joinPath(...parts: string[]): string {\n const cleaned = parts\n .map((part) => part.trim())\n .filter(Boolean)\n .map((part) => part.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\"));\n\n if (cleaned.length === 0) return \"/\";\n return `/${cleaned.join(\"/\")}`;\n}\n\nfunction unique(values: readonly string[]): string[] {\n return [...new Set(values)];\n}\n\nfunction pickLastModified(item: LocalizedManifestItem): string | Date | undefined {\n return item.lastModified ?? item.updatedAt ?? item.publishedAt ?? item.date;\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":["href","normalizedPath","hasXDefault","isAbsolute"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pas7/nextjs-sitemap-hreflang",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
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": {
@@ -66,7 +66,10 @@
66
66
  "@eslint/js": "^9.0.0",
67
67
  "@types/node": "^20.0.0",
68
68
  "eslint": "^9.0.0",
69
+ "next": "^15.0.0",
69
70
  "prettier": "^3.0.0",
71
+ "react": "^19.0.0",
72
+ "react-dom": "^19.0.0",
70
73
  "tsup": "^8.0.0",
71
74
  "typescript": "^5.0.0",
72
75
  "typescript-eslint": "^8.0.0",