@warlock.js/sitemap 5.19.1 → 5.21.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/llms-full.txt CHANGED
@@ -6,179 +6,208 @@
6
6
 
7
7
  ## sitemap-overview `@warlock.js/sitemap/sitemap-overview/SKILL.md`
8
8
 
9
- ---
10
- name: sitemap-overview
11
- description: "@warlock.js/sitemap — overview in @warlock.js/sitemap; use when you need to sitemap overview."
12
- ---
13
-
14
- # `@warlock.js/sitemap` — overview
15
-
16
- A sitemap builder that knows nothing about any framework. Entries in, a valid
17
- sitemaps.org document out. **Zero runtime dependencies.** Generation only; no
18
- remote sitemap parser.
19
-
20
- ## It does not import Warlock
21
-
22
- There is no connector, no config module, no lifecycle and no page registry in
23
- this package. It owns the sitemap PROTOCOL and every byte it emits, and
24
- nothing else. `@warlock.js/web` is a CALLER of it, exactly like an Express app
25
- or a cron script is — which is what makes the same three lines work everywhere.
26
-
27
- If you are looking for page discovery, `sitemap: false` exclusion, locale
28
- expansion, the `/sitemap.xml` route or `robots.txt`, those belong to
29
- `@warlock.js/web`, not here.
30
-
31
- ## The whole common case
32
-
33
- ```ts
34
- import { Sitemap } from "@warlock.js/sitemap";
35
-
36
- const sitemap = new Sitemap({
37
- baseUrl: "https://example.com",
38
- changefreq: "weekly",
39
- priority: 0.5,
40
- });
41
-
42
- sitemap.add({ path: "/" });
43
-
44
- declare const posts: { slug: string; updatedAt: Date }[];
45
-
46
- for (const post of posts) {
47
- sitemap.add({
48
- name: "post-details",
49
- route: "/posts/:id",
50
- path: `/posts/${post.slug}`,
51
- lastmod: post.updatedAt,
52
- priority: 0.8,
53
- });
54
- }
55
-
56
- const xml = sitemap.toXML();
57
-
58
- await sitemap.saveTo("public/sitemap.xml");
59
- ```
60
-
61
- ## `baseUrl` is validated in the CONSTRUCTOR
62
-
63
- A `Sitemap` that cannot produce a valid URL should not exist. Anything that is
64
- not an absolute `http(s)` URL throws `InvalidBaseUrlError` at construction, so
65
- a typo surfaces at the line that wrote it rather than at the first request.
66
- `new URL()` accepts `mailto:` and `file:` happily, so the protocol is checked
67
- explicitly.
68
-
69
- This replaces the 5.15.0 config key, its `PUBLIC_APP_URL` env fallback and its
70
- boot refusal entirely: the origin is an argument now, so it cannot be missing.
71
-
72
- ## The entry
73
-
74
- Only `path` is required, and it must be a **concrete path** — `/posts/123`,
75
- never `/posts/:id`. A pattern is not a URL.
76
-
77
- | Field | Meaning |
78
- | --- | --- |
79
- | `path` | Required. Resolved against `baseUrl`; an absolute URL is used as given. |
80
- | `name` | Optional route label, for your own diagnostics. Never serialised. |
81
- | `route` | Optional pattern this URL came from. Feeds `routes()` and `duplicates()`. |
82
- | `lastmod` | `Date` → W3C datetime. A string is passed through UNTOUCHED, never re-parsed. |
83
- | `changefreq` | One of the seven protocol values; anything else throws. |
84
- | `priority` | `0.0`–`1.0`; anything else throws. |
9
+ ---
10
+ name: sitemap-overview
11
+ description: 'Front-door orientation for `@warlock.js/sitemap`: framework-blind `Sitemap` and streaming `SitemapIndex` builders, URL metadata, hreflang, Google image entries, sharding, diagnostics, and atomic publication. Use when adding or debugging sitemap output; Warlock page discovery and `/sitemap.xml` routing belong to `@warlock.js/web`.'
12
+ ---
13
+
14
+ # `@warlock.js/sitemap` — overview
15
+
16
+ A sitemap builder that knows nothing about any framework. Entries in, a valid
17
+ sitemaps.org document out. **Zero runtime dependencies.** Generation only; no
18
+ remote sitemap parser.
19
+
20
+ ## It does not import Warlock
21
+
22
+ There is no connector, no config module, no lifecycle and no page registry in
23
+ this package. It owns the sitemap PROTOCOL and every byte it emits, and
24
+ nothing else. `@warlock.js/web` is a CALLER of it, exactly like an Express app
25
+ or a cron script is — which is what makes the same three lines work everywhere.
26
+
27
+ If you are looking for page discovery, `sitemap: false` exclusion, locale
28
+ expansion, the `/sitemap.xml` route or `robots.txt`, those belong to
29
+ `@warlock.js/web`, not here.
30
+
31
+ ## The whole common case
32
+
33
+ ```ts
34
+ import { Sitemap } from "@warlock.js/sitemap";
35
+
36
+ const sitemap = new Sitemap({
37
+ baseUrl: "https://example.com",
38
+ changefreq: "weekly",
39
+ priority: 0.5,
40
+ });
41
+
42
+ sitemap.add({ path: "/" });
43
+
44
+ declare const posts: { slug: string; updatedAt: Date }[];
45
+
46
+ for (const post of posts) {
47
+ sitemap.add({
48
+ name: "post-details",
49
+ route: "/posts/:id",
50
+ path: `/posts/${post.slug}`,
51
+ lastmod: post.updatedAt,
52
+ priority: 0.8,
53
+ });
54
+ }
55
+
56
+ const xml = sitemap.toXML();
57
+
58
+ await sitemap.saveTo("public/sitemap.xml");
59
+ ```
60
+
61
+ ## `baseUrl` is validated in the CONSTRUCTOR
62
+
63
+ A `Sitemap` that cannot produce a valid URL should not exist. Anything that is
64
+ not an absolute `http(s)` URL throws `InvalidBaseUrlError` at construction, so
65
+ a typo surfaces at the line that wrote it rather than at the first request.
66
+ `new URL()` accepts `mailto:` and `file:` happily, so the protocol is checked
67
+ explicitly.
68
+
69
+ This replaces the 5.15.0 config key, its `PUBLIC_APP_URL` env fallback and its
70
+ boot refusal entirely: the origin is an argument now, so it cannot be missing.
71
+
72
+ ## The entry
73
+
74
+ Only `path` is required, and it must be a **concrete path** — `/posts/123`,
75
+ never `/posts/:id`. A pattern is not a URL.
76
+
77
+ | Field | Meaning |
78
+ | --- | --- |
79
+ | `path` | Required. Resolved against `baseUrl`; an absolute URL is used as given. |
80
+ | `name` | Optional route label, for your own diagnostics. Never serialised. |
81
+ | `route` | Optional pattern this URL came from. Feeds `routes()` and `duplicates()`. |
82
+ | `lastmod` | `Date` → W3C datetime. A string is passed through UNTOUCHED, never re-parsed. |
83
+ | `changefreq` | One of the seven protocol values; anything else throws. |
84
+ | `priority` | `0.0`–`1.0`; anything else throws. |
85
85
  | `alternates` | `{ hreflang, path }[]` — language versions of this page. |
