@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/CHANGELOG.md +21 -1
- package/cjs/index.cjs +194 -10
- package/cjs/index.cjs.map +1 -1
- package/esm/generation-manifest.d.mts +40 -0
- package/esm/generation-manifest.mjs +114 -0
- package/esm/generation-manifest.mjs.map +1 -0
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/normalize-entry.mjs +19 -1
- package/esm/normalize-entry.mjs.map +1 -1
- package/esm/sitemap-index-options.mjs +10 -1
- package/esm/sitemap-index-options.mjs.map +1 -1
- package/esm/sitemap-index-types.d.mts +5 -3
- package/esm/sitemap-index-xml.mjs +2 -2
- package/esm/sitemap-index-xml.mjs.map +1 -1
- package/esm/sitemap-index.d.mts +2 -0
- package/esm/sitemap-index.mjs +13 -2
- package/esm/sitemap-index.mjs.map +1 -1
- package/esm/sitemap-shard-writer.mjs +9 -3
- package/esm/sitemap-shard-writer.mjs.map +1 -1
- package/esm/sitemap.d.mts +3 -0
- package/esm/sitemap.mjs +12 -1
- package/esm/sitemap.mjs.map +1 -1
- package/esm/types.d.mts +13 -3
- package/esm/xml.mjs +12 -2
- package/esm/xml.mjs.map +1 -1
- package/llms-full.txt +190 -161
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/skills/sitemap-overview/SKILL.md +190 -161
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:
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
149
|
+
sitemap.add({
|
|
150
|
+
path: "/products/widget",
|
|
151
|
+
images: [{ loc: "https://cdn.example.com/widget.jpg" }],
|
|
152
|
+
});
|
|
111
153
|
```
|
|
112
154
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
182
|
-
|
|
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