@warlock.js/sitemap 5.31.0 → 5.33.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/CHANGELOG.md +12 -0
- package/cjs/index.cjs +1 -1
- package/cjs/index.cjs.map +1 -1
- package/esm/errors.d.mts +4 -5
- package/esm/generation-manifest.d.mts +11 -12
- package/esm/normalize-entry.mjs +1 -1
- package/esm/sitemap-index-options.mjs +2 -2
- package/esm/sitemap-index-options.mjs.map +1 -1
- package/esm/sitemap-index-types.d.mts +22 -15
- package/esm/sitemap-index.d.mts +1 -3
- package/esm/sitemap.d.mts +1 -3
- package/esm/types.d.mts +31 -20
- package/esm/url.d.mts +1 -2
- package/esm/url.mjs +1 -1
- package/esm/xml.d.mts +3 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to `@warlock.js/sitemap` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.33.0 - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Lockstep release; package APIs are unchanged.
|
|
12
|
+
|
|
13
|
+
## 5.32.0 - 2026-10-18
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Pin the package development compiler to TypeScript 7.0.2 and use explicit Node types and source roots with supported module resolution.
|
|
18
|
+
|
|
7
19
|
## 5.31.0 - 2026-10-18
|
|
8
20
|
|
|
9
21
|
### Changed
|
package/cjs/index.cjs
CHANGED
|
@@ -502,7 +502,7 @@ var Sitemap = class {
|
|
|
502
502
|
//#region ../../packages/sitemap/src/sitemap-index-options.ts
|
|
503
503
|
/** The sitemaps.org limits. Never clamped to — a silently clamped option is a lie about what was written. */
|
|
504
504
|
const PROTOCOL_MAX_URLS_PER_FILE = 5e4;
|
|
505
|
-
const PROTOCOL_MAX_BYTES_PER_FILE =
|
|
505
|
+
const PROTOCOL_MAX_BYTES_PER_FILE = 52428800;
|
|
506
506
|
/** Validates and folds in defaults, once, at the constructor — the same discipline as `Sitemap`. */
|
|
507
507
|
function normalizeSitemapIndexOptions(options) {
|
|
508
508
|
const baseUrl = normalizeBaseUrl(options?.baseUrl);
|
package/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../../../../../../packages/sitemap/src/errors.ts","../../../../../../../packages/sitemap/src/atomic-publish.ts","../../../../../../../packages/sitemap/src/atomic-write-file.ts","../../../../../../../packages/sitemap/src/lastmod.ts","../../../../../../../packages/sitemap/src/normalize-entry.ts","../../../../../../../packages/sitemap/src/url.ts","../../../../../../../packages/sitemap/src/xml.ts","../../../../../../../packages/sitemap/src/sitemap.ts","../../../../../../../packages/sitemap/src/sitemap-index-options.ts","../../../../../../../packages/sitemap/src/shard-name.ts","../../../../../../../packages/sitemap/src/sitemap-shard-writer.ts","../../../../../../../packages/sitemap/src/sitemap-index-xml.ts","../../../../../../../packages/sitemap/src/duplicate-path-tracker.ts","../../../../../../../packages/sitemap/src/route-counter.ts","../../../../../../../packages/sitemap/src/sitemap-index.ts","../../../../../../../packages/sitemap/src/generation-manifest.ts"],"sourcesContent":["/**\n * The `baseUrl` given to a `Sitemap` is not an absolute http(s) URL.\n *\n * Thrown from the CONSTRUCTOR: a sitemap that cannot produce a valid URL\n * should not exist, and the mistake belongs at the line that wrote the value\n * rather than at the first request that reads it.\n */\nexport class InvalidBaseUrlError extends Error {\n public constructor(value: unknown, reason: string) {\n super(`Invalid sitemap baseUrl ${JSON.stringify(value)}: ${reason}.`);\n this.name = \"InvalidBaseUrlError\";\n }\n}\n\n/** An entry the sitemap protocol cannot represent. Thrown from `add()`. */\nexport class InvalidSitemapEntryError extends Error {\n public constructor(reason: string) {\n super(`Invalid sitemap entry: ${reason}.`);\n this.name = \"InvalidSitemapEntryError\";\n }\n}\n\n/**\n * Two `SitemapIndex` source keys collide once canonicalised (`en-US` and\n * `en-us` would produce the same filename on a case-insensitive filesystem\n * and silently overwrite one another). Thrown from `addSource()`, not at\n * `saveTo()`, so the mistake is caught at the line that registered it.\n */\nexport class DuplicateSourceKeyError extends Error {\n public constructor(key: string) {\n super(\n `Duplicate sitemap source key ${JSON.stringify(key)}: keys collide case-insensitively ` +\n `and would overwrite one another's shard files.`,\n );\n this.name = \"DuplicateSourceKeyError\";\n }\n}\n\n/**\n * `SitemapIndex.saveTo(outDir)` swaps the ENTIRE `outDir` for a freshly\n * written set (`atomic-publish.ts`). That is safe only when `outDir` is a\n * directory this package already owns — marked by its own\n * `.sitemap-set.json` from a prior publish. A non-empty directory with no\n * marker is presumed to belong to someone else (a caller's `public/`, most\n * dangerously) and is never swapped or deleted; this is thrown instead, from\n * `saveTo()` before anything is written.\n */\nexport class UnownedOutputDirectoryError extends Error {\n public constructor(outDir: string) {\n super(\n `Refusing to publish a sitemap set to ${JSON.stringify(outDir)}: this directory already ` +\n `has content but no \".sitemap-set.json\" marker from a previous @warlock.js/sitemap ` +\n `publish, so it is not safe to swap or delete. Point saveTo() at a dedicated, ` +\n `sitemap-only directory instead.`,\n );\n this.name = \"UnownedOutputDirectoryError\";\n }\n}\n","import { mkdir, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { UnownedOutputDirectoryError } from \"./errors\";\n\n/**\n * Marks a directory as one `publishAtomically` swapped into place. Its\n * presence is the ONLY thing that lets a later publish treat the directory\n * as safe to swap out from under itself — see {@link assertOutDirIsOwned}.\n */\nconst OWNERSHIP_MARKER_FILE = \".sitemap-set.json\";\nconst OWNERSHIP_MARKER_VERSION = 1;\n\nasync function validateShards(tempDir: string, fileNames: readonly string[]): Promise<void> {\n for (const fileName of fileNames) {\n const info = await stat(join(tempDir, fileName)).catch(() => undefined);\n\n if (!info || !info.isFile() || info.size === 0) {\n throw new Error(`sitemap publish aborted: shard \"${fileName}\" is missing or empty.`);\n }\n }\n}\n\n/**\n * `outDir` is safe to take over when it does not exist yet, is empty, or\n * already carries {@link OWNERSHIP_MARKER_FILE} from a previous publish.\n * Anything else — a non-empty directory this package never wrote — is\n * refused rather than swapped or deleted (`de97020e`).\n */\nasync function assertOutDirIsOwned(outDir: string): Promise<void> {\n const entries = await readdir(outDir).catch((error) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw error;\n });\n\n if (entries === undefined || entries.length === 0) return;\n\n if (!entries.includes(OWNERSHIP_MARKER_FILE)) {\n throw new UnownedOutputDirectoryError(outDir);\n }\n}\n\n/**\n * Writes a complete set into a sibling temp directory, validates that every\n * file it named actually exists and is non-empty, then swaps it into\n * `outDir`. A crawler arriving mid-write sees the previous complete set or\n * the new one, never a partial one, and a failed run leaves the previous set\n * untouched and rejects.\n *\n * `write` performs the writes and returns the file names (relative to the\n * temp dir) that must be present for the set to be considered valid — it is\n * only known after writing, since shard count depends on what was walked.\n */\nexport async function publishAtomically(\n outDir: string,\n write: (tempDir: string) => Promise<readonly string[]>,\n): Promise<void> {\n await assertOutDirIsOwned(outDir);\n\n const parent = dirname(outDir);\n\n await mkdir(parent, { recursive: true });\n\n const tempDir = join(parent, `.${basename(outDir)}.tmp-${process.pid}-${Date.now()}`);\n\n await mkdir(tempDir, { recursive: true });\n\n try {\n const fileNames = await write(tempDir);\n\n await validateShards(tempDir, fileNames);\n\n await writeFile(\n join(tempDir, OWNERSHIP_MARKER_FILE),\n JSON.stringify({ package: \"@warlock.js/sitemap\", version: OWNERSHIP_MARKER_VERSION }),\n \"utf8\",\n );\n\n const displacedDir = join(parent, `.${basename(outDir)}.previous-${Date.now()}`);\n let displacedPrevious = false;\n\n try {\n await rename(outDir, displacedDir);\n displacedPrevious = true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n\n try {\n await rename(tempDir, outDir);\n } catch (error) {\n if (displacedPrevious) await rename(displacedDir, outDir);\n throw error;\n }\n\n if (displacedPrevious) await rm(displacedDir, { recursive: true, force: true });\n } catch (error) {\n await rm(tempDir, { recursive: true, force: true });\n throw error;\n }\n}\n","import { rename, rm, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\n\n/** The subset of `node:fs/promises` this helper needs — swappable so a test can inject a failing write or rename without mocking the global module. */\nexport type AtomicWriteFileDeps = {\n writeFile: typeof writeFile;\n rename: typeof rename;\n rm: typeof rm;\n};\n\nconst defaultDeps: AtomicWriteFileDeps = { writeFile, rename, rm };\n\nconst RENAME_RETRY_ATTEMPTS = 5;\nconst RENAME_RETRY_DELAY_MS = 20;\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Windows can report a rename over an existing file as EPERM/EBUSY while something briefly holds the target (an AV scan, a reader) — retrying is correct there, not a masked bug. */\nfunction isTransientRenameError(error: unknown): boolean {\n const code = (error as NodeJS.ErrnoException)?.code;\n\n return code === \"EPERM\" || code === \"EBUSY\";\n}\n\nasync function renameWithRetry(from: string, to: string, deps: AtomicWriteFileDeps): Promise<void> {\n for (let attempt = 1; attempt <= RENAME_RETRY_ATTEMPTS; attempt++) {\n try {\n await deps.rename(from, to);\n\n return;\n } catch (error) {\n if (attempt === RENAME_RETRY_ATTEMPTS || !isTransientRenameError(error)) throw error;\n\n await delay(RENAME_RETRY_DELAY_MS * attempt);\n }\n }\n}\n\n/**\n * Writes `content` to `filePath` atomically: the content lands in a unique\n * sibling temp file first — same directory, so same filesystem, so the\n * rename that follows is atomic — and only then is renamed over the target.\n * An interrupted or failing write never truncates or otherwise touches the\n * existing target; on any failure the temp file is removed and the error is\n * rethrown.\n */\nexport async function atomicWriteFile(\n filePath: string,\n content: string,\n deps: AtomicWriteFileDeps = defaultDeps,\n): Promise<void> {\n const tempPath = join(\n dirname(filePath),\n `.${basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n );\n\n try {\n await deps.writeFile(tempPath, content, \"utf8\");\n await renameWithRetry(tempPath, filePath, deps);\n } catch (error) {\n await deps.rm(tempPath, { force: true }).catch(() => undefined);\n\n throw error;\n }\n}\n","import { InvalidSitemapEntryError } from \"./errors\";\n\n/**\n * Serialises a `lastmod` value.\n *\n * A `Date` becomes W3C datetime, which is what the schema wants and what\n * `toISOString()` already produces. A string is passed through UNTOUCHED: a\n * caller who already holds an ISO string gets it back verbatim rather than\n * having us re-parse it and risk shifting it across a timezone.\n */\nexport function formatLastmod(value: string | Date): string {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new InvalidSitemapEntryError(\"lastmod is an invalid Date\");\n }\n\n return value.toISOString();\n }\n\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new InvalidSitemapEntryError(\"lastmod must be a non-empty string or a Date\");\n }\n\n return value;\n}\n","import { InvalidSitemapEntryError } from \"./errors\";\nimport { formatLastmod } from \"./lastmod\";\nimport type {\n ChangeFreq,\n ResolvedSitemapEntry,\n SitemapEntry,\n SitemapImage,\n SitemapOptions,\n} from \"./types\";\n\nconst CHANGE_FREQS: readonly ChangeFreq[] = [\n \"always\",\n \"hourly\",\n \"daily\",\n \"weekly\",\n \"monthly\",\n \"yearly\",\n \"never\",\n];\nconst MAX_IMAGES_PER_URL = 1_000;\n\nfunction assertChangeFreq(value: unknown): asserts value is ChangeFreq {\n if (!CHANGE_FREQS.includes(value as ChangeFreq)) {\n throw new InvalidSitemapEntryError(\n `changefreq ${JSON.stringify(value)} is not one of ${CHANGE_FREQS.join(\", \")}`,\n );\n }\n}\n\nfunction assertPriority(value: unknown): asserts value is number {\n if (typeof value !== \"number\" || Number.isNaN(value) || value < 0 || value > 1) {\n throw new InvalidSitemapEntryError(\n `priority ${JSON.stringify(value)} is outside the protocol range 0.0–1.0`,\n );\n }\n}\n\n/** Every stored path carries its leading slash, so `/a` and `a` are one entry, not two. */\nexport function normalizePath(path: unknown): string {\n if (typeof path !== \"string\" || path.trim() === \"\") {\n throw new InvalidSitemapEntryError(\"path is required and must be a non-empty string\");\n }\n\n // An absolute URL is left alone: the caller is overriding the base origin\n // deliberately, which a cross-origin alternate legitimately needs.\n if (/^https?:\\/\\//i.test(path)) return path;\n\n return path.startsWith(\"/\") ? path : `/${path}`;\n}\n\n/**\n * Validates one entry and folds the builder's defaults into it. Defaults are\n * resolved HERE rather than at serialisation time so that `entries()` shows\n * what will actually be emitted — a diagnostic that reports something other\n * than the output is worse than none.\n */\nexport function normalizeEntry(\n entry: SitemapEntry,\n defaults: Pick<SitemapOptions, \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\">,\n): ResolvedSitemapEntry {\n const path = normalizePath(entry.path);\n const changefreq = entry.changefreq ?? defaults.changefreq;\n const priority = entry.priority ?? defaults.priority;\n const lastmod = entry.lastmod ?? defaults.lastmod;\n\n if (changefreq !== undefined) assertChangeFreq(changefreq);\n if (priority !== undefined) assertPriority(priority);\n\n const alternates = entry.alternates?.map((alternate) => {\n if (typeof alternate?.hreflang !== \"string\" || alternate.hreflang.trim() === \"\") {\n throw new InvalidSitemapEntryError(\"alternate hreflang is required\");\n }\n\n return { hreflang: alternate.hreflang, path: normalizePath(alternate.path) };\n });\n if (entry.images !== undefined && !Array.isArray(entry.images)) {\n throw new InvalidSitemapEntryError(\"images must be an array\");\n }\n const images = entry.images?.slice(0, MAX_IMAGES_PER_URL).map((image): SitemapImage => {\n if (typeof image?.loc !== \"string\" || image.loc.trim() === \"\") {\n throw new InvalidSitemapEntryError(\"image loc is required\");\n }\n\n let url: URL;\n try {\n url = new URL(image.loc);\n } catch {\n throw new InvalidSitemapEntryError(\"image loc must be an absolute HTTP(S) URL\");\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new InvalidSitemapEntryError(\"image loc must be an absolute HTTP(S) URL\");\n }\n\n return { loc: image.loc };\n });\n if ((entry.images?.length ?? 0) > MAX_IMAGES_PER_URL) {\n defaults.onImageLimitExceeded?.({\n ...(entry.route === undefined ? {} : { route: entry.route }),\n dropped: entry.images!.length - MAX_IMAGES_PER_URL,\n });\n }\n\n return {\n path,\n ...(entry.name !== undefined ? { name: entry.name } : {}),\n ...(entry.route !== undefined ? { route: entry.route } : {}),\n ...(lastmod !== undefined ? { lastmod: formatLastmod(lastmod) } : {}),\n ...(changefreq !== undefined ? { changefreq } : {}),\n ...(priority !== undefined ? { priority } : {}),\n ...(alternates !== undefined ? { alternates } : {}),\n ...(images !== undefined ? { images } : {}),\n };\n}\n","import { InvalidBaseUrlError } from \"./errors\";\n\n/**\n * Joins a configured origin and an app-relative route path into one absolute\n * URL, with exactly one slash at the seam regardless of whether either side\n * already carries one.\n */\nexport function joinOrigin(origin: string, routePath: string): string {\n const trimmedOrigin = origin.endsWith(\"/\") ? origin.slice(0, -1) : origin;\n const normalizedPath = routePath.startsWith(\"/\") ? routePath : `/${routePath}`;\n\n return `${trimmedOrigin}${normalizedPath}`;\n}\n\n/**\n * Validates a `baseUrl` and returns it without its trailing slash.\n *\n * `new URL()` accepts `mailto:` and `file:` happily, so the protocol is\n * checked explicitly — a sitemap `<loc>` that is not http(s) is not a document\n * any crawler will fetch.\n */\nexport function normalizeBaseUrl(value: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new InvalidBaseUrlError(value, \"expected a non-empty string\");\n }\n\n let parsed: URL;\n\n try {\n parsed = new URL(value);\n } catch {\n throw new InvalidBaseUrlError(value, \"not an absolute URL\");\n }\n\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new InvalidBaseUrlError(value, `unsupported protocol \"${parsed.protocol}\"`);\n }\n\n const href = parsed.href;\n\n return href.endsWith(\"/\") ? href.slice(0, -1) : href;\n}\n\n/** True for a value already usable as a `<loc>` without joining an origin. */\nexport function isAbsoluteUrl(value: string): boolean {\n return /^https?:\\/\\//i.test(value);\n}\n\n/** Resolves an entry or alternate path against the base URL, unless it is already absolute. */\nexport function resolveAgainstBase(baseUrl: string, path: string): string {\n return isAbsoluteUrl(path) ? path : joinOrigin(baseUrl, path);\n}\n","import type { ResolvedSitemapEntry } from \"./types\";\nimport { resolveAgainstBase } from \"./url\";\n\nconst XML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\n/** Escapes the five XML-significant characters. A URL's query string routinely contains `&`. */\nexport function escapeXml(value: string): string {\n // The character class and the table are written together, so the lookup can\n // only miss if one is edited without the other; falling back to the original\n // character keeps that editing mistake from silently emitting `undefined`\n // into a URL.\n return value.replace(/[&<>\"']/g, (char) => XML_ESCAPES[char] ?? char);\n}\n\nconst XHTML_NAMESPACE = \"http://www.w3.org/1999/xhtml\";\nconst IMAGE_NAMESPACE = \"http://www.google.com/schemas/sitemap-image/1.1\";\n\n/**\n * The `xmlns:xhtml` attribute exactly as it appears on `<urlset>`, including\n * its leading space. Exported so the shard writer can charge its byte length\n * against the ceiling without duplicating the string it measures.\n */\nexport const XHTML_NAMESPACE_ATTR = ` xmlns:xhtml=\"${XHTML_NAMESPACE}\"`;\n/** See {@link XHTML_NAMESPACE_ATTR}; measured by the shard writer before roll-over. */\nexport const IMAGE_NAMESPACE_ATTR = ` xmlns:image=\"${IMAGE_NAMESPACE}\"`;\n\n/**\n * Renders one `<url>` block. Exported so the streaming writer can measure the\n * exact bytes it is about to append before deciding whether the byte ceiling\n * forces a new shard — a separate approximation could disagree with what is\n * actually written.\n */\nexport function renderUrlBlock(entry: ResolvedSitemapEntry, baseUrl: string): string {\n const lines = [` <url>`, ` <loc>${escapeXml(resolveAgainstBase(baseUrl, entry.path))}</loc>`];\n\n if (entry.lastmod !== undefined) {\n lines.push(` <lastmod>${escapeXml(entry.lastmod)}</lastmod>`);\n }\n\n if (entry.changefreq !== undefined) {\n lines.push(` <changefreq>${entry.changefreq}</changefreq>`);\n }\n\n if (entry.priority !== undefined) {\n lines.push(` <priority>${entry.priority}</priority>`);\n }\n\n for (const alternate of entry.alternates ?? []) {\n const href = escapeXml(resolveAgainstBase(baseUrl, alternate.path));\n\n lines.push(\n ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alternate.hreflang)}\" href=\"${href}\"/>`,\n );\n }\n\n for (const image of entry.images ?? []) {\n lines.push(` <image:image>`);\n lines.push(` <image:loc>${escapeXml(resolveAgainstBase(baseUrl, image.loc))}</image:loc>`);\n lines.push(` </image:image>`);\n }\n\n lines.push(` </url>`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Serialises entries into a `urlset` sitemap document — the sitemaps.org\n * namespace, `<url>` per entry, element order `loc` / `lastmod` / `changefreq`\n * / `priority` (schema order; a validator that checks order rejects any other),\n * then any `xhtml:link` alternates.\n *\n * The xhtml namespace is declared only when some entry actually carries an\n * alternate — an unused namespace on every single-language sitemap is noise.\n */\nexport function buildSitemapXml(entries: readonly ResolvedSitemapEntry[], baseUrl: string): string {\n const hasAlternates = entries.some((entry) => (entry.alternates?.length ?? 0) > 0);\n const hasImages = entries.some((entry) => (entry.images?.length ?? 0) > 0);\n const namespaces = `${hasAlternates ? XHTML_NAMESPACE_ATTR : \"\"}${hasImages ? IMAGE_NAMESPACE_ATTR : \"\"}`;\n const body = entries.map((entry) => renderUrlBlock(entry, baseUrl)).join(\"\\n\");\n\n return (\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n` +\n `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"${namespaces}>\\n` +\n (body.length > 0 ? `${body}\\n` : \"\") +\n `</urlset>\\n`\n );\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { publishAtomically } from \"./atomic-publish\";\nimport { atomicWriteFile } from \"./atomic-write-file\";\nimport { normalizeEntry } from \"./normalize-entry\";\nimport type {\n DuplicateReport,\n ResolvedSitemapEntry,\n RouteSummary,\n SitemapEntry,\n SitemapOptions,\n} from \"./types\";\nimport { normalizeBaseUrl } from \"./url\";\nimport { buildSitemapXml } from \"./xml\";\n\n/**\n * A bounded sitemap builder: it RETAINS every entry, which is what makes\n * `entries()` and a repeatable `toXML()` possible, and is exactly right up to\n * the sitemaps.org ceiling of 50,000 URLs / 50MB.\n *\n * Above that ceiling this is the wrong tool — the streaming writer retains\n * nothing and emits shards plus an index instead. The two modes are separate\n * on purpose: a class that promised not to retain entries and still offered\n * `entries()` would be lying about one of them.\n */\nexport class Sitemap {\n private readonly baseUrl: string;\n\n private readonly defaults: Pick<\n SitemapOptions,\n \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\"\n >;\n\n private readonly warnedImageRoutes = new Set<string>();\n\n private readonly optionsImageReporter: SitemapOptions[\"onImageLimitExceeded\"];\n\n /** Keyed by path: a duplicate `<loc>` makes the document invalid, so the later add wins. */\n private readonly entriesByPath = new Map<string, ResolvedSitemapEntry>();\n\n /** Insertion order per path, so `duplicates()` can name every colliding source. */\n private readonly routesByPath = new Map<string, (string | undefined)[]>();\n\n /** Declared patterns, including ones that never contributed a URL. */\n private readonly declaredRoutes = new Set<string>();\n\n public constructor(options: SitemapOptions) {\n this.baseUrl = normalizeBaseUrl(options?.baseUrl);\n this.optionsImageReporter = options.onImageLimitExceeded;\n this.defaults = {\n changefreq: options.changefreq,\n priority: options.priority,\n lastmod: options.lastmod,\n onImageLimitExceeded: (event) => this.reportImageLimit(event),\n };\n }\n\n private reportImageLimit(event: { readonly route?: string; readonly dropped: number }): void {\n const key = event.route ?? \"unattributed\";\n if (this.warnedImageRoutes.has(key)) return;\n\n this.warnedImageRoutes.add(key);\n if (this.optionsImageReporter) this.optionsImageReporter(event);\n else\n console.warn(\n `[warlock:sitemap] ${key} exceeded the 1,000-image limit; dropped ${event.dropped} image(s).`,\n );\n }\n\n public add(entry: SitemapEntry): this {\n const resolved = normalizeEntry(entry, this.defaults);\n\n this.entriesByPath.set(resolved.path, resolved);\n\n const seen = this.routesByPath.get(resolved.path);\n\n if (seen) {\n seen.push(resolved.route);\n } else {\n this.routesByPath.set(resolved.path, [resolved.route]);\n }\n\n if (resolved.route !== undefined) this.declaredRoutes.add(resolved.route);\n\n return this;\n }\n\n public addMany(entries: Iterable<SitemapEntry>): this {\n for (const entry of entries) this.add(entry);\n\n return this;\n }\n\n /**\n * Names a pattern the caller EXPECTS to contribute URLs, so that one which\n * contributes none shows up in `routes()` as a `count: 0` row instead of as\n * silence. A dynamic route whose supplier returned nothing is the failure\n * this package exists to make visible.\n */\n public declareRoute(route: string): this {\n this.declaredRoutes.add(route);\n\n return this;\n }\n\n public get size(): number {\n return this.entriesByPath.size;\n }\n\n public entries(): readonly ResolvedSitemapEntry[] {\n return [...this.entriesByPath.values()];\n }\n\n public routes(): readonly RouteSummary[] {\n const counts = new Map<string, number>();\n\n for (const route of this.declaredRoutes) counts.set(route, 0);\n\n for (const entry of this.entriesByPath.values()) {\n if (entry.route === undefined) continue;\n\n counts.set(entry.route, (counts.get(entry.route) ?? 0) + 1);\n }\n\n return [...counts].map(([route, count]) => ({ route, count }));\n }\n\n /**\n * Every path added more than once. The override is silent — it is not\n * hidden: a collision between static discovery and a dynamic supplier is a\n * real defect, and the CALLER decides whether it fails their build. This\n * package reports; it never prints and never throws over a duplicate.\n */\n public duplicates(): readonly DuplicateReport[] {\n const reports: DuplicateReport[] = [];\n\n for (const [path, routes] of this.routesByPath) {\n if (routes.length < 2) continue;\n\n reports.push({ path, count: routes.length, routes: [...routes] });\n }\n\n return reports;\n }\n\n /** Pure and repeatable: calling it twice returns the same string and mutates nothing. */\n public toXML(): string {\n return buildSitemapXml(this.entries(), this.baseUrl);\n }\n\n /**\n * Writes the document, creating parent directories so a clean checkout\n * works. The write is atomic: a failed or interrupted publish leaves\n * whatever was already at `filePath` untouched instead of truncating it.\n */\n public async saveTo(filePath: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n await atomicWriteFile(filePath, this.toXML());\n }\n\n /**\n * Publishes the document as the whole content of `outDir`, exactly the way\n * `SitemapIndex.saveTo` publishes a set: swapped in atomically, and marked\n * as owned. So a site that later outgrows one file can publish an index\n * into the same directory, and a later single file removes stale shards.\n * Refuses a non-empty directory this package did not write\n * (`UnownedOutputDirectoryError`). Returns the published file's path.\n */\n public async publishTo(outDir: string, fileName = \"sitemap.xml\"): Promise<string> {\n const xml = this.toXML();\n\n await publishAtomically(outDir, async (tempDir) => {\n await writeFile(join(tempDir, fileName), xml, \"utf8\");\n\n return [fileName];\n });\n\n return join(outDir, fileName);\n }\n}\n","import type { SitemapOptions } from \"./types\";\nimport { normalizeBaseUrl } from \"./url\";\nimport type { SitemapIndexOptions } from \"./sitemap-index-types\";\n\n/** The sitemaps.org limits. Never clamped to — a silently clamped option is a lie about what was written. */\nexport const PROTOCOL_MAX_URLS_PER_FILE = 50_000;\nexport const PROTOCOL_MAX_BYTES_PER_FILE = 50 * 1024 * 1024;\n\nexport type ResolvedSitemapIndexOptions = {\n readonly baseUrl: string;\n readonly filePrefix: string;\n readonly indexFileName: string;\n readonly shardPathPrefix: string;\n readonly gzip: boolean;\n readonly maxUrlsPerFile: number;\n readonly maxBytesPerFile: number;\n readonly defaults: Pick<\n SitemapOptions,\n \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\"\n >;\n};\n\n/** Validates and folds in defaults, once, at the constructor — the same discipline as `Sitemap`. */\nexport function normalizeSitemapIndexOptions(\n options: SitemapIndexOptions,\n): ResolvedSitemapIndexOptions {\n const baseUrl = normalizeBaseUrl(options?.baseUrl);\n const shardPathPrefix = normalizeShardPathPrefix(options.shardPathPrefix);\n\n const maxUrlsPerFile = options.maxUrlsPerFile ?? PROTOCOL_MAX_URLS_PER_FILE;\n const maxBytesPerFile = options.maxBytesPerFile ?? PROTOCOL_MAX_BYTES_PER_FILE;\n\n if (\n !Number.isInteger(maxUrlsPerFile) ||\n maxUrlsPerFile < 1 ||\n maxUrlsPerFile > PROTOCOL_MAX_URLS_PER_FILE\n ) {\n throw new RangeError(\n `maxUrlsPerFile must be an integer between 1 and the sitemaps.org ceiling of ` +\n `${PROTOCOL_MAX_URLS_PER_FILE}, got ${JSON.stringify(maxUrlsPerFile)}.`,\n );\n }\n\n if (\n !Number.isFinite(maxBytesPerFile) ||\n maxBytesPerFile < 1 ||\n maxBytesPerFile > PROTOCOL_MAX_BYTES_PER_FILE\n ) {\n throw new RangeError(\n `maxBytesPerFile must be between 1 and the sitemaps.org ceiling of ` +\n `${PROTOCOL_MAX_BYTES_PER_FILE} bytes, got ${JSON.stringify(maxBytesPerFile)}.`,\n );\n }\n\n return {\n baseUrl,\n filePrefix: options.filePrefix ?? \"sitemap\",\n indexFileName: options.indexFileName ?? \"sitemap_index.xml\",\n shardPathPrefix,\n gzip: options.gzip ?? false,\n maxUrlsPerFile,\n maxBytesPerFile,\n defaults: {\n changefreq: options.changefreq,\n priority: options.priority,\n lastmod: options.lastmod,\n onImageLimitExceeded: options.onImageLimitExceeded,\n },\n };\n}\n\nfunction normalizeShardPathPrefix(value: string | undefined): string {\n if (value === undefined || value === \"\" || value === \"/\") return \"\";\n\n const prefix = value.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n if (\n prefix.length === 0 ||\n prefix.split(\"/\").some((segment) => !/^[A-Za-z0-9_-]+$/.test(segment))\n ) {\n throw new TypeError(\"shardPathPrefix must contain only safe URL path segments.\");\n }\n\n return `${prefix}/`;\n}\n","const SAFE_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;\n\n/**\n * Canonical form used to detect a case collision before it reaches the\n * filesystem: `en-US` and `en-us` would produce the same file on a\n * case-insensitive filesystem and silently overwrite one another.\n *\n * The key reaches the filename and nothing else — it is validated as a\n * filename fragment, not interpreted as a locale or anything else.\n */\nexport function canonicalizeSourceKey(key: string): string {\n if (typeof key !== \"string\" || !SAFE_KEY_PATTERN.test(key)) {\n throw new RangeError(\n `sitemap source key ${JSON.stringify(key)} must be a non-empty filename fragment ` +\n `(letters, digits, \"-\", \"_\").`,\n );\n }\n\n return key.toLowerCase();\n}\n\n/**\n * Stable, zero-padded, ordinal from shard one. A group that later crosses a\n * ceiling GAINS a file; it never renames the first one, so a crawler that has\n * already indexed `sitemap-en-0001.xml` never loses it because the site grew.\n */\nexport function shardFileName(\n filePrefix: string,\n key: string | undefined,\n ordinal: number,\n gzip: boolean,\n): string {\n const paddedOrdinal = String(ordinal).padStart(4, \"0\");\n const base =\n key !== undefined ? `${filePrefix}-${key}-${paddedOrdinal}` : `${filePrefix}-${paddedOrdinal}`;\n\n return gzip ? `${base}.xml.gz` : `${base}.xml`;\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { gzipSync } from \"node:zlib\";\nimport { join } from \"node:path\";\nimport { normalizeEntry } from \"./normalize-entry\";\nimport { shardFileName } from \"./shard-name\";\nimport type { ResolvedSitemapEntry, SitemapOptions } from \"./types\";\nimport type { DuplicatePathTracker } from \"./duplicate-path-tracker\";\nimport type { RouteCounter } from \"./route-counter\";\nimport type { SitemapSourceFactory } from \"./sitemap-index-types\";\nimport { buildSitemapXml, IMAGE_NAMESPACE_ATTR, renderUrlBlock, XHTML_NAMESPACE_ATTR } from \"./xml\";\n\n/** One `addSource` group: the unnamed group merges every unkeyed call; a keyed group holds exactly one factory. */\nexport type ShardGroup = {\n readonly key?: string;\n readonly factories: readonly SitemapSourceFactory[];\n};\n\nexport type ShardWriterContext = {\n readonly tempDir: string;\n readonly baseUrl: string;\n readonly filePrefix: string;\n readonly gzip: boolean;\n readonly maxUrlsPerFile: number;\n readonly maxBytesPerFile: number;\n readonly defaults: Pick<\n SitemapOptions,\n \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\"\n >;\n readonly duplicates: DuplicatePathTracker;\n readonly routes: RouteCounter;\n};\n\n/** A shard actually written, or the zero-url row reported for a group that produced nothing. */\nexport type ShardFile = {\n readonly fileName: string;\n readonly key?: string;\n readonly urls: number;\n readonly bytes: number;\n readonly gzipped: boolean;\n};\n\nfunction envelopeBytes(baseUrl: string): number {\n return Buffer.byteLength(buildSitemapXml([], baseUrl), \"utf8\");\n}\n\n/**\n * The extra bytes `<urlset>` gains for `xmlns:xhtml=\"…\"` once a shard holds\n * an entry with alternates. Charged separately from `envelopeBytes()` so the\n * ceiling check can add it exactly once — the moment the first alternate\n * enters the buffer — rather than missing it entirely, which would let a\n * shard's real bytes on disk exceed the ceiling it was rolled against.\n */\nconst NAMESPACE_BYTES = Buffer.byteLength(XHTML_NAMESPACE_ATTR, \"utf8\");\nconst IMAGE_NAMESPACE_BYTES = Buffer.byteLength(IMAGE_NAMESPACE_ATTR, \"utf8\");\n\nasync function writeShardFile(\n ctx: ShardWriterContext,\n fileName: string,\n entries: readonly ResolvedSitemapEntry[],\n): Promise<{ bytes: number }> {\n const xml = buildSitemapXml(entries, ctx.baseUrl);\n const filePath = join(ctx.tempDir, fileName);\n\n if (ctx.gzip) {\n const compressed = gzipSync(Buffer.from(xml, \"utf8\"));\n\n await writeFile(filePath, compressed);\n\n return { bytes: compressed.byteLength };\n }\n\n await writeFile(filePath, xml, \"utf8\");\n\n return { bytes: Buffer.byteLength(xml, \"utf8\") };\n}\n\n/**\n * Walks one group's factories, one at a time, rolling to a new shard on\n * whichever ceiling — URL count or serialised bytes — is hit first. Holds\n * only the current shard's buffer, never the whole group.\n */\nexport async function writeShardGroup(\n group: ShardGroup,\n ctx: ShardWriterContext,\n): Promise<ShardFile[]> {\n const results: ShardFile[] = [];\n\n let buffer: ResolvedSitemapEntry[] = [];\n let bufferBytes = envelopeBytes(ctx.baseUrl);\n // Charged once, the moment the buffer's first alternate-bearing entry is added — mirrors\n // buildSitemapXml()'s own \"at least one entry has alternates\" rule for the same shard.\n let bufferHasAlternates = false;\n let bufferHasImages = false;\n let ordinal = 1;\n\n const flush = async () => {\n if (buffer.length === 0) return;\n\n const fileName = shardFileName(ctx.filePrefix, group.key, ordinal, ctx.gzip);\n const { bytes } = await writeShardFile(ctx, fileName, buffer);\n\n results.push({ fileName, key: group.key, urls: buffer.length, bytes, gzipped: ctx.gzip });\n\n ordinal += 1;\n buffer = [];\n bufferBytes = envelopeBytes(ctx.baseUrl);\n bufferHasAlternates = false;\n bufferHasImages = false;\n };\n\n for (const factory of group.factories) {\n const source = await factory();\n\n for await (const rawEntry of source) {\n const resolved = normalizeEntry(rawEntry, ctx.defaults);\n\n // Duplicate paths are SKIPPED, not overridden: the earlier one is already on disk.\n if (!ctx.duplicates.attempt(resolved.path, resolved.route)) continue;\n\n const entryHasAlternates = (resolved.alternates?.length ?? 0) > 0;\n const entryHasImages = (resolved.images?.length ?? 0) > 0;\n const blockBytes = Buffer.byteLength(renderUrlBlock(resolved, ctx.baseUrl), \"utf8\") + 1;\n // What this entry would add to the CURRENT shard: its own block, plus the namespace\n // attribute if this is the shard's first alternate and the buffer doesn't carry it yet.\n const addedBytes =\n blockBytes +\n (entryHasAlternates && !bufferHasAlternates ? NAMESPACE_BYTES : 0) +\n (entryHasImages && !bufferHasImages ? IMAGE_NAMESPACE_BYTES : 0);\n\n const hitsUrlCeiling = buffer.length >= ctx.maxUrlsPerFile;\n // A single entry can never be split, so the byte ceiling only rolls an already-nonempty shard.\n const hitsByteCeiling = buffer.length > 0 && bufferBytes + addedBytes > ctx.maxBytesPerFile;\n\n if (hitsUrlCeiling || hitsByteCeiling) await flush();\n\n const addsNamespaceNow = entryHasAlternates && !bufferHasAlternates;\n const addsImageNamespaceNow = entryHasImages && !bufferHasImages;\n\n buffer.push(resolved);\n bufferBytes +=\n blockBytes +\n (addsNamespaceNow ? NAMESPACE_BYTES : 0) +\n (addsImageNamespaceNow ? IMAGE_NAMESPACE_BYTES : 0);\n if (addsNamespaceNow) bufferHasAlternates = true;\n if (addsImageNamespaceNow) bufferHasImages = true;\n ctx.routes.record(resolved.route);\n }\n }\n\n await flush();\n\n if (results.length === 0) {\n // Reported, never written: an empty shard in an index is a section someone lost.\n results.push({\n fileName: shardFileName(ctx.filePrefix, group.key, 1, ctx.gzip),\n key: group.key,\n urls: 0,\n bytes: 0,\n gzipped: false,\n });\n }\n\n return results;\n}\n","import { escapeXml } from \"./xml\";\nimport { joinOrigin } from \"./url\";\nimport type { ShardFile } from \"./sitemap-shard-writer\";\n\n/**\n * Serialises the flat master `sitemapindex` document: every shard of every\n * group, directly, in the order the groups and shards were produced — no\n * nested per-group indexes, and never a zero-url row (that is a diagnostic\n * for `files`, not something a crawler should be told to fetch).\n */\nexport function buildSitemapIndexXml(\n files: readonly ShardFile[],\n baseUrl: string,\n shardPathPrefix = \"\",\n): string {\n const body = files\n .filter((file) => file.urls > 0)\n .map((file) => {\n const loc = escapeXml(joinOrigin(baseUrl, `${shardPathPrefix}${file.fileName}`));\n\n return ` <sitemap>\\n <loc>${loc}</loc>\\n </sitemap>`;\n })\n .join(\"\\n\");\n\n return (\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n` +\n `<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n` +\n (body.length > 0 ? `${body}\\n` : \"\") +\n `</sitemapindex>\\n`\n );\n}\n","import type { DuplicateReport } from \"./types\";\n\n/**\n * The streaming equivalent of `Sitemap`'s `routesByPath` map. A streaming\n * writer cannot compare an entry against 500K predecessors, so it keeps only\n * the paths (and contributing routes) it has already seen. A duplicate is\n * SKIPPED here, not last-wins — the earlier one is already on disk.\n */\nexport class DuplicatePathTracker {\n private readonly routesByPath = new Map<string, (string | undefined)[]>();\n\n /** Records an attempt to write `path`. Returns `false` when it was already seen — skip it. */\n public attempt(path: string, route: string | undefined): boolean {\n const seen = this.routesByPath.get(path);\n\n if (seen) {\n seen.push(route);\n\n return false;\n }\n\n this.routesByPath.set(path, [route]);\n\n return true;\n }\n\n public report(): DuplicateReport[] {\n const reports: DuplicateReport[] = [];\n\n for (const [path, routes] of this.routesByPath) {\n if (routes.length < 2) continue;\n\n reports.push({ path, count: routes.length, routes: [...routes] });\n }\n\n return reports;\n }\n}\n","import type { RouteSummary } from \"./types\";\n\n/** Counts URLs actually written per declared route, across every shard of every group. */\nexport class RouteCounter {\n private readonly counts = new Map<string, number>();\n\n public record(route: string | undefined): void {\n if (route === undefined) return;\n\n this.counts.set(route, (this.counts.get(route) ?? 0) + 1);\n }\n\n public summary(): RouteSummary[] {\n return [...this.counts].map(([route, count]) => ({ route, count }));\n }\n}\n","import { join } from \"node:path\";\nimport { writeFile } from \"node:fs/promises\";\nimport { DuplicateSourceKeyError } from \"./errors\";\nimport {\n normalizeSitemapIndexOptions,\n type ResolvedSitemapIndexOptions,\n} from \"./sitemap-index-options\";\nimport { canonicalizeSourceKey } from \"./shard-name\";\nimport { publishAtomically } from \"./atomic-publish\";\nimport { writeShardGroup, type ShardFile, type ShardGroup } from \"./sitemap-shard-writer\";\nimport { buildSitemapIndexXml } from \"./sitemap-index-xml\";\nimport { DuplicatePathTracker } from \"./duplicate-path-tracker\";\nimport { RouteCounter } from \"./route-counter\";\nimport type {\n SitemapFileResult,\n SitemapIndexOptions,\n SitemapSetResult,\n SitemapSourceFactory,\n} from \"./sitemap-index-types\";\n\n/**\n * The streaming path for 300–500K URLs: shards, one flat master index, gzip,\n * and an atomic publish. It is a SECOND mode, not a bigger `Sitemap` — it\n * retains only the current shard's buffer and the set of paths it has seen.\n * There is no `entries()`, `toXML()` or `size`; anyone who can afford those\n * is in `Sitemap` and should be there instead.\n */\nexport class SitemapIndex {\n private readonly options: ResolvedSitemapIndexOptions;\n\n /** Every unnamed `addSource(factory)` call merges into this one group, sharing one shard counter. */\n private readonly unnamedFactories: SitemapSourceFactory[] = [];\n\n private readonly namedGroups: ShardGroup[] = [];\n\n /** Canonical (lowercased) keys already registered, so a case collision is caught at `addSource()`. */\n private readonly registeredKeys = new Set<string>();\n\n private readonly warnedImageRoutes = new Set<string>();\n\n public constructor(options: SitemapIndexOptions) {\n this.options = normalizeSitemapIndexOptions(options);\n }\n\n public addSource(source: SitemapSourceFactory): this;\n public addSource(key: string, source: SitemapSourceFactory): this;\n public addSource(\n keyOrSource: string | SitemapSourceFactory,\n maybeSource?: SitemapSourceFactory,\n ): this {\n if (typeof keyOrSource === \"string\") {\n const canonicalKey = canonicalizeSourceKey(keyOrSource);\n\n if (this.registeredKeys.has(canonicalKey)) {\n throw new DuplicateSourceKeyError(keyOrSource);\n }\n\n this.registeredKeys.add(canonicalKey);\n this.namedGroups.push({ key: keyOrSource, factories: [maybeSource as SitemapSourceFactory] });\n } else {\n this.unnamedFactories.push(keyOrSource);\n }\n\n return this;\n }\n\n /**\n * Walks every group one at a time — never all at once — into a sibling\n * temp directory, then publishes the whole set atomically. `files` is\n * reported in registration order: the unnamed group first (if any), then\n * named groups key-then-ordinal.\n */\n public async saveTo(outDir: string): Promise<SitemapSetResult> {\n const groups: ShardGroup[] = [\n ...(this.unnamedFactories.length > 0\n ? [{ key: undefined, factories: this.unnamedFactories }]\n : []),\n ...this.namedGroups,\n ];\n\n const duplicates = new DuplicatePathTracker();\n const routes = new RouteCounter();\n let shardFiles: ShardFile[] = [];\n\n const write = async (tempDir: string): Promise<readonly string[]> => {\n shardFiles = [];\n\n for (const group of groups) {\n const files = await writeShardGroup(group, {\n tempDir,\n baseUrl: this.options.baseUrl,\n filePrefix: this.options.filePrefix,\n gzip: this.options.gzip,\n maxUrlsPerFile: this.options.maxUrlsPerFile,\n maxBytesPerFile: this.options.maxBytesPerFile,\n defaults: {\n ...this.options.defaults,\n onImageLimitExceeded: (event) => this.reportImageLimit(event),\n },\n duplicates,\n routes,\n });\n\n shardFiles.push(...files);\n }\n\n const indexXml = buildSitemapIndexXml(\n shardFiles,\n this.options.baseUrl,\n this.options.shardPathPrefix,\n );\n\n await writeFile(join(tempDir, this.options.indexFileName), indexXml, \"utf8\");\n\n const writtenShardNames = shardFiles\n .filter((file) => file.urls > 0)\n .map((file) => file.fileName);\n\n return [...writtenShardNames, this.options.indexFileName];\n };\n\n await publishAtomically(outDir, write);\n\n const files: SitemapFileResult[] = shardFiles.map((file) => ({\n path: join(outDir, file.fileName),\n ...(file.key !== undefined ? { key: file.key } : {}),\n urls: file.urls,\n bytes: file.bytes,\n gzipped: file.gzipped,\n }));\n\n return {\n indexPath: join(outDir, this.options.indexFileName),\n files,\n totalUrls: files.reduce((total, file) => total + file.urls, 0),\n duplicates: duplicates.report(),\n routes: routes.summary(),\n };\n }\n\n private reportImageLimit(event: { readonly route?: string; readonly dropped: number }): void {\n const key = event.route ?? \"unattributed\";\n if (this.warnedImageRoutes.has(key)) return;\n\n this.warnedImageRoutes.add(key);\n if (this.options.defaults.onImageLimitExceeded)\n this.options.defaults.onImageLimitExceeded(event);\n else\n console.warn(\n `[warlock:sitemap] ${key} exceeded the 1,000-image limit; dropped ${event.dropped} image(s).`,\n );\n }\n}\n","/** Storage-independent schema and key helpers for immutable sitemap generations. */\nexport const GENERATION_MANIFEST_VERSION = 1 as const;\nexport const MANIFEST_FENCE_WIDTH = 16;\n\nexport type SitemapGenerationKind = \"single\" | \"index\";\n\nexport type SitemapGenerationFile = {\n readonly path: string;\n readonly bytes: number;\n readonly sha256: string;\n};\n\nexport type SitemapGenerationManifest = {\n readonly version: typeof GENERATION_MANIFEST_VERSION;\n readonly fence: number;\n readonly generationId: string;\n readonly coversRev: number;\n readonly kind: SitemapGenerationKind;\n readonly mainFile: string;\n readonly files: readonly SitemapGenerationFile[];\n readonly entries: number;\n readonly generatedAt: string;\n};\n\nexport type SitemapManifestKey = {\n readonly key: string;\n readonly fence: number;\n readonly generationId: string;\n};\n\nexport class InvalidSitemapGenerationManifestError extends Error {\n public constructor(message: string) {\n super(`Invalid sitemap generation manifest: ${message}`);\n this.name = \"InvalidSitemapGenerationManifestError\";\n }\n}\n\nconst SAFE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;\nconst SAFE_FILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;\nconst SHA256 = /^[a-f0-9]{64}$/;\nconst ISO_DATE_TIME = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,9})?(?:Z|[+-]\\d{2}:\\d{2})$/;\nconst MANIFEST_KEY = /^manifests\\/(\\d{16})-([A-Za-z0-9][A-Za-z0-9_-]{0,127})\\.json$/;\n\nfunction fail(message: string): never {\n throw new InvalidSitemapGenerationManifestError(message);\n}\n\nfunction object(value: unknown, label: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value))\n return fail(`${label} must be an object.`);\n return value as Record<string, unknown>;\n}\n\nfunction nonNegativeInteger(value: unknown, label: string): number {\n if (!Number.isSafeInteger(value) || (value as number) < 0)\n return fail(`${label} must be a safe non-negative integer.`);\n return value as number;\n}\n\nfunction generationIdentifier(value: unknown, label: string): string {\n if (typeof value !== \"string\" || !SAFE_IDENTIFIER.test(value))\n return fail(`${label} must be a safe generation identifier.`);\n return value;\n}\n\nfunction generatedAt(value: unknown): string {\n if (typeof value !== \"string\" || !ISO_DATE_TIME.test(value) || Number.isNaN(Date.parse(value))) {\n return fail(\"generatedAt must be a valid ISO-8601 date-time.\");\n }\n return value;\n}\n\nfunction artifactPath(value: unknown, generationId: string, label: string): string {\n if (typeof value !== \"string\") return fail(`${label} must be a string.`);\n const prefix = `generations/${generationId}/`;\n if (!value.startsWith(prefix)) return fail(`${label} must be contained in ${prefix}.`);\n const fileName = value.slice(prefix.length);\n if (\n !SAFE_FILE_NAME.test(fileName) ||\n fileName.includes(\"/\") ||\n fileName === \".\" ||\n fileName === \"..\"\n ) {\n return fail(`${label} must name one safe, flat artifact file.`);\n }\n return value;\n}\n\nfunction manifestFile(value: unknown, generationId: string, index: number): SitemapGenerationFile {\n const record = object(value, `files[${index}]`);\n const path = artifactPath(record.path, generationId, `files[${index}].path`);\n const bytes = nonNegativeInteger(record.bytes, `files[${index}].bytes`);\n if (typeof record.sha256 !== \"string\" || !SHA256.test(record.sha256)) {\n return fail(`files[${index}].sha256 must be a lowercase SHA-256 hex digest.`);\n }\n return { path, bytes, sha256: record.sha256 };\n}\n\n/** Parse untrusted JSON into the v1 manifest contract. It does not touch storage. */\nexport function parseSitemapGenerationManifest(value: unknown): SitemapGenerationManifest {\n const record = object(value, \"manifest\");\n if (record.version !== GENERATION_MANIFEST_VERSION)\n return fail(`version must be ${GENERATION_MANIFEST_VERSION}.`);\n\n const fence = nonNegativeInteger(record.fence, \"fence\");\n const generationId = generationIdentifier(record.generationId, \"generationId\");\n const coversRev = nonNegativeInteger(record.coversRev, \"coversRev\");\n if (record.kind !== \"single\" && record.kind !== \"index\")\n return fail('kind must be \"single\" or \"index\".');\n if (!Array.isArray(record.files) || record.files.length === 0)\n return fail(\"files must be a non-empty array.\");\n\n const files = record.files.map((file, index) => manifestFile(file, generationId, index));\n const paths = new Set<string>();\n const fileNames = new Set<string>();\n for (const file of files) {\n const fileName = file.path.slice(`generations/${generationId}/`.length);\n if (paths.has(file.path))\n return fail(`files contains duplicate path ${JSON.stringify(file.path)}.`);\n if (fileNames.has(fileName))\n return fail(`files contains duplicate public file name ${JSON.stringify(fileName)}.`);\n paths.add(file.path);\n fileNames.add(fileName);\n }\n\n const mainFile = artifactPath(record.mainFile, generationId, \"mainFile\");\n if (!paths.has(mainFile)) return fail(\"mainFile must match one files[].path.\");\n\n return {\n version: GENERATION_MANIFEST_VERSION,\n fence,\n generationId,\n coversRev,\n kind: record.kind,\n mainFile,\n files,\n entries: nonNegativeInteger(record.entries, \"entries\"),\n generatedAt: generatedAt(record.generatedAt),\n };\n}\n\n/** Build the immutable v1 manifest object key for a generation fence. */\nexport function sitemapManifestKey(fence: number, generationId: string): string {\n const safeFence = nonNegativeInteger(fence, \"fence\");\n if (safeFence >= 10 ** MANIFEST_FENCE_WIDTH)\n return fail(`fence must fit within ${MANIFEST_FENCE_WIDTH} decimal digits.`);\n return `manifests/${String(safeFence).padStart(MANIFEST_FENCE_WIDTH, \"0\")}-${generationIdentifier(generationId, \"generationId\")}.json`;\n}\n/** Parse only the shape of a manifest object key; artifact validation belongs to the storage adapter. */\nexport function parseSitemapManifestKey(key: unknown): SitemapManifestKey | undefined {\n if (typeof key !== \"string\") return undefined;\n const match = MANIFEST_KEY.exec(key);\n if (!match) return undefined;\n const fence = Number(match[1]);\n const generationId = match[2];\n if (!Number.isSafeInteger(fence) || generationId === undefined) return undefined;\n return { key, fence, generationId };\n}\n\n/** Numeric-fence-descending candidates; callers validate storage contents before adoption. */\nexport function sortSitemapManifestCandidates(keys: readonly string[]): SitemapManifestKey[] {\n return keys\n .map(parseSitemapManifestKey)\n .filter((candidate): candidate is SitemapManifestKey => candidate !== undefined)\n .sort(\n (left, right) =>\n right.fence - left.fence || right.generationId.localeCompare(left.generationId),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["stat","join","readdir","dirname","mkdir","basename","writeFile","rename","rm","join","dirname","basename","mkdir","dirname","writeFile","join","join","gzipSync","writeFile","writeFile","join"],"sources":["../../../../../../../packages/sitemap/src/errors.ts","../../../../../../../packages/sitemap/src/atomic-publish.ts","../../../../../../../packages/sitemap/src/atomic-write-file.ts","../../../../../../../packages/sitemap/src/lastmod.ts","../../../../../../../packages/sitemap/src/normalize-entry.ts","../../../../../../../packages/sitemap/src/url.ts","../../../../../../../packages/sitemap/src/xml.ts","../../../../../../../packages/sitemap/src/sitemap.ts","../../../../../../../packages/sitemap/src/sitemap-index-options.ts","../../../../../../../packages/sitemap/src/shard-name.ts","../../../../../../../packages/sitemap/src/sitemap-shard-writer.ts","../../../../../../../packages/sitemap/src/sitemap-index-xml.ts","../../../../../../../packages/sitemap/src/duplicate-path-tracker.ts","../../../../../../../packages/sitemap/src/route-counter.ts","../../../../../../../packages/sitemap/src/sitemap-index.ts","../../../../../../../packages/sitemap/src/generation-manifest.ts"],"sourcesContent":["/**\n * The `baseUrl` given to a `Sitemap` is not an absolute http(s) URL.\n *\n * Thrown from the CONSTRUCTOR: a sitemap that cannot produce a valid URL\n * should not exist, and the mistake belongs at the line that wrote the value\n * rather than at the first request that reads it.\n */\nexport class InvalidBaseUrlError extends Error {\n public constructor(value: unknown, reason: string) {\n super(`Invalid sitemap baseUrl ${JSON.stringify(value)}: ${reason}.`);\n this.name = \"InvalidBaseUrlError\";\n }\n}\n\n/** An entry the sitemap protocol cannot represent. Thrown from `add()`. */\nexport class InvalidSitemapEntryError extends Error {\n public constructor(reason: string) {\n super(`Invalid sitemap entry: ${reason}.`);\n this.name = \"InvalidSitemapEntryError\";\n }\n}\n\n/**\n * Two `SitemapIndex` source keys collide once canonicalised (`en-US` and\n * `en-us` would produce the same filename on a case-insensitive filesystem\n * and silently overwrite one another). Thrown from `addSource()`, not at\n * `saveTo()`, so the mistake is caught at the line that registered it.\n */\nexport class DuplicateSourceKeyError extends Error {\n public constructor(key: string) {\n super(\n `Duplicate sitemap source key ${JSON.stringify(key)}: keys collide case-insensitively ` +\n `and would overwrite one another's shard files.`,\n );\n this.name = \"DuplicateSourceKeyError\";\n }\n}\n\n/**\n * `SitemapIndex.saveTo(outDir)` swaps the ENTIRE `outDir` for a freshly\n * written set (`atomic-publish.ts`). That is safe only when `outDir` is a\n * directory this package already owns — marked by its own\n * `.sitemap-set.json` from a prior publish. A non-empty directory with no\n * marker is presumed to belong to someone else (a caller's `public/`, most\n * dangerously) and is never swapped or deleted; this is thrown instead, from\n * `saveTo()` before anything is written.\n */\nexport class UnownedOutputDirectoryError extends Error {\n public constructor(outDir: string) {\n super(\n `Refusing to publish a sitemap set to ${JSON.stringify(outDir)}: this directory already ` +\n `has content but no \".sitemap-set.json\" marker from a previous @warlock.js/sitemap ` +\n `publish, so it is not safe to swap or delete. Point saveTo() at a dedicated, ` +\n `sitemap-only directory instead.`,\n );\n this.name = \"UnownedOutputDirectoryError\";\n }\n}\n","import { mkdir, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { UnownedOutputDirectoryError } from \"./errors\";\n\n/**\n * Marks a directory as one `publishAtomically` swapped into place. Its\n * presence is the ONLY thing that lets a later publish treat the directory\n * as safe to swap out from under itself — see {@link assertOutDirIsOwned}.\n */\nconst OWNERSHIP_MARKER_FILE = \".sitemap-set.json\";\nconst OWNERSHIP_MARKER_VERSION = 1;\n\nasync function validateShards(tempDir: string, fileNames: readonly string[]): Promise<void> {\n for (const fileName of fileNames) {\n const info = await stat(join(tempDir, fileName)).catch(() => undefined);\n\n if (!info || !info.isFile() || info.size === 0) {\n throw new Error(`sitemap publish aborted: shard \"${fileName}\" is missing or empty.`);\n }\n }\n}\n\n/**\n * `outDir` is safe to take over when it does not exist yet, is empty, or\n * already carries {@link OWNERSHIP_MARKER_FILE} from a previous publish.\n * Anything else — a non-empty directory this package never wrote — is\n * refused rather than swapped or deleted (`de97020e`).\n */\nasync function assertOutDirIsOwned(outDir: string): Promise<void> {\n const entries = await readdir(outDir).catch((error) => {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw error;\n });\n\n if (entries === undefined || entries.length === 0) return;\n\n if (!entries.includes(OWNERSHIP_MARKER_FILE)) {\n throw new UnownedOutputDirectoryError(outDir);\n }\n}\n\n/**\n * Writes a complete set into a sibling temp directory, validates that every\n * file it named actually exists and is non-empty, then swaps it into\n * `outDir`. A crawler arriving mid-write sees the previous complete set or\n * the new one, never a partial one, and a failed run leaves the previous set\n * untouched and rejects.\n *\n * `write` performs the writes and returns the file names (relative to the\n * temp dir) that must be present for the set to be considered valid — it is\n * only known after writing, since shard count depends on what was walked.\n */\nexport async function publishAtomically(\n outDir: string,\n write: (tempDir: string) => Promise<readonly string[]>,\n): Promise<void> {\n await assertOutDirIsOwned(outDir);\n\n const parent = dirname(outDir);\n\n await mkdir(parent, { recursive: true });\n\n const tempDir = join(parent, `.${basename(outDir)}.tmp-${process.pid}-${Date.now()}`);\n\n await mkdir(tempDir, { recursive: true });\n\n try {\n const fileNames = await write(tempDir);\n\n await validateShards(tempDir, fileNames);\n\n await writeFile(\n join(tempDir, OWNERSHIP_MARKER_FILE),\n JSON.stringify({ package: \"@warlock.js/sitemap\", version: OWNERSHIP_MARKER_VERSION }),\n \"utf8\",\n );\n\n const displacedDir = join(parent, `.${basename(outDir)}.previous-${Date.now()}`);\n let displacedPrevious = false;\n\n try {\n await rename(outDir, displacedDir);\n displacedPrevious = true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n\n try {\n await rename(tempDir, outDir);\n } catch (error) {\n if (displacedPrevious) await rename(displacedDir, outDir);\n throw error;\n }\n\n if (displacedPrevious) await rm(displacedDir, { recursive: true, force: true });\n } catch (error) {\n await rm(tempDir, { recursive: true, force: true });\n throw error;\n }\n}\n","import { rename, rm, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\n\n/** The subset of `node:fs/promises` this helper needs — swappable so a test can inject a failing write or rename without mocking the global module. */\nexport type AtomicWriteFileDeps = {\n writeFile: typeof writeFile;\n rename: typeof rename;\n rm: typeof rm;\n};\n\nconst defaultDeps: AtomicWriteFileDeps = { writeFile, rename, rm };\n\nconst RENAME_RETRY_ATTEMPTS = 5;\nconst RENAME_RETRY_DELAY_MS = 20;\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Windows can report a rename over an existing file as EPERM/EBUSY while something briefly holds the target (an AV scan, a reader) — retrying is correct there, not a masked bug. */\nfunction isTransientRenameError(error: unknown): boolean {\n const code = (error as NodeJS.ErrnoException)?.code;\n\n return code === \"EPERM\" || code === \"EBUSY\";\n}\n\nasync function renameWithRetry(from: string, to: string, deps: AtomicWriteFileDeps): Promise<void> {\n for (let attempt = 1; attempt <= RENAME_RETRY_ATTEMPTS; attempt++) {\n try {\n await deps.rename(from, to);\n\n return;\n } catch (error) {\n if (attempt === RENAME_RETRY_ATTEMPTS || !isTransientRenameError(error)) throw error;\n\n await delay(RENAME_RETRY_DELAY_MS * attempt);\n }\n }\n}\n\n/**\n * Writes `content` to `filePath` atomically: the content lands in a unique\n * sibling temp file first — same directory, so same filesystem, so the\n * rename that follows is atomic — and only then is renamed over the target.\n * An interrupted or failing write never truncates or otherwise touches the\n * existing target; on any failure the temp file is removed and the error is\n * rethrown.\n */\nexport async function atomicWriteFile(\n filePath: string,\n content: string,\n deps: AtomicWriteFileDeps = defaultDeps,\n): Promise<void> {\n const tempPath = join(\n dirname(filePath),\n `.${basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n );\n\n try {\n await deps.writeFile(tempPath, content, \"utf8\");\n await renameWithRetry(tempPath, filePath, deps);\n } catch (error) {\n await deps.rm(tempPath, { force: true }).catch(() => undefined);\n\n throw error;\n }\n}\n","import { InvalidSitemapEntryError } from \"./errors\";\n\n/**\n * Serialises a `lastmod` value.\n *\n * A `Date` becomes W3C datetime, which is what the schema wants and what\n * `toISOString()` already produces. A string is passed through UNTOUCHED: a\n * caller who already holds an ISO string gets it back verbatim rather than\n * having us re-parse it and risk shifting it across a timezone.\n */\nexport function formatLastmod(value: string | Date): string {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new InvalidSitemapEntryError(\"lastmod is an invalid Date\");\n }\n\n return value.toISOString();\n }\n\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new InvalidSitemapEntryError(\"lastmod must be a non-empty string or a Date\");\n }\n\n return value;\n}\n","import { InvalidSitemapEntryError } from \"./errors\";\nimport { formatLastmod } from \"./lastmod\";\nimport type {\n ChangeFreq,\n ResolvedSitemapEntry,\n SitemapEntry,\n SitemapImage,\n SitemapOptions,\n} from \"./types\";\n\nconst CHANGE_FREQS: readonly ChangeFreq[] = [\n \"always\",\n \"hourly\",\n \"daily\",\n \"weekly\",\n \"monthly\",\n \"yearly\",\n \"never\",\n];\nconst MAX_IMAGES_PER_URL = 1_000;\n\nfunction assertChangeFreq(value: unknown): asserts value is ChangeFreq {\n if (!CHANGE_FREQS.includes(value as ChangeFreq)) {\n throw new InvalidSitemapEntryError(\n `changefreq ${JSON.stringify(value)} is not one of ${CHANGE_FREQS.join(\", \")}`,\n );\n }\n}\n\nfunction assertPriority(value: unknown): asserts value is number {\n if (typeof value !== \"number\" || Number.isNaN(value) || value < 0 || value > 1) {\n throw new InvalidSitemapEntryError(\n `priority ${JSON.stringify(value)} is outside the protocol range 0.0–1.0`,\n );\n }\n}\n\n/** Every stored path carries its leading slash, so `/a` and `a` are one entry, not two. */\nexport function normalizePath(path: unknown): string {\n if (typeof path !== \"string\" || path.trim() === \"\") {\n throw new InvalidSitemapEntryError(\"path is required and must be a non-empty string\");\n }\n\n // An absolute URL is left alone: the caller is overriding the base origin\n // deliberately, which a cross-origin alternate legitimately needs.\n if (/^https?:\\/\\//i.test(path)) return path;\n\n return path.startsWith(\"/\") ? path : `/${path}`;\n}\n\n/**\n * Validates one entry and folds the builder's defaults into it. Defaults are\n * resolved HERE rather than at serialisation time so that `entries()` shows\n * what will actually be emitted — a diagnostic that reports something other\n * than the output is worse than none.\n */\nexport function normalizeEntry(\n entry: SitemapEntry,\n defaults: Pick<SitemapOptions, \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\">,\n): ResolvedSitemapEntry {\n const path = normalizePath(entry.path);\n const changefreq = entry.changefreq ?? defaults.changefreq;\n const priority = entry.priority ?? defaults.priority;\n const lastmod = entry.lastmod ?? defaults.lastmod;\n\n if (changefreq !== undefined) assertChangeFreq(changefreq);\n if (priority !== undefined) assertPriority(priority);\n\n const alternates = entry.alternates?.map((alternate) => {\n if (typeof alternate?.hreflang !== \"string\" || alternate.hreflang.trim() === \"\") {\n throw new InvalidSitemapEntryError(\"alternate hreflang is required\");\n }\n\n return { hreflang: alternate.hreflang, path: normalizePath(alternate.path) };\n });\n if (entry.images !== undefined && !Array.isArray(entry.images)) {\n throw new InvalidSitemapEntryError(\"images must be an array\");\n }\n const images = entry.images?.slice(0, MAX_IMAGES_PER_URL).map((image): SitemapImage => {\n if (typeof image?.loc !== \"string\" || image.loc.trim() === \"\") {\n throw new InvalidSitemapEntryError(\"image loc is required\");\n }\n\n let url: URL;\n try {\n url = new URL(image.loc);\n } catch {\n throw new InvalidSitemapEntryError(\"image loc must be an absolute HTTP(S) URL\");\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new InvalidSitemapEntryError(\"image loc must be an absolute HTTP(S) URL\");\n }\n\n return { loc: image.loc };\n });\n if ((entry.images?.length ?? 0) > MAX_IMAGES_PER_URL) {\n defaults.onImageLimitExceeded?.({\n ...(entry.route === undefined ? {} : { route: entry.route }),\n dropped: entry.images!.length - MAX_IMAGES_PER_URL,\n });\n }\n\n return {\n path,\n ...(entry.name !== undefined ? { name: entry.name } : {}),\n ...(entry.route !== undefined ? { route: entry.route } : {}),\n ...(lastmod !== undefined ? { lastmod: formatLastmod(lastmod) } : {}),\n ...(changefreq !== undefined ? { changefreq } : {}),\n ...(priority !== undefined ? { priority } : {}),\n ...(alternates !== undefined ? { alternates } : {}),\n ...(images !== undefined ? { images } : {}),\n };\n}\n","import { InvalidBaseUrlError } from \"./errors\";\n\n/**\n * Joins a configured origin and an app-relative route path into one absolute\n * URL, with exactly one slash at the seam regardless of whether either side\n * already carries one.\n */\nexport function joinOrigin(origin: string, routePath: string): string {\n const trimmedOrigin = origin.endsWith(\"/\") ? origin.slice(0, -1) : origin;\n const normalizedPath = routePath.startsWith(\"/\") ? routePath : `/${routePath}`;\n\n return `${trimmedOrigin}${normalizedPath}`;\n}\n\n/**\n * Validates a `baseUrl` and returns it without its trailing slash.\n *\n * `new URL()` accepts `mailto:` and `file:` happily, so the protocol is\n * checked explicitly — a sitemap `<loc>` that is not http(s) is not a document\n * any crawler will fetch.\n */\nexport function normalizeBaseUrl(value: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new InvalidBaseUrlError(value, \"expected a non-empty string\");\n }\n\n let parsed: URL;\n\n try {\n parsed = new URL(value);\n } catch {\n throw new InvalidBaseUrlError(value, \"not an absolute URL\");\n }\n\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new InvalidBaseUrlError(value, `unsupported protocol \"${parsed.protocol}\"`);\n }\n\n const href = parsed.href;\n\n return href.endsWith(\"/\") ? href.slice(0, -1) : href;\n}\n\n/** True for a value already usable as a `<loc>` without joining an origin. */\nexport function isAbsoluteUrl(value: string): boolean {\n return /^https?:\\/\\//i.test(value);\n}\n\n/** Resolves an entry or alternate path against the base URL, unless it is already absolute. */\nexport function resolveAgainstBase(baseUrl: string, path: string): string {\n return isAbsoluteUrl(path) ? path : joinOrigin(baseUrl, path);\n}\n","import type { ResolvedSitemapEntry } from \"./types\";\nimport { resolveAgainstBase } from \"./url\";\n\nconst XML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\n/** Escapes the five XML-significant characters. A URL's query string routinely contains `&`. */\nexport function escapeXml(value: string): string {\n // The character class and the table are written together, so the lookup can\n // only miss if one is edited without the other; falling back to the original\n // character keeps that editing mistake from silently emitting `undefined`\n // into a URL.\n return value.replace(/[&<>\"']/g, (char) => XML_ESCAPES[char] ?? char);\n}\n\nconst XHTML_NAMESPACE = \"http://www.w3.org/1999/xhtml\";\nconst IMAGE_NAMESPACE = \"http://www.google.com/schemas/sitemap-image/1.1\";\n\n/**\n * The `xmlns:xhtml` attribute exactly as it appears on `<urlset>`, including\n * its leading space. Exported so the shard writer can charge its byte length\n * against the ceiling without duplicating the string it measures.\n */\nexport const XHTML_NAMESPACE_ATTR = ` xmlns:xhtml=\"${XHTML_NAMESPACE}\"`;\n/** See {@link XHTML_NAMESPACE_ATTR}; measured by the shard writer before roll-over. */\nexport const IMAGE_NAMESPACE_ATTR = ` xmlns:image=\"${IMAGE_NAMESPACE}\"`;\n\n/**\n * Renders one `<url>` block. Exported so the streaming writer can measure the\n * exact bytes it is about to append before deciding whether the byte ceiling\n * forces a new shard — a separate approximation could disagree with what is\n * actually written.\n */\nexport function renderUrlBlock(entry: ResolvedSitemapEntry, baseUrl: string): string {\n const lines = [` <url>`, ` <loc>${escapeXml(resolveAgainstBase(baseUrl, entry.path))}</loc>`];\n\n if (entry.lastmod !== undefined) {\n lines.push(` <lastmod>${escapeXml(entry.lastmod)}</lastmod>`);\n }\n\n if (entry.changefreq !== undefined) {\n lines.push(` <changefreq>${entry.changefreq}</changefreq>`);\n }\n\n if (entry.priority !== undefined) {\n lines.push(` <priority>${entry.priority}</priority>`);\n }\n\n for (const alternate of entry.alternates ?? []) {\n const href = escapeXml(resolveAgainstBase(baseUrl, alternate.path));\n\n lines.push(\n ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alternate.hreflang)}\" href=\"${href}\"/>`,\n );\n }\n\n for (const image of entry.images ?? []) {\n lines.push(` <image:image>`);\n lines.push(` <image:loc>${escapeXml(resolveAgainstBase(baseUrl, image.loc))}</image:loc>`);\n lines.push(` </image:image>`);\n }\n\n lines.push(` </url>`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Serialises entries into a `urlset` sitemap document — the sitemaps.org\n * namespace, `<url>` per entry, element order `loc` / `lastmod` / `changefreq`\n * / `priority` (schema order; a validator that checks order rejects any other),\n * then any `xhtml:link` alternates.\n *\n * The xhtml namespace is declared only when some entry actually carries an\n * alternate — an unused namespace on every single-language sitemap is noise.\n */\nexport function buildSitemapXml(entries: readonly ResolvedSitemapEntry[], baseUrl: string): string {\n const hasAlternates = entries.some((entry) => (entry.alternates?.length ?? 0) > 0);\n const hasImages = entries.some((entry) => (entry.images?.length ?? 0) > 0);\n const namespaces = `${hasAlternates ? XHTML_NAMESPACE_ATTR : \"\"}${hasImages ? IMAGE_NAMESPACE_ATTR : \"\"}`;\n const body = entries.map((entry) => renderUrlBlock(entry, baseUrl)).join(\"\\n\");\n\n return (\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n` +\n `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"${namespaces}>\\n` +\n (body.length > 0 ? `${body}\\n` : \"\") +\n `</urlset>\\n`\n );\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { publishAtomically } from \"./atomic-publish\";\nimport { atomicWriteFile } from \"./atomic-write-file\";\nimport { normalizeEntry } from \"./normalize-entry\";\nimport type {\n DuplicateReport,\n ResolvedSitemapEntry,\n RouteSummary,\n SitemapEntry,\n SitemapOptions,\n} from \"./types\";\nimport { normalizeBaseUrl } from \"./url\";\nimport { buildSitemapXml } from \"./xml\";\n\n/**\n * A bounded sitemap builder: it RETAINS every entry, which is what makes\n * `entries()` and a repeatable `toXML()` possible, and is exactly right up to\n * the sitemaps.org ceiling of 50,000 URLs / 50MB.\n *\n * Above that ceiling this is the wrong tool — the streaming writer retains\n * nothing and emits shards plus an index instead. The two modes are separate\n * on purpose: a class that promised not to retain entries and still offered\n * `entries()` would be lying about one of them.\n */\nexport class Sitemap {\n private readonly baseUrl: string;\n\n private readonly defaults: Pick<\n SitemapOptions,\n \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\"\n >;\n\n private readonly warnedImageRoutes = new Set<string>();\n\n private readonly optionsImageReporter: SitemapOptions[\"onImageLimitExceeded\"];\n\n /** Keyed by path: a duplicate `<loc>` makes the document invalid, so the later add wins. */\n private readonly entriesByPath = new Map<string, ResolvedSitemapEntry>();\n\n /** Insertion order per path, so `duplicates()` can name every colliding source. */\n private readonly routesByPath = new Map<string, (string | undefined)[]>();\n\n /** Declared patterns, including ones that never contributed a URL. */\n private readonly declaredRoutes = new Set<string>();\n\n public constructor(options: SitemapOptions) {\n this.baseUrl = normalizeBaseUrl(options?.baseUrl);\n this.optionsImageReporter = options.onImageLimitExceeded;\n this.defaults = {\n changefreq: options.changefreq,\n priority: options.priority,\n lastmod: options.lastmod,\n onImageLimitExceeded: (event) => this.reportImageLimit(event),\n };\n }\n\n private reportImageLimit(event: { readonly route?: string; readonly dropped: number }): void {\n const key = event.route ?? \"unattributed\";\n if (this.warnedImageRoutes.has(key)) return;\n\n this.warnedImageRoutes.add(key);\n if (this.optionsImageReporter) this.optionsImageReporter(event);\n else\n console.warn(\n `[warlock:sitemap] ${key} exceeded the 1,000-image limit; dropped ${event.dropped} image(s).`,\n );\n }\n\n public add(entry: SitemapEntry): this {\n const resolved = normalizeEntry(entry, this.defaults);\n\n this.entriesByPath.set(resolved.path, resolved);\n\n const seen = this.routesByPath.get(resolved.path);\n\n if (seen) {\n seen.push(resolved.route);\n } else {\n this.routesByPath.set(resolved.path, [resolved.route]);\n }\n\n if (resolved.route !== undefined) this.declaredRoutes.add(resolved.route);\n\n return this;\n }\n\n public addMany(entries: Iterable<SitemapEntry>): this {\n for (const entry of entries) this.add(entry);\n\n return this;\n }\n\n /**\n * Names a pattern the caller EXPECTS to contribute URLs, so that one which\n * contributes none shows up in `routes()` as a `count: 0` row instead of as\n * silence. A dynamic route whose supplier returned nothing is the failure\n * this package exists to make visible.\n */\n public declareRoute(route: string): this {\n this.declaredRoutes.add(route);\n\n return this;\n }\n\n public get size(): number {\n return this.entriesByPath.size;\n }\n\n public entries(): readonly ResolvedSitemapEntry[] {\n return [...this.entriesByPath.values()];\n }\n\n public routes(): readonly RouteSummary[] {\n const counts = new Map<string, number>();\n\n for (const route of this.declaredRoutes) counts.set(route, 0);\n\n for (const entry of this.entriesByPath.values()) {\n if (entry.route === undefined) continue;\n\n counts.set(entry.route, (counts.get(entry.route) ?? 0) + 1);\n }\n\n return [...counts].map(([route, count]) => ({ route, count }));\n }\n\n /**\n * Every path added more than once. The override is silent — it is not\n * hidden: a collision between static discovery and a dynamic supplier is a\n * real defect, and the CALLER decides whether it fails their build. This\n * package reports; it never prints and never throws over a duplicate.\n */\n public duplicates(): readonly DuplicateReport[] {\n const reports: DuplicateReport[] = [];\n\n for (const [path, routes] of this.routesByPath) {\n if (routes.length < 2) continue;\n\n reports.push({ path, count: routes.length, routes: [...routes] });\n }\n\n return reports;\n }\n\n /** Pure and repeatable: calling it twice returns the same string and mutates nothing. */\n public toXML(): string {\n return buildSitemapXml(this.entries(), this.baseUrl);\n }\n\n /**\n * Writes the document, creating parent directories so a clean checkout\n * works. The write is atomic: a failed or interrupted publish leaves\n * whatever was already at `filePath` untouched instead of truncating it.\n */\n public async saveTo(filePath: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n await atomicWriteFile(filePath, this.toXML());\n }\n\n /**\n * Publishes the document as the whole content of `outDir`, exactly the way\n * `SitemapIndex.saveTo` publishes a set: swapped in atomically, and marked\n * as owned. So a site that later outgrows one file can publish an index\n * into the same directory, and a later single file removes stale shards.\n * Refuses a non-empty directory this package did not write\n * (`UnownedOutputDirectoryError`). Returns the published file's path.\n */\n public async publishTo(outDir: string, fileName = \"sitemap.xml\"): Promise<string> {\n const xml = this.toXML();\n\n await publishAtomically(outDir, async (tempDir) => {\n await writeFile(join(tempDir, fileName), xml, \"utf8\");\n\n return [fileName];\n });\n\n return join(outDir, fileName);\n }\n}\n","import type { SitemapOptions } from \"./types\";\nimport { normalizeBaseUrl } from \"./url\";\nimport type { SitemapIndexOptions } from \"./sitemap-index-types\";\n\n/** The sitemaps.org limits. Never clamped to — a silently clamped option is a lie about what was written. */\nexport const PROTOCOL_MAX_URLS_PER_FILE = 50_000;\nexport const PROTOCOL_MAX_BYTES_PER_FILE = 50 * 1024 * 1024;\n\nexport type ResolvedSitemapIndexOptions = {\n readonly baseUrl: string;\n readonly filePrefix: string;\n readonly indexFileName: string;\n readonly shardPathPrefix: string;\n readonly gzip: boolean;\n readonly maxUrlsPerFile: number;\n readonly maxBytesPerFile: number;\n readonly defaults: Pick<\n SitemapOptions,\n \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\"\n >;\n};\n\n/** Validates and folds in defaults, once, at the constructor — the same discipline as `Sitemap`. */\nexport function normalizeSitemapIndexOptions(\n options: SitemapIndexOptions,\n): ResolvedSitemapIndexOptions {\n const baseUrl = normalizeBaseUrl(options?.baseUrl);\n const shardPathPrefix = normalizeShardPathPrefix(options.shardPathPrefix);\n\n const maxUrlsPerFile = options.maxUrlsPerFile ?? PROTOCOL_MAX_URLS_PER_FILE;\n const maxBytesPerFile = options.maxBytesPerFile ?? PROTOCOL_MAX_BYTES_PER_FILE;\n\n if (\n !Number.isInteger(maxUrlsPerFile) ||\n maxUrlsPerFile < 1 ||\n maxUrlsPerFile > PROTOCOL_MAX_URLS_PER_FILE\n ) {\n throw new RangeError(\n `maxUrlsPerFile must be an integer between 1 and the sitemaps.org ceiling of ` +\n `${PROTOCOL_MAX_URLS_PER_FILE}, got ${JSON.stringify(maxUrlsPerFile)}.`,\n );\n }\n\n if (\n !Number.isFinite(maxBytesPerFile) ||\n maxBytesPerFile < 1 ||\n maxBytesPerFile > PROTOCOL_MAX_BYTES_PER_FILE\n ) {\n throw new RangeError(\n `maxBytesPerFile must be between 1 and the sitemaps.org ceiling of ` +\n `${PROTOCOL_MAX_BYTES_PER_FILE} bytes, got ${JSON.stringify(maxBytesPerFile)}.`,\n );\n }\n\n return {\n baseUrl,\n filePrefix: options.filePrefix ?? \"sitemap\",\n indexFileName: options.indexFileName ?? \"sitemap_index.xml\",\n shardPathPrefix,\n gzip: options.gzip ?? false,\n maxUrlsPerFile,\n maxBytesPerFile,\n defaults: {\n changefreq: options.changefreq,\n priority: options.priority,\n lastmod: options.lastmod,\n onImageLimitExceeded: options.onImageLimitExceeded,\n },\n };\n}\n\nfunction normalizeShardPathPrefix(value: string | undefined): string {\n if (value === undefined || value === \"\" || value === \"/\") return \"\";\n\n const prefix = value.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n if (\n prefix.length === 0 ||\n prefix.split(\"/\").some((segment) => !/^[A-Za-z0-9_-]+$/.test(segment))\n ) {\n throw new TypeError(\"shardPathPrefix must contain only safe URL path segments.\");\n }\n\n return `${prefix}/`;\n}\n","const SAFE_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;\n\n/**\n * Canonical form used to detect a case collision before it reaches the\n * filesystem: `en-US` and `en-us` would produce the same file on a\n * case-insensitive filesystem and silently overwrite one another.\n *\n * The key reaches the filename and nothing else — it is validated as a\n * filename fragment, not interpreted as a locale or anything else.\n */\nexport function canonicalizeSourceKey(key: string): string {\n if (typeof key !== \"string\" || !SAFE_KEY_PATTERN.test(key)) {\n throw new RangeError(\n `sitemap source key ${JSON.stringify(key)} must be a non-empty filename fragment ` +\n `(letters, digits, \"-\", \"_\").`,\n );\n }\n\n return key.toLowerCase();\n}\n\n/**\n * Stable, zero-padded, ordinal from shard one. A group that later crosses a\n * ceiling GAINS a file; it never renames the first one, so a crawler that has\n * already indexed `sitemap-en-0001.xml` never loses it because the site grew.\n */\nexport function shardFileName(\n filePrefix: string,\n key: string | undefined,\n ordinal: number,\n gzip: boolean,\n): string {\n const paddedOrdinal = String(ordinal).padStart(4, \"0\");\n const base =\n key !== undefined ? `${filePrefix}-${key}-${paddedOrdinal}` : `${filePrefix}-${paddedOrdinal}`;\n\n return gzip ? `${base}.xml.gz` : `${base}.xml`;\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { gzipSync } from \"node:zlib\";\nimport { join } from \"node:path\";\nimport { normalizeEntry } from \"./normalize-entry\";\nimport { shardFileName } from \"./shard-name\";\nimport type { ResolvedSitemapEntry, SitemapOptions } from \"./types\";\nimport type { DuplicatePathTracker } from \"./duplicate-path-tracker\";\nimport type { RouteCounter } from \"./route-counter\";\nimport type { SitemapSourceFactory } from \"./sitemap-index-types\";\nimport { buildSitemapXml, IMAGE_NAMESPACE_ATTR, renderUrlBlock, XHTML_NAMESPACE_ATTR } from \"./xml\";\n\n/** One `addSource` group: the unnamed group merges every unkeyed call; a keyed group holds exactly one factory. */\nexport type ShardGroup = {\n readonly key?: string;\n readonly factories: readonly SitemapSourceFactory[];\n};\n\nexport type ShardWriterContext = {\n readonly tempDir: string;\n readonly baseUrl: string;\n readonly filePrefix: string;\n readonly gzip: boolean;\n readonly maxUrlsPerFile: number;\n readonly maxBytesPerFile: number;\n readonly defaults: Pick<\n SitemapOptions,\n \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\"\n >;\n readonly duplicates: DuplicatePathTracker;\n readonly routes: RouteCounter;\n};\n\n/** A shard actually written, or the zero-url row reported for a group that produced nothing. */\nexport type ShardFile = {\n readonly fileName: string;\n readonly key?: string;\n readonly urls: number;\n readonly bytes: number;\n readonly gzipped: boolean;\n};\n\nfunction envelopeBytes(baseUrl: string): number {\n return Buffer.byteLength(buildSitemapXml([], baseUrl), \"utf8\");\n}\n\n/**\n * The extra bytes `<urlset>` gains for `xmlns:xhtml=\"…\"` once a shard holds\n * an entry with alternates. Charged separately from `envelopeBytes()` so the\n * ceiling check can add it exactly once — the moment the first alternate\n * enters the buffer — rather than missing it entirely, which would let a\n * shard's real bytes on disk exceed the ceiling it was rolled against.\n */\nconst NAMESPACE_BYTES = Buffer.byteLength(XHTML_NAMESPACE_ATTR, \"utf8\");\nconst IMAGE_NAMESPACE_BYTES = Buffer.byteLength(IMAGE_NAMESPACE_ATTR, \"utf8\");\n\nasync function writeShardFile(\n ctx: ShardWriterContext,\n fileName: string,\n entries: readonly ResolvedSitemapEntry[],\n): Promise<{ bytes: number }> {\n const xml = buildSitemapXml(entries, ctx.baseUrl);\n const filePath = join(ctx.tempDir, fileName);\n\n if (ctx.gzip) {\n const compressed = gzipSync(Buffer.from(xml, \"utf8\"));\n\n await writeFile(filePath, compressed);\n\n return { bytes: compressed.byteLength };\n }\n\n await writeFile(filePath, xml, \"utf8\");\n\n return { bytes: Buffer.byteLength(xml, \"utf8\") };\n}\n\n/**\n * Walks one group's factories, one at a time, rolling to a new shard on\n * whichever ceiling — URL count or serialised bytes — is hit first. Holds\n * only the current shard's buffer, never the whole group.\n */\nexport async function writeShardGroup(\n group: ShardGroup,\n ctx: ShardWriterContext,\n): Promise<ShardFile[]> {\n const results: ShardFile[] = [];\n\n let buffer: ResolvedSitemapEntry[] = [];\n let bufferBytes = envelopeBytes(ctx.baseUrl);\n // Charged once, the moment the buffer's first alternate-bearing entry is added — mirrors\n // buildSitemapXml()'s own \"at least one entry has alternates\" rule for the same shard.\n let bufferHasAlternates = false;\n let bufferHasImages = false;\n let ordinal = 1;\n\n const flush = async () => {\n if (buffer.length === 0) return;\n\n const fileName = shardFileName(ctx.filePrefix, group.key, ordinal, ctx.gzip);\n const { bytes } = await writeShardFile(ctx, fileName, buffer);\n\n results.push({ fileName, key: group.key, urls: buffer.length, bytes, gzipped: ctx.gzip });\n\n ordinal += 1;\n buffer = [];\n bufferBytes = envelopeBytes(ctx.baseUrl);\n bufferHasAlternates = false;\n bufferHasImages = false;\n };\n\n for (const factory of group.factories) {\n const source = await factory();\n\n for await (const rawEntry of source) {\n const resolved = normalizeEntry(rawEntry, ctx.defaults);\n\n // Duplicate paths are SKIPPED, not overridden: the earlier one is already on disk.\n if (!ctx.duplicates.attempt(resolved.path, resolved.route)) continue;\n\n const entryHasAlternates = (resolved.alternates?.length ?? 0) > 0;\n const entryHasImages = (resolved.images?.length ?? 0) > 0;\n const blockBytes = Buffer.byteLength(renderUrlBlock(resolved, ctx.baseUrl), \"utf8\") + 1;\n // What this entry would add to the CURRENT shard: its own block, plus the namespace\n // attribute if this is the shard's first alternate and the buffer doesn't carry it yet.\n const addedBytes =\n blockBytes +\n (entryHasAlternates && !bufferHasAlternates ? NAMESPACE_BYTES : 0) +\n (entryHasImages && !bufferHasImages ? IMAGE_NAMESPACE_BYTES : 0);\n\n const hitsUrlCeiling = buffer.length >= ctx.maxUrlsPerFile;\n // A single entry can never be split, so the byte ceiling only rolls an already-nonempty shard.\n const hitsByteCeiling = buffer.length > 0 && bufferBytes + addedBytes > ctx.maxBytesPerFile;\n\n if (hitsUrlCeiling || hitsByteCeiling) await flush();\n\n const addsNamespaceNow = entryHasAlternates && !bufferHasAlternates;\n const addsImageNamespaceNow = entryHasImages && !bufferHasImages;\n\n buffer.push(resolved);\n bufferBytes +=\n blockBytes +\n (addsNamespaceNow ? NAMESPACE_BYTES : 0) +\n (addsImageNamespaceNow ? IMAGE_NAMESPACE_BYTES : 0);\n if (addsNamespaceNow) bufferHasAlternates = true;\n if (addsImageNamespaceNow) bufferHasImages = true;\n ctx.routes.record(resolved.route);\n }\n }\n\n await flush();\n\n if (results.length === 0) {\n // Reported, never written: an empty shard in an index is a section someone lost.\n results.push({\n fileName: shardFileName(ctx.filePrefix, group.key, 1, ctx.gzip),\n key: group.key,\n urls: 0,\n bytes: 0,\n gzipped: false,\n });\n }\n\n return results;\n}\n","import { escapeXml } from \"./xml\";\nimport { joinOrigin } from \"./url\";\nimport type { ShardFile } from \"./sitemap-shard-writer\";\n\n/**\n * Serialises the flat master `sitemapindex` document: every shard of every\n * group, directly, in the order the groups and shards were produced — no\n * nested per-group indexes, and never a zero-url row (that is a diagnostic\n * for `files`, not something a crawler should be told to fetch).\n */\nexport function buildSitemapIndexXml(\n files: readonly ShardFile[],\n baseUrl: string,\n shardPathPrefix = \"\",\n): string {\n const body = files\n .filter((file) => file.urls > 0)\n .map((file) => {\n const loc = escapeXml(joinOrigin(baseUrl, `${shardPathPrefix}${file.fileName}`));\n\n return ` <sitemap>\\n <loc>${loc}</loc>\\n </sitemap>`;\n })\n .join(\"\\n\");\n\n return (\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n` +\n `<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n` +\n (body.length > 0 ? `${body}\\n` : \"\") +\n `</sitemapindex>\\n`\n );\n}\n","import type { DuplicateReport } from \"./types\";\n\n/**\n * The streaming equivalent of `Sitemap`'s `routesByPath` map. A streaming\n * writer cannot compare an entry against 500K predecessors, so it keeps only\n * the paths (and contributing routes) it has already seen. A duplicate is\n * SKIPPED here, not last-wins — the earlier one is already on disk.\n */\nexport class DuplicatePathTracker {\n private readonly routesByPath = new Map<string, (string | undefined)[]>();\n\n /** Records an attempt to write `path`. Returns `false` when it was already seen — skip it. */\n public attempt(path: string, route: string | undefined): boolean {\n const seen = this.routesByPath.get(path);\n\n if (seen) {\n seen.push(route);\n\n return false;\n }\n\n this.routesByPath.set(path, [route]);\n\n return true;\n }\n\n public report(): DuplicateReport[] {\n const reports: DuplicateReport[] = [];\n\n for (const [path, routes] of this.routesByPath) {\n if (routes.length < 2) continue;\n\n reports.push({ path, count: routes.length, routes: [...routes] });\n }\n\n return reports;\n }\n}\n","import type { RouteSummary } from \"./types\";\n\n/** Counts URLs actually written per declared route, across every shard of every group. */\nexport class RouteCounter {\n private readonly counts = new Map<string, number>();\n\n public record(route: string | undefined): void {\n if (route === undefined) return;\n\n this.counts.set(route, (this.counts.get(route) ?? 0) + 1);\n }\n\n public summary(): RouteSummary[] {\n return [...this.counts].map(([route, count]) => ({ route, count }));\n }\n}\n","import { join } from \"node:path\";\nimport { writeFile } from \"node:fs/promises\";\nimport { DuplicateSourceKeyError } from \"./errors\";\nimport {\n normalizeSitemapIndexOptions,\n type ResolvedSitemapIndexOptions,\n} from \"./sitemap-index-options\";\nimport { canonicalizeSourceKey } from \"./shard-name\";\nimport { publishAtomically } from \"./atomic-publish\";\nimport { writeShardGroup, type ShardFile, type ShardGroup } from \"./sitemap-shard-writer\";\nimport { buildSitemapIndexXml } from \"./sitemap-index-xml\";\nimport { DuplicatePathTracker } from \"./duplicate-path-tracker\";\nimport { RouteCounter } from \"./route-counter\";\nimport type {\n SitemapFileResult,\n SitemapIndexOptions,\n SitemapSetResult,\n SitemapSourceFactory,\n} from \"./sitemap-index-types\";\n\n/**\n * The streaming path for 300–500K URLs: shards, one flat master index, gzip,\n * and an atomic publish. It is a SECOND mode, not a bigger `Sitemap` — it\n * retains only the current shard's buffer and the set of paths it has seen.\n * There is no `entries()`, `toXML()` or `size`; anyone who can afford those\n * is in `Sitemap` and should be there instead.\n */\nexport class SitemapIndex {\n private readonly options: ResolvedSitemapIndexOptions;\n\n /** Every unnamed `addSource(factory)` call merges into this one group, sharing one shard counter. */\n private readonly unnamedFactories: SitemapSourceFactory[] = [];\n\n private readonly namedGroups: ShardGroup[] = [];\n\n /** Canonical (lowercased) keys already registered, so a case collision is caught at `addSource()`. */\n private readonly registeredKeys = new Set<string>();\n\n private readonly warnedImageRoutes = new Set<string>();\n\n public constructor(options: SitemapIndexOptions) {\n this.options = normalizeSitemapIndexOptions(options);\n }\n\n public addSource(source: SitemapSourceFactory): this;\n public addSource(key: string, source: SitemapSourceFactory): this;\n public addSource(\n keyOrSource: string | SitemapSourceFactory,\n maybeSource?: SitemapSourceFactory,\n ): this {\n if (typeof keyOrSource === \"string\") {\n const canonicalKey = canonicalizeSourceKey(keyOrSource);\n\n if (this.registeredKeys.has(canonicalKey)) {\n throw new DuplicateSourceKeyError(keyOrSource);\n }\n\n this.registeredKeys.add(canonicalKey);\n this.namedGroups.push({ key: keyOrSource, factories: [maybeSource as SitemapSourceFactory] });\n } else {\n this.unnamedFactories.push(keyOrSource);\n }\n\n return this;\n }\n\n /**\n * Walks every group one at a time — never all at once — into a sibling\n * temp directory, then publishes the whole set atomically. `files` is\n * reported in registration order: the unnamed group first (if any), then\n * named groups key-then-ordinal.\n */\n public async saveTo(outDir: string): Promise<SitemapSetResult> {\n const groups: ShardGroup[] = [\n ...(this.unnamedFactories.length > 0\n ? [{ key: undefined, factories: this.unnamedFactories }]\n : []),\n ...this.namedGroups,\n ];\n\n const duplicates = new DuplicatePathTracker();\n const routes = new RouteCounter();\n let shardFiles: ShardFile[] = [];\n\n const write = async (tempDir: string): Promise<readonly string[]> => {\n shardFiles = [];\n\n for (const group of groups) {\n const files = await writeShardGroup(group, {\n tempDir,\n baseUrl: this.options.baseUrl,\n filePrefix: this.options.filePrefix,\n gzip: this.options.gzip,\n maxUrlsPerFile: this.options.maxUrlsPerFile,\n maxBytesPerFile: this.options.maxBytesPerFile,\n defaults: {\n ...this.options.defaults,\n onImageLimitExceeded: (event) => this.reportImageLimit(event),\n },\n duplicates,\n routes,\n });\n\n shardFiles.push(...files);\n }\n\n const indexXml = buildSitemapIndexXml(\n shardFiles,\n this.options.baseUrl,\n this.options.shardPathPrefix,\n );\n\n await writeFile(join(tempDir, this.options.indexFileName), indexXml, \"utf8\");\n\n const writtenShardNames = shardFiles\n .filter((file) => file.urls > 0)\n .map((file) => file.fileName);\n\n return [...writtenShardNames, this.options.indexFileName];\n };\n\n await publishAtomically(outDir, write);\n\n const files: SitemapFileResult[] = shardFiles.map((file) => ({\n path: join(outDir, file.fileName),\n ...(file.key !== undefined ? { key: file.key } : {}),\n urls: file.urls,\n bytes: file.bytes,\n gzipped: file.gzipped,\n }));\n\n return {\n indexPath: join(outDir, this.options.indexFileName),\n files,\n totalUrls: files.reduce((total, file) => total + file.urls, 0),\n duplicates: duplicates.report(),\n routes: routes.summary(),\n };\n }\n\n private reportImageLimit(event: { readonly route?: string; readonly dropped: number }): void {\n const key = event.route ?? \"unattributed\";\n if (this.warnedImageRoutes.has(key)) return;\n\n this.warnedImageRoutes.add(key);\n if (this.options.defaults.onImageLimitExceeded)\n this.options.defaults.onImageLimitExceeded(event);\n else\n console.warn(\n `[warlock:sitemap] ${key} exceeded the 1,000-image limit; dropped ${event.dropped} image(s).`,\n );\n }\n}\n","/** Storage-independent schema and key helpers for immutable sitemap generations. */\nexport const GENERATION_MANIFEST_VERSION = 1 as const;\nexport const MANIFEST_FENCE_WIDTH = 16;\n\nexport type SitemapGenerationKind = \"single\" | \"index\";\n\nexport type SitemapGenerationFile = {\n readonly path: string;\n readonly bytes: number;\n readonly sha256: string;\n};\n\nexport type SitemapGenerationManifest = {\n readonly version: typeof GENERATION_MANIFEST_VERSION;\n readonly fence: number;\n readonly generationId: string;\n readonly coversRev: number;\n readonly kind: SitemapGenerationKind;\n readonly mainFile: string;\n readonly files: readonly SitemapGenerationFile[];\n readonly entries: number;\n readonly generatedAt: string;\n};\n\nexport type SitemapManifestKey = {\n readonly key: string;\n readonly fence: number;\n readonly generationId: string;\n};\n\nexport class InvalidSitemapGenerationManifestError extends Error {\n public constructor(message: string) {\n super(`Invalid sitemap generation manifest: ${message}`);\n this.name = \"InvalidSitemapGenerationManifestError\";\n }\n}\n\nconst SAFE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;\nconst SAFE_FILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;\nconst SHA256 = /^[a-f0-9]{64}$/;\nconst ISO_DATE_TIME = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,9})?(?:Z|[+-]\\d{2}:\\d{2})$/;\nconst MANIFEST_KEY = /^manifests\\/(\\d{16})-([A-Za-z0-9][A-Za-z0-9_-]{0,127})\\.json$/;\n\nfunction fail(message: string): never {\n throw new InvalidSitemapGenerationManifestError(message);\n}\n\nfunction object(value: unknown, label: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value))\n return fail(`${label} must be an object.`);\n return value as Record<string, unknown>;\n}\n\nfunction nonNegativeInteger(value: unknown, label: string): number {\n if (!Number.isSafeInteger(value) || (value as number) < 0)\n return fail(`${label} must be a safe non-negative integer.`);\n return value as number;\n}\n\nfunction generationIdentifier(value: unknown, label: string): string {\n if (typeof value !== \"string\" || !SAFE_IDENTIFIER.test(value))\n return fail(`${label} must be a safe generation identifier.`);\n return value;\n}\n\nfunction generatedAt(value: unknown): string {\n if (typeof value !== \"string\" || !ISO_DATE_TIME.test(value) || Number.isNaN(Date.parse(value))) {\n return fail(\"generatedAt must be a valid ISO-8601 date-time.\");\n }\n return value;\n}\n\nfunction artifactPath(value: unknown, generationId: string, label: string): string {\n if (typeof value !== \"string\") return fail(`${label} must be a string.`);\n const prefix = `generations/${generationId}/`;\n if (!value.startsWith(prefix)) return fail(`${label} must be contained in ${prefix}.`);\n const fileName = value.slice(prefix.length);\n if (\n !SAFE_FILE_NAME.test(fileName) ||\n fileName.includes(\"/\") ||\n fileName === \".\" ||\n fileName === \"..\"\n ) {\n return fail(`${label} must name one safe, flat artifact file.`);\n }\n return value;\n}\n\nfunction manifestFile(value: unknown, generationId: string, index: number): SitemapGenerationFile {\n const record = object(value, `files[${index}]`);\n const path = artifactPath(record.path, generationId, `files[${index}].path`);\n const bytes = nonNegativeInteger(record.bytes, `files[${index}].bytes`);\n if (typeof record.sha256 !== \"string\" || !SHA256.test(record.sha256)) {\n return fail(`files[${index}].sha256 must be a lowercase SHA-256 hex digest.`);\n }\n return { path, bytes, sha256: record.sha256 };\n}\n\n/** Parse untrusted JSON into the v1 manifest contract. It does not touch storage. */\nexport function parseSitemapGenerationManifest(value: unknown): SitemapGenerationManifest {\n const record = object(value, \"manifest\");\n if (record.version !== GENERATION_MANIFEST_VERSION)\n return fail(`version must be ${GENERATION_MANIFEST_VERSION}.`);\n\n const fence = nonNegativeInteger(record.fence, \"fence\");\n const generationId = generationIdentifier(record.generationId, \"generationId\");\n const coversRev = nonNegativeInteger(record.coversRev, \"coversRev\");\n if (record.kind !== \"single\" && record.kind !== \"index\")\n return fail('kind must be \"single\" or \"index\".');\n if (!Array.isArray(record.files) || record.files.length === 0)\n return fail(\"files must be a non-empty array.\");\n\n const files = record.files.map((file, index) => manifestFile(file, generationId, index));\n const paths = new Set<string>();\n const fileNames = new Set<string>();\n for (const file of files) {\n const fileName = file.path.slice(`generations/${generationId}/`.length);\n if (paths.has(file.path))\n return fail(`files contains duplicate path ${JSON.stringify(file.path)}.`);\n if (fileNames.has(fileName))\n return fail(`files contains duplicate public file name ${JSON.stringify(fileName)}.`);\n paths.add(file.path);\n fileNames.add(fileName);\n }\n\n const mainFile = artifactPath(record.mainFile, generationId, \"mainFile\");\n if (!paths.has(mainFile)) return fail(\"mainFile must match one files[].path.\");\n\n return {\n version: GENERATION_MANIFEST_VERSION,\n fence,\n generationId,\n coversRev,\n kind: record.kind,\n mainFile,\n files,\n entries: nonNegativeInteger(record.entries, \"entries\"),\n generatedAt: generatedAt(record.generatedAt),\n };\n}\n\n/** Build the immutable v1 manifest object key for a generation fence. */\nexport function sitemapManifestKey(fence: number, generationId: string): string {\n const safeFence = nonNegativeInteger(fence, \"fence\");\n if (safeFence >= 10 ** MANIFEST_FENCE_WIDTH)\n return fail(`fence must fit within ${MANIFEST_FENCE_WIDTH} decimal digits.`);\n return `manifests/${String(safeFence).padStart(MANIFEST_FENCE_WIDTH, \"0\")}-${generationIdentifier(generationId, \"generationId\")}.json`;\n}\n/** Parse only the shape of a manifest object key; artifact validation belongs to the storage adapter. */\nexport function parseSitemapManifestKey(key: unknown): SitemapManifestKey | undefined {\n if (typeof key !== \"string\") return undefined;\n const match = MANIFEST_KEY.exec(key);\n if (!match) return undefined;\n const fence = Number(match[1]);\n const generationId = match[2];\n if (!Number.isSafeInteger(fence) || generationId === undefined) return undefined;\n return { key, fence, generationId };\n}\n\n/** Numeric-fence-descending candidates; callers validate storage contents before adoption. */\nexport function sortSitemapManifestCandidates(keys: readonly string[]): SitemapManifestKey[] {\n return keys\n .map(parseSitemapManifestKey)\n .filter((candidate): candidate is SitemapManifestKey => candidate !== undefined)\n .sort(\n (left, right) =>\n right.fence - left.fence || right.generationId.localeCompare(left.generationId),\n );\n}\n"],"mappings":"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"}
|
package/esm/errors.d.mts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* should not exist, and the mistake belongs at the line that wrote the value
|
|
7
7
|
* rather than at the first request that reads it.
|
|
8
8
|
*/
|
|
9
|
-
declare class InvalidBaseUrlError extends Error {
|
|
9
|
+
export declare class InvalidBaseUrlError extends Error {
|
|
10
10
|
constructor(value: unknown, reason: string);
|
|
11
11
|
}
|
|
12
12
|
/** An entry the sitemap protocol cannot represent. Thrown from `add()`. */
|
|
13
|
-
declare class InvalidSitemapEntryError extends Error {
|
|
13
|
+
export declare class InvalidSitemapEntryError extends Error {
|
|
14
14
|
constructor(reason: string);
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
@@ -19,7 +19,7 @@ declare class InvalidSitemapEntryError extends Error {
|
|
|
19
19
|
* and silently overwrite one another). Thrown from `addSource()`, not at
|
|
20
20
|
* `saveTo()`, so the mistake is caught at the line that registered it.
|
|
21
21
|
*/
|
|
22
|
-
declare class DuplicateSourceKeyError extends Error {
|
|
22
|
+
export declare class DuplicateSourceKeyError extends Error {
|
|
23
23
|
constructor(key: string);
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
@@ -31,9 +31,8 @@ declare class DuplicateSourceKeyError extends Error {
|
|
|
31
31
|
* dangerously) and is never swapped or deleted; this is thrown instead, from
|
|
32
32
|
* `saveTo()` before anything is written.
|
|
33
33
|
*/
|
|
34
|
-
declare class UnownedOutputDirectoryError extends Error {
|
|
34
|
+
export declare class UnownedOutputDirectoryError extends Error {
|
|
35
35
|
constructor(outDir: string);
|
|
36
36
|
}
|
|
37
37
|
//#endregion
|
|
38
|
-
export { DuplicateSourceKeyError, InvalidBaseUrlError, InvalidSitemapEntryError, UnownedOutputDirectoryError };
|
|
39
38
|
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
//#region ../../packages/sitemap/src/generation-manifest.d.ts
|
|
2
2
|
/** Storage-independent schema and key helpers for immutable sitemap generations. */
|
|
3
|
-
declare const GENERATION_MANIFEST_VERSION: 1;
|
|
4
|
-
declare const MANIFEST_FENCE_WIDTH = 16;
|
|
5
|
-
type SitemapGenerationKind = "single" | "index";
|
|
6
|
-
type SitemapGenerationFile = {
|
|
3
|
+
export declare const GENERATION_MANIFEST_VERSION: 1;
|
|
4
|
+
export declare const MANIFEST_FENCE_WIDTH = 16;
|
|
5
|
+
export type SitemapGenerationKind = "single" | "index";
|
|
6
|
+
export type SitemapGenerationFile = {
|
|
7
7
|
readonly path: string;
|
|
8
8
|
readonly bytes: number;
|
|
9
9
|
readonly sha256: string;
|
|
10
10
|
};
|
|
11
|
-
type SitemapGenerationManifest = {
|
|
11
|
+
export type SitemapGenerationManifest = {
|
|
12
12
|
readonly version: typeof GENERATION_MANIFEST_VERSION;
|
|
13
13
|
readonly fence: number;
|
|
14
14
|
readonly generationId: string;
|
|
@@ -19,22 +19,21 @@ type SitemapGenerationManifest = {
|
|
|
19
19
|
readonly entries: number;
|
|
20
20
|
readonly generatedAt: string;
|
|
21
21
|
};
|
|
22
|
-
type SitemapManifestKey = {
|
|
22
|
+
export type SitemapManifestKey = {
|
|
23
23
|
readonly key: string;
|
|
24
24
|
readonly fence: number;
|
|
25
25
|
readonly generationId: string;
|
|
26
26
|
};
|
|
27
|
-
declare class InvalidSitemapGenerationManifestError extends Error {
|
|
27
|
+
export declare class InvalidSitemapGenerationManifestError extends Error {
|
|
28
28
|
constructor(message: string);
|
|
29
29
|
}
|
|
30
30
|
/** Parse untrusted JSON into the v1 manifest contract. It does not touch storage. */
|
|
31
|
-
declare function parseSitemapGenerationManifest(value: unknown): SitemapGenerationManifest;
|
|
31
|
+
export declare function parseSitemapGenerationManifest(value: unknown): SitemapGenerationManifest;
|
|
32
32
|
/** Build the immutable v1 manifest object key for a generation fence. */
|
|
33
|
-
declare function sitemapManifestKey(fence: number, generationId: string): string;
|
|
33
|
+
export declare function sitemapManifestKey(fence: number, generationId: string): string;
|
|
34
34
|
/** Parse only the shape of a manifest object key; artifact validation belongs to the storage adapter. */
|
|
35
|
-
declare function parseSitemapManifestKey(key: unknown): SitemapManifestKey | undefined;
|
|
35
|
+
export declare function parseSitemapManifestKey(key: unknown): SitemapManifestKey | undefined;
|
|
36
36
|
/** Numeric-fence-descending candidates; callers validate storage contents before adoption. */
|
|
37
|
-
declare function sortSitemapManifestCandidates(keys: readonly string[]): SitemapManifestKey[];
|
|
37
|
+
export declare function sortSitemapManifestCandidates(keys: readonly string[]): SitemapManifestKey[];
|
|
38
38
|
//#endregion
|
|
39
|
-
export { GENERATION_MANIFEST_VERSION, InvalidSitemapGenerationManifestError, MANIFEST_FENCE_WIDTH, SitemapGenerationFile, SitemapGenerationKind, SitemapGenerationManifest, SitemapManifestKey, parseSitemapGenerationManifest, parseSitemapManifestKey, sitemapManifestKey, sortSitemapManifestCandidates };
|
|
40
39
|
//# sourceMappingURL=generation-manifest.d.mts.map
|
package/esm/normalize-entry.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { normalizeBaseUrl } from "./url.mjs";
|
|
|
3
3
|
//#region ../../packages/sitemap/src/sitemap-index-options.ts
|
|
4
4
|
/** The sitemaps.org limits. Never clamped to — a silently clamped option is a lie about what was written. */
|
|
5
5
|
const PROTOCOL_MAX_URLS_PER_FILE = 5e4;
|
|
6
|
-
const PROTOCOL_MAX_BYTES_PER_FILE =
|
|
6
|
+
const PROTOCOL_MAX_BYTES_PER_FILE = 52428800;
|
|
7
7
|
/** Validates and folds in defaults, once, at the constructor — the same discipline as `Sitemap`. */
|
|
8
8
|
function normalizeSitemapIndexOptions(options) {
|
|
9
9
|
const baseUrl = normalizeBaseUrl(options?.baseUrl);
|
|
@@ -36,5 +36,5 @@ function normalizeShardPathPrefix(value) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
//#endregion
|
|
39
|
-
export { normalizeSitemapIndexOptions };
|
|
39
|
+
export { PROTOCOL_MAX_BYTES_PER_FILE, PROTOCOL_MAX_URLS_PER_FILE, normalizeSitemapIndexOptions };
|
|
40
40
|
//# sourceMappingURL=sitemap-index-options.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemap-index-options.mjs","names":[],"sources":["../../../../../../../packages/sitemap/src/sitemap-index-options.ts"],"sourcesContent":["import type { SitemapOptions } from \"./types\";\nimport { normalizeBaseUrl } from \"./url\";\nimport type { SitemapIndexOptions } from \"./sitemap-index-types\";\n\n/** The sitemaps.org limits. Never clamped to — a silently clamped option is a lie about what was written. */\nexport const PROTOCOL_MAX_URLS_PER_FILE = 50_000;\nexport const PROTOCOL_MAX_BYTES_PER_FILE = 50 * 1024 * 1024;\n\nexport type ResolvedSitemapIndexOptions = {\n readonly baseUrl: string;\n readonly filePrefix: string;\n readonly indexFileName: string;\n readonly shardPathPrefix: string;\n readonly gzip: boolean;\n readonly maxUrlsPerFile: number;\n readonly maxBytesPerFile: number;\n readonly defaults: Pick<\n SitemapOptions,\n \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\"\n >;\n};\n\n/** Validates and folds in defaults, once, at the constructor — the same discipline as `Sitemap`. */\nexport function normalizeSitemapIndexOptions(\n options: SitemapIndexOptions,\n): ResolvedSitemapIndexOptions {\n const baseUrl = normalizeBaseUrl(options?.baseUrl);\n const shardPathPrefix = normalizeShardPathPrefix(options.shardPathPrefix);\n\n const maxUrlsPerFile = options.maxUrlsPerFile ?? PROTOCOL_MAX_URLS_PER_FILE;\n const maxBytesPerFile = options.maxBytesPerFile ?? PROTOCOL_MAX_BYTES_PER_FILE;\n\n if (\n !Number.isInteger(maxUrlsPerFile) ||\n maxUrlsPerFile < 1 ||\n maxUrlsPerFile > PROTOCOL_MAX_URLS_PER_FILE\n ) {\n throw new RangeError(\n `maxUrlsPerFile must be an integer between 1 and the sitemaps.org ceiling of ` +\n `${PROTOCOL_MAX_URLS_PER_FILE}, got ${JSON.stringify(maxUrlsPerFile)}.`,\n );\n }\n\n if (\n !Number.isFinite(maxBytesPerFile) ||\n maxBytesPerFile < 1 ||\n maxBytesPerFile > PROTOCOL_MAX_BYTES_PER_FILE\n ) {\n throw new RangeError(\n `maxBytesPerFile must be between 1 and the sitemaps.org ceiling of ` +\n `${PROTOCOL_MAX_BYTES_PER_FILE} bytes, got ${JSON.stringify(maxBytesPerFile)}.`,\n );\n }\n\n return {\n baseUrl,\n filePrefix: options.filePrefix ?? \"sitemap\",\n indexFileName: options.indexFileName ?? \"sitemap_index.xml\",\n shardPathPrefix,\n gzip: options.gzip ?? false,\n maxUrlsPerFile,\n maxBytesPerFile,\n defaults: {\n changefreq: options.changefreq,\n priority: options.priority,\n lastmod: options.lastmod,\n onImageLimitExceeded: options.onImageLimitExceeded,\n },\n };\n}\n\nfunction normalizeShardPathPrefix(value: string | undefined): string {\n if (value === undefined || value === \"\" || value === \"/\") return \"\";\n\n const prefix = value.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n if (\n prefix.length === 0 ||\n prefix.split(\"/\").some((segment) => !/^[A-Za-z0-9_-]+$/.test(segment))\n ) {\n throw new TypeError(\"shardPathPrefix must contain only safe URL path segments.\");\n }\n\n return `${prefix}/`;\n}\n"],"mappings":";;;;AAKA,MAAa,6BAA6B;AAC1C,MAAa,8BAA8B
|
|
1
|
+
{"version":3,"file":"sitemap-index-options.mjs","names":[],"sources":["../../../../../../../packages/sitemap/src/sitemap-index-options.ts"],"sourcesContent":["import type { SitemapOptions } from \"./types\";\nimport { normalizeBaseUrl } from \"./url\";\nimport type { SitemapIndexOptions } from \"./sitemap-index-types\";\n\n/** The sitemaps.org limits. Never clamped to — a silently clamped option is a lie about what was written. */\nexport const PROTOCOL_MAX_URLS_PER_FILE = 50_000;\nexport const PROTOCOL_MAX_BYTES_PER_FILE = 50 * 1024 * 1024;\n\nexport type ResolvedSitemapIndexOptions = {\n readonly baseUrl: string;\n readonly filePrefix: string;\n readonly indexFileName: string;\n readonly shardPathPrefix: string;\n readonly gzip: boolean;\n readonly maxUrlsPerFile: number;\n readonly maxBytesPerFile: number;\n readonly defaults: Pick<\n SitemapOptions,\n \"changefreq\" | \"priority\" | \"lastmod\" | \"onImageLimitExceeded\"\n >;\n};\n\n/** Validates and folds in defaults, once, at the constructor — the same discipline as `Sitemap`. */\nexport function normalizeSitemapIndexOptions(\n options: SitemapIndexOptions,\n): ResolvedSitemapIndexOptions {\n const baseUrl = normalizeBaseUrl(options?.baseUrl);\n const shardPathPrefix = normalizeShardPathPrefix(options.shardPathPrefix);\n\n const maxUrlsPerFile = options.maxUrlsPerFile ?? PROTOCOL_MAX_URLS_PER_FILE;\n const maxBytesPerFile = options.maxBytesPerFile ?? PROTOCOL_MAX_BYTES_PER_FILE;\n\n if (\n !Number.isInteger(maxUrlsPerFile) ||\n maxUrlsPerFile < 1 ||\n maxUrlsPerFile > PROTOCOL_MAX_URLS_PER_FILE\n ) {\n throw new RangeError(\n `maxUrlsPerFile must be an integer between 1 and the sitemaps.org ceiling of ` +\n `${PROTOCOL_MAX_URLS_PER_FILE}, got ${JSON.stringify(maxUrlsPerFile)}.`,\n );\n }\n\n if (\n !Number.isFinite(maxBytesPerFile) ||\n maxBytesPerFile < 1 ||\n maxBytesPerFile > PROTOCOL_MAX_BYTES_PER_FILE\n ) {\n throw new RangeError(\n `maxBytesPerFile must be between 1 and the sitemaps.org ceiling of ` +\n `${PROTOCOL_MAX_BYTES_PER_FILE} bytes, got ${JSON.stringify(maxBytesPerFile)}.`,\n );\n }\n\n return {\n baseUrl,\n filePrefix: options.filePrefix ?? \"sitemap\",\n indexFileName: options.indexFileName ?? \"sitemap_index.xml\",\n shardPathPrefix,\n gzip: options.gzip ?? false,\n maxUrlsPerFile,\n maxBytesPerFile,\n defaults: {\n changefreq: options.changefreq,\n priority: options.priority,\n lastmod: options.lastmod,\n onImageLimitExceeded: options.onImageLimitExceeded,\n },\n };\n}\n\nfunction normalizeShardPathPrefix(value: string | undefined): string {\n if (value === undefined || value === \"\" || value === \"/\") return \"\";\n\n const prefix = value.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n if (\n prefix.length === 0 ||\n prefix.split(\"/\").some((segment) => !/^[A-Za-z0-9_-]+$/.test(segment))\n ) {\n throw new TypeError(\"shardPathPrefix must contain only safe URL path segments.\");\n }\n\n return `${prefix}/`;\n}\n"],"mappings":";;;;AAKA,MAAa,6BAA6B;AAC1C,MAAa,8BAA8B;;AAiB3C,SAAgB,6BACd,SAC6B;CAC7B,MAAM,UAAU,iBAAiB,SAAS,OAAO;CACjD,MAAM,kBAAkB,yBAAyB,QAAQ,eAAe;CAExE,MAAM,iBAAiB,QAAQ;CAC/B,MAAM,kBAAkB,QAAQ;CAEhC,IACE,CAAC,OAAO,UAAU,cAAc,KAChC,iBAAiB,KACjB,sBAEA,MAAM,IAAI,WACR,+EACK,2BAA2B,QAAQ,KAAK,UAAU,cAAc,EAAE,EACzE;CAGF,IACE,CAAC,OAAO,SAAS,eAAe,KAChC,kBAAkB,KAClB,4BAEA,MAAM,IAAI,WACR,qEACK,4BAA4B,cAAc,KAAK,UAAU,eAAe,EAAE,EACjF;CAGF,OAAO;EACL;EACA,YAAY,QAAQ,cAAc;EAClC,eAAe,QAAQ,iBAAiB;EACxC;EACA,MAAM,QAAQ,QAAQ;EACtB;EACA;EACA,UAAU;GACR,YAAY,QAAQ;GACpB,UAAU,QAAQ;GAClB,SAAS,QAAQ;GACjB,sBAAsB,QAAQ;EAChC;CACF;AACF;AAEA,SAAS,yBAAyB,OAAmC;CACnE,IAAI,UAAU,UAAa,UAAU,MAAM,UAAU,KAAK,OAAO;CAEjE,MAAM,SAAS,MAAM,QAAQ,OAAO,EAAE,CAAC,CAAC,QAAQ,OAAO,EAAE;CACzD,IACE,OAAO,WAAW,KAClB,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM,YAAY,CAAC,mBAAmB,KAAK,OAAO,CAAC,GAErE,MAAM,IAAI,UAAU,2DAA2D;CAGjF,OAAO,GAAG,OAAO;AACnB"}
|
|
@@ -1,41 +1,48 @@
|
|
|
1
1
|
import { ChangeFreq, DuplicateReport, RouteSummary, SitemapEntry, SitemapImageLimitExceeded } from "./types.mjs";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/sitemap/src/sitemap-index-types.d.ts
|
|
4
3
|
/** What a source factory produces: one walk over a group's entries. */
|
|
5
|
-
type SitemapSource = Iterable<SitemapEntry> | AsyncIterable<SitemapEntry>;
|
|
4
|
+
export type SitemapSource = Iterable<SitemapEntry> | AsyncIterable<SitemapEntry>;
|
|
6
5
|
/**
|
|
7
6
|
* A FACTORY, not a bare iterable. An async iterable can only be walked once,
|
|
8
7
|
* so it cannot survive a retry, a second `saveTo()`, or a failed run that has
|
|
9
8
|
* to rebuild the whole set — the factory can simply be called again.
|
|
10
9
|
*/
|
|
11
|
-
type SitemapSourceFactory = () => SitemapSource | Promise<SitemapSource>;
|
|
12
|
-
type SitemapIndexOptions = {
|
|
13
|
-
/** Absolute origin, validated exactly as `Sitemap` validates it. */
|
|
14
|
-
readonly
|
|
15
|
-
|
|
16
|
-
readonly
|
|
17
|
-
|
|
18
|
-
readonly
|
|
10
|
+
export type SitemapSourceFactory = () => SitemapSource | Promise<SitemapSource>;
|
|
11
|
+
export type SitemapIndexOptions = {
|
|
12
|
+
/** Absolute origin, validated exactly as `Sitemap` validates it. */
|
|
13
|
+
readonly baseUrl: string;
|
|
14
|
+
/** Shard file name prefix. Default `sitemap`, giving `sitemap-0001.xml`. */
|
|
15
|
+
readonly filePrefix?: string;
|
|
16
|
+
/** Index file name. Default `sitemap_index.xml`. */
|
|
17
|
+
readonly indexFileName?: string;
|
|
18
|
+
/** URL directory for shard links, independent of their on-disk directory. */
|
|
19
|
+
readonly shardPathPrefix?: string;
|
|
20
|
+
/** Write `.xml.gz` beside each shard and point the index at it. Default false. */
|
|
21
|
+
readonly gzip?: boolean;
|
|
22
|
+
/** Hard ceiling per shard. Default 50_000; never accepted above the protocol ceiling. */
|
|
23
|
+
readonly maxUrlsPerFile?: number;
|
|
24
|
+
/** Hard ceiling per shard in bytes, uncompressed. Default 50 * 1024 * 1024. */
|
|
19
25
|
readonly maxBytesPerFile?: number;
|
|
20
26
|
readonly changefreq?: ChangeFreq;
|
|
21
27
|
readonly priority?: number;
|
|
22
|
-
readonly lastmod?: string | Date;
|
|
28
|
+
readonly lastmod?: string | Date;
|
|
29
|
+
/** Called once per route when an entry exceeds Google's 1,000-image limit. */
|
|
23
30
|
readonly onImageLimitExceeded?: (event: SitemapImageLimitExceeded) => void;
|
|
24
31
|
};
|
|
25
|
-
type SitemapSetResult = {
|
|
32
|
+
export type SitemapSetResult = {
|
|
26
33
|
readonly indexPath: string;
|
|
27
34
|
readonly files: readonly SitemapFileResult[];
|
|
28
35
|
readonly totalUrls: number;
|
|
29
36
|
readonly duplicates: readonly DuplicateReport[];
|
|
30
37
|
readonly routes: readonly RouteSummary[];
|
|
31
38
|
};
|
|
32
|
-
type SitemapFileResult = {
|
|
33
|
-
readonly path: string;
|
|
39
|
+
export type SitemapFileResult = {
|
|
40
|
+
readonly path: string;
|
|
41
|
+
/** The source key this shard came from, when the source was named. */
|
|
34
42
|
readonly key?: string;
|
|
35
43
|
readonly urls: number;
|
|
36
44
|
readonly bytes: number;
|
|
37
45
|
readonly gzipped: boolean;
|
|
38
46
|
};
|
|
39
47
|
//#endregion
|
|
40
|
-
export { SitemapFileResult, SitemapIndexOptions, SitemapSetResult, SitemapSource, SitemapSourceFactory };
|
|
41
48
|
//# sourceMappingURL=sitemap-index-types.d.mts.map
|
package/esm/sitemap-index.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SitemapIndexOptions, SitemapSetResult, SitemapSourceFactory } from "./sitemap-index-types.mjs";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/sitemap/src/sitemap-index.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* The streaming path for 300–500K URLs: shards, one flat master index, gzip,
|
|
@@ -8,7 +7,7 @@ import { SitemapIndexOptions, SitemapSetResult, SitemapSourceFactory } from "./s
|
|
|
8
7
|
* There is no `entries()`, `toXML()` or `size`; anyone who can afford those
|
|
9
8
|
* is in `Sitemap` and should be there instead.
|
|
10
9
|
*/
|
|
11
|
-
declare class SitemapIndex {
|
|
10
|
+
export declare class SitemapIndex {
|
|
12
11
|
private readonly options;
|
|
13
12
|
/** Every unnamed `addSource(factory)` call merges into this one group, sharing one shard counter. */
|
|
14
13
|
private readonly unnamedFactories;
|
|
@@ -29,5 +28,4 @@ declare class SitemapIndex {
|
|
|
29
28
|
private reportImageLimit;
|
|
30
29
|
}
|
|
31
30
|
//#endregion
|
|
32
|
-
export { SitemapIndex };
|
|
33
31
|
//# sourceMappingURL=sitemap-index.d.mts.map
|
package/esm/sitemap.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DuplicateReport, ResolvedSitemapEntry, RouteSummary, SitemapEntry, SitemapOptions } from "./types.mjs";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/sitemap/src/sitemap.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* A bounded sitemap builder: it RETAINS every entry, which is what makes
|
|
@@ -11,7 +10,7 @@ import { DuplicateReport, ResolvedSitemapEntry, RouteSummary, SitemapEntry, Site
|
|
|
11
10
|
* on purpose: a class that promised not to retain entries and still offered
|
|
12
11
|
* `entries()` would be lying about one of them.
|
|
13
12
|
*/
|
|
14
|
-
declare class Sitemap {
|
|
13
|
+
export declare class Sitemap {
|
|
15
14
|
private readonly baseUrl;
|
|
16
15
|
private readonly defaults;
|
|
17
16
|
private readonly warnedImageRoutes;
|
|
@@ -62,5 +61,4 @@ declare class Sitemap {
|
|
|
62
61
|
publishTo(outDir: string, fileName?: string): Promise<string>;
|
|
63
62
|
}
|
|
64
63
|
//#endregion
|
|
65
|
-
export { Sitemap };
|
|
66
64
|
//# sourceMappingURL=sitemap.d.mts.map
|
package/esm/types.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region ../../packages/sitemap/src/types.d.ts
|
|
2
2
|
/** The `<changefreq>` values the sitemap protocol defines. */
|
|
3
|
-
type ChangeFreq = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
3
|
+
export type ChangeFreq = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
4
4
|
/**
|
|
5
5
|
* One language version of a page, serialised as
|
|
6
6
|
* `<xhtml:link rel="alternate" hreflang="…" href="…"/>`.
|
|
@@ -10,15 +10,17 @@ type ChangeFreq = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly
|
|
|
10
10
|
* does not understand anybody's locale system, and it never derives a
|
|
11
11
|
* locale-prefixed path for you.
|
|
12
12
|
*/
|
|
13
|
-
type SitemapAlternate = {
|
|
14
|
-
readonly hreflang: string;
|
|
13
|
+
export type SitemapAlternate = {
|
|
14
|
+
readonly hreflang: string;
|
|
15
|
+
/** A path resolved against `baseUrl`, or an absolute URL used as given. */
|
|
15
16
|
readonly path: string;
|
|
16
17
|
};
|
|
17
18
|
/** One image associated with a page URL in Google's image sitemap extension. */
|
|
18
|
-
type SitemapImage = {
|
|
19
|
-
/** An absolute HTTP(S) URL. It may point at a verified external CDN. */
|
|
19
|
+
export type SitemapImage = {
|
|
20
|
+
/** An absolute HTTP(S) URL. It may point at a verified external CDN. */
|
|
21
|
+
readonly loc: string;
|
|
20
22
|
};
|
|
21
|
-
type SitemapImageLimitExceeded = {
|
|
23
|
+
export type SitemapImageLimitExceeded = {
|
|
22
24
|
readonly route?: string;
|
|
23
25
|
readonly dropped: number;
|
|
24
26
|
};
|
|
@@ -26,14 +28,19 @@ type SitemapImageLimitExceeded = {
|
|
|
26
28
|
* One `<url>` block. Only `path` is required; `name` and `route` are carried
|
|
27
29
|
* for the diagnostics in `routes()` and `duplicates()` and never serialised.
|
|
28
30
|
*/
|
|
29
|
-
type SitemapEntry = {
|
|
30
|
-
/** The CONCRETE path: `/posts/123`. A pattern is not a URL. */
|
|
31
|
-
readonly
|
|
31
|
+
export type SitemapEntry = {
|
|
32
|
+
/** The CONCRETE path: `/posts/123`. A pattern is not a URL. */
|
|
33
|
+
readonly path: string;
|
|
34
|
+
/** Optional label for the route this came from: `post-details`. */
|
|
35
|
+
readonly name?: string;
|
|
36
|
+
/** Optional pattern this came from: `/posts/:id`. */
|
|
32
37
|
readonly route?: string;
|
|
33
38
|
readonly lastmod?: string | Date;
|
|
34
39
|
readonly changefreq?: ChangeFreq;
|
|
35
|
-
readonly priority?: number;
|
|
36
|
-
|
|
40
|
+
readonly priority?: number;
|
|
41
|
+
/** Language versions of THIS page, conventionally including itself. */
|
|
42
|
+
readonly alternates?: readonly SitemapAlternate[];
|
|
43
|
+
/** Images discoverable from this page. At most 1,000 are emitted per URL. */
|
|
37
44
|
readonly images?: readonly SitemapImage[];
|
|
38
45
|
};
|
|
39
46
|
/**
|
|
@@ -42,21 +49,24 @@ type SitemapEntry = {
|
|
|
42
49
|
* serialised string — a `Date` is resolved once, at `add()`, so `toXML()`
|
|
43
50
|
* stays pure and repeatable.
|
|
44
51
|
*/
|
|
45
|
-
type ResolvedSitemapEntry = Omit<SitemapEntry, "lastmod"> & {
|
|
52
|
+
export type ResolvedSitemapEntry = Omit<SitemapEntry, "lastmod"> & {
|
|
46
53
|
readonly lastmod?: string;
|
|
47
54
|
};
|
|
48
|
-
type SitemapOptions = {
|
|
49
|
-
/** Absolute origin: `https://example.com` or `https://example.com/base`. */
|
|
55
|
+
export type SitemapOptions = {
|
|
56
|
+
/** Absolute origin: `https://example.com` or `https://example.com/base`. */
|
|
57
|
+
readonly baseUrl: string;
|
|
58
|
+
/** Applied to every entry that does not set its own. */
|
|
50
59
|
readonly changefreq?: ChangeFreq;
|
|
51
60
|
readonly priority?: number;
|
|
52
|
-
readonly lastmod?: string | Date;
|
|
61
|
+
readonly lastmod?: string | Date;
|
|
62
|
+
/** Called once per route when an entry exceeds Google's 1,000-image limit. */
|
|
53
63
|
readonly onImageLimitExceeded?: (event: SitemapImageLimitExceeded) => void;
|
|
54
64
|
};
|
|
55
65
|
/**
|
|
56
66
|
* How many URLs a route contributed. A `count: 0` row is the interesting one:
|
|
57
67
|
* the route was declared and produced nothing, so a whole section is missing.
|
|
58
68
|
*/
|
|
59
|
-
type RouteSummary = {
|
|
69
|
+
export type RouteSummary = {
|
|
60
70
|
readonly route: string;
|
|
61
71
|
readonly count: number;
|
|
62
72
|
};
|
|
@@ -64,11 +74,12 @@ type RouteSummary = {
|
|
|
64
74
|
* A path that was added more than once. The later add wins silently, so this
|
|
65
75
|
* is the only way a caller can see it happened.
|
|
66
76
|
*/
|
|
67
|
-
type DuplicateReport = {
|
|
68
|
-
readonly path: string;
|
|
69
|
-
|
|
77
|
+
export type DuplicateReport = {
|
|
78
|
+
readonly path: string;
|
|
79
|
+
/** Always >= 2. */
|
|
80
|
+
readonly count: number;
|
|
81
|
+
/** The `route` of each add, in order, so colliding sources are nameable. */
|
|
70
82
|
readonly routes: readonly (string | undefined)[];
|
|
71
83
|
};
|
|
72
84
|
//#endregion
|
|
73
|
-
export { ChangeFreq, DuplicateReport, ResolvedSitemapEntry, RouteSummary, SitemapAlternate, SitemapEntry, SitemapImage, SitemapImageLimitExceeded, SitemapOptions };
|
|
74
85
|
//# sourceMappingURL=types.d.mts.map
|
package/esm/url.d.mts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* URL, with exactly one slash at the seam regardless of whether either side
|
|
5
5
|
* already carries one.
|
|
6
6
|
*/
|
|
7
|
-
declare function joinOrigin(origin: string, routePath: string): string;
|
|
7
|
+
export declare function joinOrigin(origin: string, routePath: string): string;
|
|
8
8
|
//#endregion
|
|
9
|
-
export { joinOrigin };
|
|
10
9
|
//# sourceMappingURL=url.d.mts.map
|
package/esm/url.mjs
CHANGED
package/esm/xml.d.mts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { ResolvedSitemapEntry } from "./types.mjs";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/sitemap/src/xml.d.ts
|
|
4
3
|
/** Escapes the five XML-significant characters. A URL's query string routinely contains `&`. */
|
|
5
|
-
declare function escapeXml(value: string): string;
|
|
4
|
+
export declare function escapeXml(value: string): string;
|
|
6
5
|
/**
|
|
7
6
|
* Renders one `<url>` block. Exported so the streaming writer can measure the
|
|
8
7
|
* exact bytes it is about to append before deciding whether the byte ceiling
|
|
9
8
|
* forces a new shard — a separate approximation could disagree with what is
|
|
10
9
|
* actually written.
|
|
11
10
|
*/
|
|
12
|
-
declare function renderUrlBlock(entry: ResolvedSitemapEntry, baseUrl: string): string;
|
|
11
|
+
export declare function renderUrlBlock(entry: ResolvedSitemapEntry, baseUrl: string): string;
|
|
13
12
|
/**
|
|
14
13
|
* Serialises entries into a `urlset` sitemap document — the sitemaps.org
|
|
15
14
|
* namespace, `<url>` per entry, element order `loc` / `lastmod` / `changefreq`
|
|
@@ -19,7 +18,6 @@ declare function renderUrlBlock(entry: ResolvedSitemapEntry, baseUrl: string): s
|
|
|
19
18
|
* The xhtml namespace is declared only when some entry actually carries an
|
|
20
19
|
* alternate — an unused namespace on every single-language sitemap is noise.
|
|
21
20
|
*/
|
|
22
|
-
declare function buildSitemapXml(entries: readonly ResolvedSitemapEntry[], baseUrl: string): string;
|
|
21
|
+
export declare function buildSitemapXml(entries: readonly ResolvedSitemapEntry[], baseUrl: string): string;
|
|
23
22
|
//#endregion
|
|
24
|
-
export { buildSitemapXml, escapeXml, renderUrlBlock };
|
|
25
23
|
//# sourceMappingURL=xml.d.mts.map
|
package/package.json
CHANGED