86
-
87
- Invalid entries throw `InvalidSitemapEntryError` from `add()`. Paths are
88
- normalised to carry a leading slash, so `a` and `/a` are ONE entry.
89
-
90
- ## Duplicates: silent, but not hidden
91
-
92
- Entries are stored keyed by `path`. Adding the same path twice keeps the later
93
- one, silently — a duplicate `<loc>` makes the document invalid, and two loops
94
- legitimately covering an overlapping set is the normal cause.
95
-
96
- `duplicates()` reports every collision with its count and the `route` of each
97
- contributing add, so a caller who cares can fail their own build:
98
-
99
- ```ts
100
- sitemap.duplicates();
101
- // [ { path: "/posts/1", count: 2, routes: ["/posts/:id", "/:slug"] } ]
102
- ```
103
-
104
- ## `routes()` and why `declareRoute()` exists
86
+ | `images` | `{ loc }[]` — absolute HTTP(S) image URLs, including verified external CDNs. |
87
+
88
+ Invalid entries throw `InvalidSitemapEntryError` from `add()`. Paths are
89
+ normalised to carry a leading slash, so `a` and `/a` are ONE entry.
90
+
91
+ ## Duplicates: silent, but not hidden
92
+
93
+ Entries are stored keyed by `path`. Adding the same path twice keeps the later
94
+ one, silently — a duplicate `<loc>` makes the document invalid, and two loops
95
+ legitimately covering an overlapping set is the normal cause.
96
+
97
+ `duplicates()` reports every collision with its count and the `route` of each
98
+ contributing add, so a caller who cares can fail their own build:
99
+
100
+ ```ts
101
+ sitemap.duplicates();
102
+ // [ { path: "/posts/1", count: 2, routes: ["/posts/:id", "/:slug"] } ]
103
+ ```
104
+
105
+ ## `routes()` and why `declareRoute()` exists
106
+
107
+ ```ts
108
+ sitemap.declareRoute("/products/:slug");
109
+
110
+ sitemap.routes();
111
+ // [ { route: "/posts/:id", count: 400 }, { route: "/products/:slug", count: 0 } ]
112
+ ```
113
+
114
+ **The zero is the interesting row.** It means a route you expected to
115
+ contribute URLs contributed none, and a whole section of the site is missing
116
+ from a document that otherwise looks perfect.
117
+
118
+ Without `declareRoute()` a route only becomes known by appearing on an entry,
119
+ so every route has at least one URL, `count: 0` can never occur and the
120
+ diagnostic is decorative. Declaring the pattern is what turns a silent loss
121
+ into a visible one.
122
+
123
+ **This package reports; it never prints.** Whether a zero is a warning or a
124
+ build failure is the caller's decision.
125
+
126
+ ## Alternates
127
+
128
+ `alternates` emits `<xhtml:link rel="alternate" hreflang="…">` inside each
129
+ `<url>`. Two rules:
130
+
131
+ 1. **Every language version is also its own `<url>`, carrying the complete
132
+ alternate set including itself.** Listing alternates on only one of them is
133
+ the usual way this ships broken.
134
+ 2. **The package does not know what a locale is.** `hreflang` is any string —
135
+ `en`, `en-GB`, `x-default` — and it never derives `/{locale}/…` for you.
136
+ Each path is supplied explicitly, which is the only thing that works when
137
+ slugs diverge between languages.
138
+
139
+ The `xhtml` namespace is declared only when something uses it. `SitemapIndex`
140
+ applies the same rule PER SHARD — a shard declares `xmlns:xhtml` only when its
141
+ own entries carry alternates — and an alternate's `<xhtml:link>` bytes count
142
+ toward that shard's byte ceiling exactly like the rest of its `<url>` block.
143
+
144
+ ## Images
145
+
146
+ Use `images` when a page has image assets to expose:
105
147
 
106
148
  ```ts
107
- sitemap.declareRoute("/products/:slug");
108
-
109
- sitemap.routes();
110
- // [ { route: "/posts/:id", count: 400 }, { route: "/products/:slug", count: 0 } ]
149
+ sitemap.add({
150
+ path: "/products/widget",
151
+ images: [{ loc: "https://cdn.example.com/widget.jpg" }],
152
+ });
111
153
  ```
112
154
 
113
- **The zero is the interesting row.** It means a route you expected to
114
- contribute URLs contributed none, and a whole section of the site is missing
115
- from a document that otherwise looks perfect.
116
-
117
- Without `declareRoute()` a route only becomes known by appearing on an entry,
118
- so every route has at least one URL, `count: 0` can never occur and the
119
- diagnostic is decorative. Declaring the pattern is what turns a silent loss
120
- into a visible one.
121
-
122
- **This package reports; it never prints.** Whether a zero is a warning or a
123
- build failure is the caller's decision.
124
-
125
- ## Alternates
155
+ Each image `loc` must be an absolute HTTP(S) URL. The builder emits Google's
156
+ `<image:image><image:loc>…</image:loc></image:image>` extension and declares
157
+ `xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"` only in
158
+ documents that need it. An external CDN URL is valid when that host is verified
159
+ for the site.
126
160
 
127
- `alternates` emits `<xhtml:link rel="alternate" hreflang="…">` inside each
128
- `<url>`. Two rules:
129
-
130
- 1. **Every language version is also its own `<url>`, carrying the complete
131
- alternate set including itself.** Listing alternates on only one of them is
132
- the usual way this ships broken.
133
- 2. **The package does not know what a locale is.** `hreflang` is any string —
134
- `en`, `en-GB`, `x-default` — and it never derives `/{locale}/…` for you.
135
- Each path is supplied explicitly, which is the only thing that works when
136
- slugs diverge between languages.
137
-
138
- The `xhtml` namespace is declared only when something uses it. `SitemapIndex`
139
- applies the same rule PER SHARD — a shard declares `xmlns:xhtml` only when its
140
- own entries carry alternates — and an alternate's `<xhtml:link>` bytes count
141
- toward that shard's byte ceiling exactly like the rest of its `<url>` block.
161
+ Google permits at most 1,000 images for one `<url>`. Extra images are dropped.
162
+ Pass `onImageLimitExceeded({ route, dropped })` in `SitemapOptions` to receive
163
+ the one diagnostic for that route; without it, the builder issues one
164
+ `console.warn` per route. Image XML and its namespace count toward the shard byte
165
+ ceiling.
142
166
 
143
167
  ## `toXML()` is pure, repeatable and synchronous
144
-
145
- Calling it twice returns the same string; it mutates nothing and drains
146
- nothing. It has no I/O and stays synchronous.
147
-
148
- ## The ceiling
149
-
150
- The protocol caps one file at **50,000 URLs or 50MB uncompressed**. `Sitemap`
151
- retains every entry — that is what makes `entries()` and a repeatable
152
- `toXML()` possible — so it is right up to that ceiling and wrong above it. A
153
- site past it needs the streaming writer, which retains nothing and emits shards
154
- plus an index.
155
-
168
+
169
+ Calling it twice returns the same string; it mutates nothing and drains
170
+ nothing. It has no I/O and stays synchronous.
171
+
172
+ ## The ceiling
173
+
174
+ The protocol caps one file at **50,000 URLs or 50MB uncompressed**. `Sitemap`
175
+ retains every entry — that is what makes `entries()` and a repeatable
176
+ `toXML()` possible — so it is right up to that ceiling and wrong above it. A
177
+ site past it needs the streaming writer, which retains nothing and emits shards
178
+ plus an index.
179
+
156
180
  `maxBytesPerFile` is enforced at shard boundaries, not within an entry: a
157
181
  single entry too large for the ceiling is written alone in its own shard
158
182
  rather than split — an entry can't be split.
159
183
 
160
- ## `SitemapIndex.saveTo(outDir)` owns the whole directory
161
-
162
- It publishes atomically by swapping `outDir` for a freshly written temp
163
- directory in one `rename` — which means `outDir` must be a directory
164
- dedicated to this sitemap set, never an app's `public/` or anything else
165
- something else writes to. Every publish marks `outDir` with
166
- `.sitemap-set.json`; a later call only swaps a directory that is absent,
167
- empty, or already carries that marker. A non-empty, unmarked `outDir` gets
168
- `UnownedOutputDirectoryError` instead, and is left completely untouched.
169
-
170
- ## Errors
171
-
172
- | Error | When |
173
- | --- | --- |
174
- | `InvalidBaseUrlError` | `baseUrl` missing, relative, or not `http(s)`. From the constructor. |
175
- | `InvalidSitemapEntryError` | No path, priority outside `0.0`–`1.0`, unknown `changefreq`, invalid `Date`, alternate with no `hreflang`. From `add()`. |
176
- | `UnownedOutputDirectoryError` | `SitemapIndex.saveTo(outDir)`: `outDir` is non-empty with no `.sitemap-set.json` marker. From `saveTo()`, before anything is written. |
177
-
178
- ## Also exported
179
-
184
+ For `SitemapIndex`, `shardPathPrefix` changes the URL directory used for shard
185
+ links in the index without changing where `saveTo(outDir)` writes those files.
186
+ Use it when a proxy serves generated shards from a different public path.
187
+
188
+ ## `SitemapIndex.saveTo(outDir)` owns the whole directory
189
+
190
+ It publishes atomically by swapping `outDir` for a freshly written temp
191
+ directory in one `rename` — which means `outDir` must be a directory
192
+ dedicated to this sitemap set, never an app's `public/` or anything else
193
+ something else writes to. Every publish marks `outDir` with
194
+ `.sitemap-set.json`; a later call only swaps a directory that is absent,
195
+ empty, or already carries that marker. A non-empty, unmarked `outDir` gets
196
+ `UnownedOutputDirectoryError` instead, and is left completely untouched.
197
+
198
+ ## Errors
199
+
200
+ | Error | When |
201
+ | --- | --- |
202
+ | `InvalidBaseUrlError` | `baseUrl` missing, relative, or not `http(s)`. From the constructor. |
203
+ | `InvalidSitemapEntryError` | No path, priority outside `0.0`–`1.0`, unknown `changefreq`, invalid `Date`, alternate with no `hreflang`. From `add()`. |
204
+ | `UnownedOutputDirectoryError` | `SitemapIndex.saveTo(outDir)`: `outDir` is non-empty with no `.sitemap-set.json` marker. From `saveTo()`, before anything is written. |
205
+
206
+ ## Also exported
207
+
180
208
  `buildSitemapXml(entries, baseUrl)`, `escapeXml(value)`, `joinOrigin(origin,
181
- path)` — the pieces the class is built from, for a caller who wants the
182
- serialiser without the builder.
209
+ path)`, `SitemapImage`, and `SitemapImageLimitExceeded` — the pieces the class
210
+ is built from, for a caller who wants the serialiser or image diagnostics
211
+ without the builder.
183
212
 
184
213
 
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
8
 
9
- - [sitemap-overview](@warlock.js/sitemap/sitemap-overview/SKILL.md): @warlock.js/sitemap — overview in @warlock.js/sitemap; use when you need to sitemap overview.
9
+ - [sitemap-overview](@warlock.js/sitemap/sitemap-overview/SKILL.md): (no description)
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/warlockjs/sitemap"
10
10
  },
11
- "version": "5.19.1",
11
+ "version": "5.21.0",
12
12
  "main": "./cjs/index.cjs",
13
13
  "module": "./esm/index.mjs",
14
14
  "types": "./esm/index.d.mts",