@whatworks/payload-sitemap 0.1.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/LICENSE.md +21 -0
- package/README.md +201 -0
- package/dist/exports/next.d.ts +78 -0
- package/dist/exports/next.js +118 -0
- package/dist/exports/next.js.map +1 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +281 -0
- package/dist/index.js.map +1 -0
- package/dist/types-j8T8euN5.d.ts +271 -0
- package/dist/xml-DSOM2moN.js +487 -0
- package/dist/xml-DSOM2moN.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
//#region src/core/cache.ts
|
|
2
|
+
const SITEMAP_CACHE_TAG = "payload-sitemap";
|
|
3
|
+
const sitemapCacheTag = (group) => `${SITEMAP_CACHE_TAG}:${group}`;
|
|
4
|
+
const createMemoryCache = () => {
|
|
5
|
+
const store = /* @__PURE__ */ new Map();
|
|
6
|
+
return {
|
|
7
|
+
invalidate(keys) {
|
|
8
|
+
for (const key of keys) store.delete(key);
|
|
9
|
+
},
|
|
10
|
+
async wrap(key, fn) {
|
|
11
|
+
const cached = store.get(key);
|
|
12
|
+
if (cached) return cached;
|
|
13
|
+
const entries = await fn();
|
|
14
|
+
store.set(key, entries);
|
|
15
|
+
return entries;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const noopCache = {
|
|
20
|
+
invalidate: () => {},
|
|
21
|
+
wrap: (_key, fn) => fn()
|
|
22
|
+
};
|
|
23
|
+
let nextCachePromise;
|
|
24
|
+
const loadNextCache = () => {
|
|
25
|
+
nextCachePromise ??= import("next/cache").then((mod) => mod, () => null);
|
|
26
|
+
return nextCachePromise;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Caches entries in the Next.js Data Cache tagged `payload-sitemap:<group>`, so
|
|
30
|
+
* invalidation is `revalidateTag` — shared across serverless instances on Vercel.
|
|
31
|
+
* Degrades to uncached execution when `next/cache` is unavailable or errors
|
|
32
|
+
* (standalone Payload, seed scripts, non-request contexts).
|
|
33
|
+
*/
|
|
34
|
+
const createNextTagsCache = () => ({
|
|
35
|
+
async invalidate(keys) {
|
|
36
|
+
const mod = await loadNextCache();
|
|
37
|
+
if (!mod?.revalidateTag) return;
|
|
38
|
+
for (const key of keys) try {
|
|
39
|
+
mod.revalidateTag(sitemapCacheTag(key), { expire: 0 });
|
|
40
|
+
} catch {}
|
|
41
|
+
},
|
|
42
|
+
async wrap(key, fn) {
|
|
43
|
+
const mod = await loadNextCache();
|
|
44
|
+
if (!mod?.unstable_cache) return fn();
|
|
45
|
+
try {
|
|
46
|
+
return await mod.unstable_cache(fn, [SITEMAP_CACHE_TAG, key], {
|
|
47
|
+
revalidate: false,
|
|
48
|
+
tags: [SITEMAP_CACHE_TAG, sitemapCacheTag(key)]
|
|
49
|
+
})();
|
|
50
|
+
} catch {
|
|
51
|
+
return fn();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
/** Next tag cache when `next/cache` is importable, in-memory otherwise. */
|
|
56
|
+
const createAutoCache = () => {
|
|
57
|
+
const memory = createMemoryCache();
|
|
58
|
+
const nextTags = createNextTagsCache();
|
|
59
|
+
const pick = async () => await loadNextCache() ? nextTags : memory;
|
|
60
|
+
return {
|
|
61
|
+
invalidate: async (keys) => (await pick()).invalidate(keys),
|
|
62
|
+
wrap: async (key, fn) => (await pick()).wrap(key, fn)
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const resolveCache = (option) => {
|
|
66
|
+
if (option === "memory") return createMemoryCache();
|
|
67
|
+
if (option === "none") return noopCache;
|
|
68
|
+
if (option === "auto" || option === void 0) return createAutoCache();
|
|
69
|
+
return option;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/core/siteUrl.ts
|
|
74
|
+
const validateSiteUrl = (raw, source) => {
|
|
75
|
+
let url;
|
|
76
|
+
try {
|
|
77
|
+
url = new URL(raw);
|
|
78
|
+
} catch {
|
|
79
|
+
throw new Error(`[payload-sitemap] Invalid siteUrl "${raw}" (from ${source}) — expected an absolute URL like https://example.com`);
|
|
80
|
+
}
|
|
81
|
+
const basePath = url.pathname.replace(/\/+$/, "");
|
|
82
|
+
return `${url.origin}${basePath}`;
|
|
83
|
+
};
|
|
84
|
+
const LOCAL_HOST = /^(?:localhost|127\.|0\.0\.0\.0|\[::1\])/;
|
|
85
|
+
/**
|
|
86
|
+
* Origin of the incoming request: proxy headers first (they carry the public host
|
|
87
|
+
* when the server sits behind one), then the request URL. Host headers are
|
|
88
|
+
* client-controlled: responses built from them must never be written to a shared
|
|
89
|
+
* cache (entries are cached as site-relative paths for this reason).
|
|
90
|
+
*/
|
|
91
|
+
const siteUrlFromRequest = (ctx) => {
|
|
92
|
+
const headers = ctx.request?.headers;
|
|
93
|
+
const host = headers?.get("x-forwarded-host")?.split(",")[0]?.trim() ?? headers?.get("host");
|
|
94
|
+
if (host) return `${headers?.get("x-forwarded-proto")?.split(",")[0]?.trim() ?? (LOCAL_HOST.test(host) ? "http" : "https")}://${host}`;
|
|
95
|
+
if (ctx.request?.url) try {
|
|
96
|
+
return new URL(ctx.request.url).origin;
|
|
97
|
+
} catch {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Resolves the site origin from explicit configuration or environment variables.
|
|
103
|
+
* Returns `undefined` when neither is set (the caller may then fall back to the
|
|
104
|
+
* request). Env vars win over request headers so that deployments reachable via
|
|
105
|
+
* non-canonical aliases (e.g. *.vercel.app) still emit the canonical domain.
|
|
106
|
+
*/
|
|
107
|
+
const resolveStaticSiteUrl = (configured) => {
|
|
108
|
+
if (configured) return validateSiteUrl(configured, "the siteUrl option");
|
|
109
|
+
if (process.env.SITE_URL) return validateSiteUrl(process.env.SITE_URL, "SITE_URL");
|
|
110
|
+
if (process.env.NEXT_PUBLIC_SERVER_URL) return validateSiteUrl(process.env.NEXT_PUBLIC_SERVER_URL, "NEXT_PUBLIC_SERVER_URL");
|
|
111
|
+
if (process.env.VERCEL_PROJECT_PRODUCTION_URL) return validateSiteUrl(`https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`, "VERCEL_PROJECT_PRODUCTION_URL");
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Resolves the canonical frontend origin: explicit option → SITE_URL →
|
|
115
|
+
* NEXT_PUBLIC_SERVER_URL → VERCEL_PROJECT_PRODUCTION_URL → the incoming
|
|
116
|
+
* request, when available.
|
|
117
|
+
*/
|
|
118
|
+
const resolveSiteUrl = (configured, ctx) => {
|
|
119
|
+
if (typeof configured === "function") return validateSiteUrl(configured(ctx ?? {}), "the siteUrl function");
|
|
120
|
+
const staticUrl = resolveStaticSiteUrl(configured);
|
|
121
|
+
if (staticUrl) return staticUrl;
|
|
122
|
+
const fromRequest = ctx && siteUrlFromRequest(ctx);
|
|
123
|
+
if (fromRequest) return validateSiteUrl(fromRequest, "the incoming request");
|
|
124
|
+
throw new Error("[payload-sitemap] No siteUrl available. Set the `siteUrl` plugin option, one of the SITE_URL / NEXT_PUBLIC_SERVER_URL environment variables, or call from a context with an incoming request.");
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/core/resolved.ts
|
|
129
|
+
/** Reserved group name for entries from the `routes` option. */
|
|
130
|
+
const ROUTES_GROUP = "_routes";
|
|
131
|
+
const DEFAULT_CHUNK_SIZE = 25e3;
|
|
132
|
+
const DEFAULT_CACHE_CONTROL = "public, max-age=0, s-maxage=600, stale-while-revalidate=86400";
|
|
133
|
+
const DEFAULT_ENDPOINTS_PATH = "/sitemap";
|
|
134
|
+
const resolveEndpoints = (option) => {
|
|
135
|
+
if (!option) return false;
|
|
136
|
+
const config = option === true ? {} : option;
|
|
137
|
+
return {
|
|
138
|
+
access: config.access,
|
|
139
|
+
cacheControl: config.cacheControl ?? DEFAULT_CACHE_CONTROL,
|
|
140
|
+
json: config.json ? { access: (config.json === true ? void 0 : config.json.access) ?? (({ req }) => Boolean(req.user)) } : false,
|
|
141
|
+
origin: config.origin,
|
|
142
|
+
path: config.path ?? DEFAULT_ENDPOINTS_PATH
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const resolveSitemapConfig = (pluginConfig) => {
|
|
146
|
+
const collections = pluginConfig.collections;
|
|
147
|
+
if (ROUTES_GROUP in collections) throw new Error(`[payload-sitemap] "${ROUTES_GROUP}" is reserved for the \`routes\` option and cannot be used as a collection slug.`);
|
|
148
|
+
let memoizedStaticSiteUrl;
|
|
149
|
+
return {
|
|
150
|
+
cache: resolveCache(pluginConfig.cache),
|
|
151
|
+
chunkSize: pluginConfig.chunkSize ?? DEFAULT_CHUNK_SIZE,
|
|
152
|
+
collections,
|
|
153
|
+
endpoints: resolveEndpoints(pluginConfig.endpoints),
|
|
154
|
+
excludeFieldPath: pluginConfig.adminFields?.exclude !== false ? pluginConfig.adminFields?.group ? `${pluginConfig.adminFields.group}.excludeFromSitemap` : "excludeFromSitemap" : void 0,
|
|
155
|
+
groups: [...Object.keys(collections), ...pluginConfig.routes ? [ROUTES_GROUP] : []],
|
|
156
|
+
robots: pluginConfig.robots ?? {},
|
|
157
|
+
routes: pluginConfig.routes,
|
|
158
|
+
siteUrl: (ctx) => {
|
|
159
|
+
if (typeof pluginConfig.siteUrl === "function") return resolveSiteUrl(pluginConfig.siteUrl, ctx);
|
|
160
|
+
return (memoizedStaticSiteUrl ??= resolveStaticSiteUrl(pluginConfig.siteUrl)) != null ? memoizedStaticSiteUrl : resolveSiteUrl(void 0, ctx);
|
|
161
|
+
},
|
|
162
|
+
trailingSlash: pluginConfig.trailingSlash ?? false
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
/** Reads the resolved plugin config stashed on the Payload config by `sitemapPlugin`. */
|
|
166
|
+
const getSitemapConfig = (config) => {
|
|
167
|
+
const resolved = config.custom?.sitemap;
|
|
168
|
+
if (!resolved) throw new Error("[payload-sitemap] Plugin config not found — is sitemapPlugin() installed (and not disabled) on this Payload config?");
|
|
169
|
+
return resolved;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/core/entries.ts
|
|
174
|
+
const PAGE_SIZE = 1e3;
|
|
175
|
+
const formatLoc = (path, siteUrl, trailingSlash) => {
|
|
176
|
+
if (/^https?:\/\//.test(path)) return path;
|
|
177
|
+
let normalized = path.startsWith("/") ? path : `/${path}`;
|
|
178
|
+
if (normalized !== "/") normalized = trailingSlash ? normalized.replace(/\/*$/, "/") : normalized.replace(/\/+$/, "");
|
|
179
|
+
return `${siteUrl}${normalized}`;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Joins cached site-relative entries onto the resolved site origin. Entries are
|
|
183
|
+
* cached with relative `loc` paths so cached data is host-independent — a
|
|
184
|
+
* request-derived siteUrl can never leak into the shared cache.
|
|
185
|
+
*/
|
|
186
|
+
const finalizeEntries = (entries, { siteUrl, trailingSlash }) => entries.map((entry) => ({
|
|
187
|
+
...entry,
|
|
188
|
+
loc: formatLoc(entry.loc, siteUrl, trailingSlash)
|
|
189
|
+
}));
|
|
190
|
+
const normalizeLastMod = (value) => {
|
|
191
|
+
if (!value) return;
|
|
192
|
+
let date;
|
|
193
|
+
if (value instanceof Date) date = value;
|
|
194
|
+
else if (typeof value === "string" || typeof value === "number") date = new Date(value);
|
|
195
|
+
else return;
|
|
196
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
197
|
+
};
|
|
198
|
+
const fetchCollectionEntries = async ({ config, payload, req }, slug, collConfig) => {
|
|
199
|
+
const collection = payload.collections[slug];
|
|
200
|
+
if (!collection) {
|
|
201
|
+
payload.logger.warn(`[payload-sitemap] Collection "${slug}" does not exist — skipping.`);
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
const draftsEnabled = Boolean(collection.config.versions?.drafts);
|
|
205
|
+
const conditions = [];
|
|
206
|
+
if (collConfig.where) conditions.push(collConfig.where);
|
|
207
|
+
if (config.excludeFieldPath) conditions.push({ [config.excludeFieldPath]: { not_equals: true } });
|
|
208
|
+
if (draftsEnabled) conditions.push({ _status: { equals: "published" } });
|
|
209
|
+
const select = {
|
|
210
|
+
...collConfig.select ?? { slug: true },
|
|
211
|
+
updatedAt: true
|
|
212
|
+
};
|
|
213
|
+
if (typeof collConfig.lastMod === "string") select[collConfig.lastMod] = true;
|
|
214
|
+
const entries = [];
|
|
215
|
+
let page = 1;
|
|
216
|
+
while (true) {
|
|
217
|
+
const result = await payload.find({
|
|
218
|
+
collection: slug,
|
|
219
|
+
depth: 0,
|
|
220
|
+
limit: PAGE_SIZE,
|
|
221
|
+
overrideAccess: true,
|
|
222
|
+
page,
|
|
223
|
+
pagination: true,
|
|
224
|
+
req,
|
|
225
|
+
select,
|
|
226
|
+
sort: "createdAt",
|
|
227
|
+
where: conditions.length ? { and: conditions } : void 0
|
|
228
|
+
});
|
|
229
|
+
for (const doc of result.docs) {
|
|
230
|
+
const path = await collConfig.path({
|
|
231
|
+
doc,
|
|
232
|
+
req
|
|
233
|
+
});
|
|
234
|
+
if (!path) continue;
|
|
235
|
+
let lastmod;
|
|
236
|
+
if (collConfig.lastMod !== false) lastmod = normalizeLastMod(typeof collConfig.lastMod === "function" ? collConfig.lastMod(doc) : doc[typeof collConfig.lastMod === "string" ? collConfig.lastMod : "updatedAt"]);
|
|
237
|
+
entries.push({
|
|
238
|
+
loc: path,
|
|
239
|
+
...lastmod ? { lastmod } : {}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (!result.hasNextPage) break;
|
|
243
|
+
page += 1;
|
|
244
|
+
}
|
|
245
|
+
return entries;
|
|
246
|
+
};
|
|
247
|
+
const fetchRouteEntries = async ({ config, payload, req }) => {
|
|
248
|
+
return (typeof config.routes === "function" ? await config.routes({
|
|
249
|
+
payload,
|
|
250
|
+
req
|
|
251
|
+
}) : config.routes ?? []).map((route) => {
|
|
252
|
+
const lastmod = normalizeLastMod(route.lastMod);
|
|
253
|
+
return {
|
|
254
|
+
loc: route.path,
|
|
255
|
+
...lastmod ? { lastmod } : {},
|
|
256
|
+
...route.changeFreq ? { changefreq: route.changeFreq } : {},
|
|
257
|
+
...route.priority !== void 0 ? { priority: route.priority } : {}
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Cached entries for one group (a collection slug or `ROUTES_GROUP`).
|
|
263
|
+
* `loc` values are site-relative — pass through `finalizeEntries` before rendering.
|
|
264
|
+
*/
|
|
265
|
+
const getGroupEntries = async (args) => {
|
|
266
|
+
const { config, group } = args;
|
|
267
|
+
return config.cache.wrap(group, () => {
|
|
268
|
+
if (group === ROUTES_GROUP) return fetchRouteEntries(args);
|
|
269
|
+
const collConfig = config.collections[group];
|
|
270
|
+
if (!collConfig) return Promise.resolve([]);
|
|
271
|
+
return fetchCollectionEntries(args, group, collConfig);
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* All sitemap entries with absolute URLs, keyed by group. Public API for SSG
|
|
276
|
+
* frontends and custom delivery — reads the plugin config from the Payload
|
|
277
|
+
* instance. Pass `req` (or `request` — a Fetch Request, or
|
|
278
|
+
* `{ headers: await headers() }` in an RSC) so the site origin can be derived
|
|
279
|
+
* from the request when it isn't configured statically.
|
|
280
|
+
*/
|
|
281
|
+
const getSitemapEntries = async (payload, options) => {
|
|
282
|
+
const config = getSitemapConfig(payload.config);
|
|
283
|
+
const groups = options?.groups ?? config.groups;
|
|
284
|
+
const siteUrl = config.siteUrl({ request: options?.request ?? options?.req });
|
|
285
|
+
const result = {};
|
|
286
|
+
for (const group of groups) result[group] = finalizeEntries(await getGroupEntries({
|
|
287
|
+
config,
|
|
288
|
+
group,
|
|
289
|
+
payload,
|
|
290
|
+
req: options?.req
|
|
291
|
+
}), {
|
|
292
|
+
siteUrl,
|
|
293
|
+
trailingSlash: config.trailingSlash
|
|
294
|
+
});
|
|
295
|
+
return result;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/core/chunks.ts
|
|
300
|
+
const chunkFileName = (group, index) => `${group}-${index + 1}.xml`;
|
|
301
|
+
/**
|
|
302
|
+
* Matches a chunk filename against the known groups by prefix, so group slugs
|
|
303
|
+
* containing hyphens or digits parse unambiguously.
|
|
304
|
+
*/
|
|
305
|
+
const matchChunkFile = (file, groups) => {
|
|
306
|
+
for (const group of groups) {
|
|
307
|
+
if (!file.startsWith(`${group}-`)) continue;
|
|
308
|
+
const match = /^([1-9]\d*)\.xml$/.exec(file.slice(group.length + 1));
|
|
309
|
+
if (match) return {
|
|
310
|
+
group,
|
|
311
|
+
index: Number(match[1]) - 1
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
};
|
|
316
|
+
const chunkSizeFor = (config, group) => config.collections[group]?.chunkSize ?? config.chunkSize;
|
|
317
|
+
const maxLastmod = (entries) => {
|
|
318
|
+
let max;
|
|
319
|
+
for (const entry of entries) if (entry.lastmod && (!max || entry.lastmod > max)) max = entry.lastmod;
|
|
320
|
+
return max;
|
|
321
|
+
};
|
|
322
|
+
/** Items for the `<sitemapindex>`: one per chunk of each non-empty group. */
|
|
323
|
+
const getIndexItems = async (args) => {
|
|
324
|
+
const { chunkUrl, config } = args;
|
|
325
|
+
const items = [];
|
|
326
|
+
for (const group of config.groups) {
|
|
327
|
+
const entries = await getGroupEntries({
|
|
328
|
+
...args,
|
|
329
|
+
group
|
|
330
|
+
});
|
|
331
|
+
if (!entries.length) continue;
|
|
332
|
+
const size = chunkSizeFor(config, group);
|
|
333
|
+
for (let index = 0; index * size < entries.length; index++) {
|
|
334
|
+
const lastmod = maxLastmod(entries.slice(index * size, (index + 1) * size));
|
|
335
|
+
items.push({
|
|
336
|
+
loc: chunkUrl(chunkFileName(group, index)),
|
|
337
|
+
...lastmod ? { lastmod } : {}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return items;
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* Entries for one chunk file, or `null` when the filename matches no group/range.
|
|
345
|
+
* `loc` values are site-relative — pass through `finalizeEntries` before rendering.
|
|
346
|
+
*/
|
|
347
|
+
const getChunkEntries = async (args) => {
|
|
348
|
+
const { config, file } = args;
|
|
349
|
+
const match = matchChunkFile(file, config.groups);
|
|
350
|
+
if (!match) return null;
|
|
351
|
+
const entries = await getGroupEntries({
|
|
352
|
+
...args,
|
|
353
|
+
group: match.group
|
|
354
|
+
});
|
|
355
|
+
const size = chunkSizeFor(config, match.group);
|
|
356
|
+
const start = match.index * size;
|
|
357
|
+
if (match.index > 0 && start >= entries.length) return null;
|
|
358
|
+
return {
|
|
359
|
+
entries: entries.slice(start, start + size),
|
|
360
|
+
group: match.group
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/core/robots.ts
|
|
366
|
+
const toArray = (value) => value === void 0 ? [] : Array.isArray(value) ? [value].flat() : [value];
|
|
367
|
+
const safePathname = (url) => {
|
|
368
|
+
try {
|
|
369
|
+
return new URL(url).pathname;
|
|
370
|
+
} catch {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Adds `Allow` exceptions for sitemap URLs that fall under a disallowed prefix
|
|
376
|
+
* (e.g. a sitemap served from `/api/...` while `/api/` is disallowed).
|
|
377
|
+
*/
|
|
378
|
+
const allowSitemaps = (rules, sitemaps) => rules.map((rule) => {
|
|
379
|
+
const disallow = toArray(rule.disallow);
|
|
380
|
+
const allow = new Set(toArray(rule.allow));
|
|
381
|
+
for (const sitemap of sitemaps) {
|
|
382
|
+
const pathname = safePathname(sitemap);
|
|
383
|
+
if (pathname && disallow.some((prefix) => prefix !== "/" && pathname.startsWith(prefix))) allow.add(pathname);
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
...rule,
|
|
387
|
+
...allow.size ? { allow: [...allow] } : {}
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
const buildRobotsData = ({ adminRoute = "/admin", apiRoute = "/api", options = {}, sitemaps }) => {
|
|
391
|
+
const isProduction = options.isProduction ?? (process.env.VERCEL_ENV ? process.env.VERCEL_ENV === "production" : process.env.NODE_ENV === "production");
|
|
392
|
+
let data;
|
|
393
|
+
if (!isProduction) data = {
|
|
394
|
+
rules: [{
|
|
395
|
+
disallow: "/",
|
|
396
|
+
userAgent: "*"
|
|
397
|
+
}],
|
|
398
|
+
sitemaps: []
|
|
399
|
+
};
|
|
400
|
+
else {
|
|
401
|
+
const sitemapUrls = options.sitemaps ?? sitemaps;
|
|
402
|
+
data = {
|
|
403
|
+
rules: allowSitemaps(options.rules ?? [{
|
|
404
|
+
disallow: [
|
|
405
|
+
`${adminRoute}/`,
|
|
406
|
+
`${apiRoute}/`,
|
|
407
|
+
...options.disallow ?? []
|
|
408
|
+
],
|
|
409
|
+
userAgent: "*"
|
|
410
|
+
}], sitemapUrls),
|
|
411
|
+
sitemaps: sitemapUrls
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return options.transform ? options.transform(data) : data;
|
|
415
|
+
};
|
|
416
|
+
const renderRobotsTxt = (data) => {
|
|
417
|
+
const blocks = data.rules.map((rule) => {
|
|
418
|
+
return [
|
|
419
|
+
...toArray(rule.userAgent).map((agent) => `User-agent: ${agent}`),
|
|
420
|
+
...toArray(rule.allow).map((path) => `Allow: ${path}`),
|
|
421
|
+
...toArray(rule.disallow).map((path) => `Disallow: ${path}`),
|
|
422
|
+
...rule.crawlDelay !== void 0 ? [`Crawl-delay: ${rule.crawlDelay}`] : []
|
|
423
|
+
].join("\n");
|
|
424
|
+
});
|
|
425
|
+
const footer = [...data.host ? [`Host: ${data.host}`] : [], ...data.sitemaps.map((url) => `Sitemap: ${url}`)];
|
|
426
|
+
return [...blocks, ...footer.length ? [footer.join("\n")] : []].join("\n\n") + "\n";
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* robots.txt for any delivery mechanism. Defaults come from the plugin config;
|
|
430
|
+
* `overrides` win field-by-field, and `transform` gets the final say. Pass
|
|
431
|
+
* `request` so the default sitemap URL can derive its origin from the incoming
|
|
432
|
+
* request when `siteUrl` isn't configured statically.
|
|
433
|
+
*/
|
|
434
|
+
const generateRobotsTxt = async (configInput, overrides) => {
|
|
435
|
+
const config = await configInput;
|
|
436
|
+
const sitemapConfig = getSitemapConfig(config);
|
|
437
|
+
const { request, ...overrideOptions } = overrides ?? {};
|
|
438
|
+
const options = {
|
|
439
|
+
...sitemapConfig.robots,
|
|
440
|
+
...overrideOptions
|
|
441
|
+
};
|
|
442
|
+
const sitemaps = options.sitemaps ?? [`${sitemapConfig.siteUrl({ request })}/sitemap.xml`];
|
|
443
|
+
return renderRobotsTxt(buildRobotsData({
|
|
444
|
+
adminRoute: config.routes.admin,
|
|
445
|
+
apiRoute: config.routes.api,
|
|
446
|
+
options,
|
|
447
|
+
sitemaps
|
|
448
|
+
}));
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/core/xml.ts
|
|
453
|
+
const XML_ESCAPES = {
|
|
454
|
+
"\"": """,
|
|
455
|
+
"&": "&",
|
|
456
|
+
"'": "'",
|
|
457
|
+
"<": "<",
|
|
458
|
+
">": ">"
|
|
459
|
+
};
|
|
460
|
+
const escapeXml = (value) => value.replace(/[&<>"']/g, (char) => XML_ESCAPES[char]);
|
|
461
|
+
const clampPriority = (priority) => String(Math.min(1, Math.max(0, priority)));
|
|
462
|
+
const buildUrlsetXml = (entries, defaults) => {
|
|
463
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${entries.map((entry) => {
|
|
464
|
+
const changefreq = entry.changefreq ?? defaults?.changeFreq;
|
|
465
|
+
const priority = entry.priority ?? defaults?.priority;
|
|
466
|
+
return [
|
|
467
|
+
" <url>",
|
|
468
|
+
` <loc>${escapeXml(entry.loc)}</loc>`,
|
|
469
|
+
entry.lastmod ? ` <lastmod>${escapeXml(entry.lastmod)}</lastmod>` : null,
|
|
470
|
+
changefreq ? ` <changefreq>${changefreq}</changefreq>` : null,
|
|
471
|
+
priority !== void 0 ? ` <priority>${clampPriority(priority)}</priority>` : null,
|
|
472
|
+
" </url>"
|
|
473
|
+
].filter(Boolean).join("\n");
|
|
474
|
+
}).join("\n")}\n</urlset>\n`;
|
|
475
|
+
};
|
|
476
|
+
const buildSitemapIndexXml = (items) => {
|
|
477
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${items.map((item) => [
|
|
478
|
+
" <sitemap>",
|
|
479
|
+
` <loc>${escapeXml(item.loc)}</loc>`,
|
|
480
|
+
item.lastmod ? ` <lastmod>${escapeXml(item.lastmod)}</lastmod>` : null,
|
|
481
|
+
" </sitemap>"
|
|
482
|
+
].filter(Boolean).join("\n")).join("\n")}\n</sitemapindex>\n`;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
//#endregion
|
|
486
|
+
export { createNextTagsCache as C, createMemoryCache as S, sitemapCacheTag as T, getSitemapConfig as _, generateRobotsTxt as a, siteUrlFromRequest as b, getChunkEntries as c, finalizeEntries as d, formatLoc as f, ROUTES_GROUP as g, DEFAULT_CACHE_CONTROL as h, buildRobotsData as i, getIndexItems as l, getSitemapEntries as m, buildUrlsetXml as n, renderRobotsTxt as o, getGroupEntries as p, escapeXml as r, chunkFileName as s, buildSitemapIndexXml as t, matchChunkFile as u, resolveSitemapConfig as v, noopCache as w, SITEMAP_CACHE_TAG as x, resolveSiteUrl as y };
|
|
487
|
+
//# sourceMappingURL=xml-DSOM2moN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml-DSOM2moN.js","names":["noopCache: SitemapCache","nextCachePromise: Promise<NextCacheModule | null> | undefined","url: URL","memoizedStaticSiteUrl: string | undefined","date: Date","collection: Payload['collections'][CollectionSlug] | undefined","conditions: Where[]","select: SelectType","entries: SitemapEntry[]","lastmod: string | undefined","result: Record<string, SitemapEntry[]>","max: string | undefined","items: Array<{ lastmod?: string; loc: string }>","data: RobotsData","XML_ESCAPES: Record<string, string>"],"sources":["../src/core/cache.ts","../src/core/siteUrl.ts","../src/core/resolved.ts","../src/core/entries.ts","../src/core/chunks.ts","../src/core/robots.ts","../src/core/xml.ts"],"sourcesContent":["import type { SitemapCache, SitemapEntry, SitemapPluginConfig } from '../types.js'\n\nexport const SITEMAP_CACHE_TAG = 'payload-sitemap'\n\nexport const sitemapCacheTag = (group: string): string => `${SITEMAP_CACHE_TAG}:${group}`\n\nexport const createMemoryCache = (): SitemapCache => {\n const store = new Map<string, SitemapEntry[]>()\n return {\n invalidate(keys) {\n for (const key of keys) {\n store.delete(key)\n }\n },\n async wrap(key, fn) {\n const cached = store.get(key)\n if (cached) {\n return cached\n }\n const entries = await fn()\n store.set(key, entries)\n return entries\n },\n }\n}\n\nexport const noopCache: SitemapCache = {\n invalidate: () => {},\n wrap: (_key, fn) => fn(),\n}\n\ntype NextCacheModule = {\n revalidateTag?: (tag: string, profile?: unknown) => void\n unstable_cache?: <T>(\n fn: () => Promise<T>,\n keyParts: string[],\n options: { revalidate: false; tags: string[] },\n ) => () => Promise<T>\n}\n\nlet nextCachePromise: Promise<NextCacheModule | null> | undefined\n\nconst loadNextCache = (): Promise<NextCacheModule | null> => {\n nextCachePromise ??= import('next/cache').then(\n (mod) => mod as NextCacheModule,\n () => null,\n )\n return nextCachePromise\n}\n\n/**\n * Caches entries in the Next.js Data Cache tagged `payload-sitemap:<group>`, so\n * invalidation is `revalidateTag` — shared across serverless instances on Vercel.\n * Degrades to uncached execution when `next/cache` is unavailable or errors\n * (standalone Payload, seed scripts, non-request contexts).\n */\nexport const createNextTagsCache = (): SitemapCache => ({\n async invalidate(keys) {\n const mod = await loadNextCache()\n if (!mod?.revalidateTag) {\n return\n }\n for (const key of keys) {\n try {\n // Next 16 requires a cache profile to expire instantly; Next 15's\n // single-argument revalidateTag ignores the extra argument.\n mod.revalidateTag(sitemapCacheTag(key), { expire: 0 })\n } catch {\n // Outside a Next request scope (scripts, workers) there is nothing to invalidate.\n }\n }\n },\n async wrap(key, fn) {\n const mod = await loadNextCache()\n if (!mod?.unstable_cache) {\n return fn()\n }\n try {\n return await mod.unstable_cache(fn, [SITEMAP_CACHE_TAG, key], {\n revalidate: false,\n tags: [SITEMAP_CACHE_TAG, sitemapCacheTag(key)],\n })()\n } catch {\n return fn()\n }\n },\n})\n\n/** Next tag cache when `next/cache` is importable, in-memory otherwise. */\nexport const createAutoCache = (): SitemapCache => {\n const memory = createMemoryCache()\n const nextTags = createNextTagsCache()\n const pick = async (): Promise<SitemapCache> => ((await loadNextCache()) ? nextTags : memory)\n return {\n invalidate: async (keys) => (await pick()).invalidate(keys),\n wrap: async (key, fn) => (await pick()).wrap(key, fn),\n }\n}\n\nexport const resolveCache = (option: SitemapPluginConfig['cache']): SitemapCache => {\n if (option === 'memory') {\n return createMemoryCache()\n }\n if (option === 'none') {\n return noopCache\n }\n if (option === 'auto' || option === undefined) {\n return createAutoCache()\n }\n return option\n}\n","/**\n * Context a site URL can be derived from when nothing is configured. `request`\n * structurally accepts a Fetch `Request`, a `PayloadRequest`, or — in contexts\n * without a request object (e.g. Next metadata routes) — a bare\n * `{ headers: await headers() }`.\n */\nexport type SiteUrlContext = {\n /** The incoming request. */\n request?: { headers?: Headers; url?: null | string }\n}\n\nconst validateSiteUrl = (raw: string, source: string): string => {\n let url: URL\n try {\n url = new URL(raw)\n } catch {\n throw new Error(\n `[payload-sitemap] Invalid siteUrl \"${raw}\" (from ${source}) — expected an absolute URL like https://example.com`,\n )\n }\n const basePath = url.pathname.replace(/\\/+$/, '')\n return `${url.origin}${basePath}`\n}\n\nconst LOCAL_HOST = /^(?:localhost|127\\.|0\\.0\\.0\\.0|\\[::1\\])/\n\n/**\n * Origin of the incoming request: proxy headers first (they carry the public host\n * when the server sits behind one), then the request URL. Host headers are\n * client-controlled: responses built from them must never be written to a shared\n * cache (entries are cached as site-relative paths for this reason).\n */\nexport const siteUrlFromRequest = (ctx: SiteUrlContext): string | undefined => {\n const headers = ctx.request?.headers\n const host = headers?.get('x-forwarded-host')?.split(',')[0]?.trim() ?? headers?.get('host')\n if (host) {\n const proto =\n headers?.get('x-forwarded-proto')?.split(',')[0]?.trim() ??\n (LOCAL_HOST.test(host) ? 'http' : 'https')\n return `${proto}://${host}`\n }\n if (ctx.request?.url) {\n try {\n return new URL(ctx.request.url).origin\n } catch {\n return undefined\n }\n }\n return undefined\n}\n\n/**\n * Resolves the site origin from explicit configuration or environment variables.\n * Returns `undefined` when neither is set (the caller may then fall back to the\n * request). Env vars win over request headers so that deployments reachable via\n * non-canonical aliases (e.g. *.vercel.app) still emit the canonical domain.\n */\nexport const resolveStaticSiteUrl = (configured?: string): string | undefined => {\n if (configured) {\n return validateSiteUrl(configured, 'the siteUrl option')\n }\n if (process.env.SITE_URL) {\n return validateSiteUrl(process.env.SITE_URL, 'SITE_URL')\n }\n if (process.env.NEXT_PUBLIC_SERVER_URL) {\n return validateSiteUrl(process.env.NEXT_PUBLIC_SERVER_URL, 'NEXT_PUBLIC_SERVER_URL')\n }\n if (process.env.VERCEL_PROJECT_PRODUCTION_URL) {\n return validateSiteUrl(\n `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`,\n 'VERCEL_PROJECT_PRODUCTION_URL',\n )\n }\n return undefined\n}\n\n/**\n * Resolves the canonical frontend origin: explicit option → SITE_URL →\n * NEXT_PUBLIC_SERVER_URL → VERCEL_PROJECT_PRODUCTION_URL → the incoming\n * request, when available.\n */\nexport const resolveSiteUrl = (\n configured?: ((ctx: SiteUrlContext) => string) | string,\n ctx?: SiteUrlContext,\n): string => {\n if (typeof configured === 'function') {\n return validateSiteUrl(configured(ctx ?? {}), 'the siteUrl function')\n }\n\n const staticUrl = resolveStaticSiteUrl(configured)\n if (staticUrl) {\n return staticUrl\n }\n\n const fromRequest = ctx && siteUrlFromRequest(ctx)\n if (fromRequest) {\n return validateSiteUrl(fromRequest, 'the incoming request')\n }\n\n throw new Error(\n '[payload-sitemap] No siteUrl available. Set the `siteUrl` plugin option, one of the SITE_URL / NEXT_PUBLIC_SERVER_URL environment variables, or call from a context with an incoming request.',\n )\n}\n","import type { SanitizedConfig } from 'payload'\n\nimport type {\n InternalSitemapCollectionConfig,\n ResolvedSitemapConfig,\n ResolvedSitemapEndpoints,\n SitemapPluginConfig,\n} from '../types.js'\n\nimport { resolveCache } from './cache.js'\nimport { resolveSiteUrl, resolveStaticSiteUrl } from './siteUrl.js'\n\n/** Reserved group name for entries from the `routes` option. */\nexport const ROUTES_GROUP = '_routes'\n\nexport const DEFAULT_CHUNK_SIZE = 25_000\n\nexport const DEFAULT_CACHE_CONTROL = 'public, max-age=0, s-maxage=600, stale-while-revalidate=86400'\n\nexport const DEFAULT_ENDPOINTS_PATH = '/sitemap'\n\nconst resolveEndpoints = (\n option: SitemapPluginConfig['endpoints'],\n): false | ResolvedSitemapEndpoints => {\n if (!option) {\n return false\n }\n const config = option === true ? {} : option\n return {\n access: config.access,\n cacheControl: config.cacheControl ?? DEFAULT_CACHE_CONTROL,\n json: config.json\n ? {\n access:\n (config.json === true ? undefined : config.json.access) ??\n (({ req }) => Boolean(req.user)),\n }\n : false,\n origin: config.origin,\n path: config.path ?? DEFAULT_ENDPOINTS_PATH,\n }\n}\n\nexport const resolveSitemapConfig = (pluginConfig: SitemapPluginConfig): ResolvedSitemapConfig => {\n const collections = pluginConfig.collections as unknown as Record<\n string,\n InternalSitemapCollectionConfig\n >\n\n if (ROUTES_GROUP in collections) {\n throw new Error(\n `[payload-sitemap] \"${ROUTES_GROUP}\" is reserved for the \\`routes\\` option and cannot be used as a collection slug.`,\n )\n }\n\n let memoizedStaticSiteUrl: string | undefined\n\n return {\n cache: resolveCache(pluginConfig.cache),\n chunkSize: pluginConfig.chunkSize ?? DEFAULT_CHUNK_SIZE,\n collections,\n endpoints: resolveEndpoints(pluginConfig.endpoints),\n excludeFieldPath:\n pluginConfig.adminFields?.exclude !== false\n ? pluginConfig.adminFields?.group\n ? `${pluginConfig.adminFields.group}.excludeFromSitemap`\n : 'excludeFromSitemap'\n : undefined,\n groups: [...Object.keys(collections), ...(pluginConfig.routes ? [ROUTES_GROUP] : [])],\n robots: pluginConfig.robots ?? {},\n routes: pluginConfig.routes,\n siteUrl: (ctx) => {\n // A configured function gets full control on every call; only the static\n // sources (option string, env vars) are safe to memoize.\n if (typeof pluginConfig.siteUrl === 'function') {\n return resolveSiteUrl(pluginConfig.siteUrl, ctx)\n }\n return (memoizedStaticSiteUrl ??= resolveStaticSiteUrl(pluginConfig.siteUrl)) != null\n ? memoizedStaticSiteUrl\n : resolveSiteUrl(undefined, ctx)\n },\n trailingSlash: pluginConfig.trailingSlash ?? false,\n }\n}\n\n/** Reads the resolved plugin config stashed on the Payload config by `sitemapPlugin`. */\nexport const getSitemapConfig = (config: SanitizedConfig): ResolvedSitemapConfig => {\n const resolved = (config.custom as { sitemap?: ResolvedSitemapConfig } | undefined)?.sitemap\n if (!resolved) {\n throw new Error(\n '[payload-sitemap] Plugin config not found — is sitemapPlugin() installed (and not disabled) on this Payload config?',\n )\n }\n return resolved\n}\n","import type { CollectionSlug, Payload, PayloadRequest, SelectType, Where } from 'payload'\n\nimport type {\n InternalSitemapCollectionConfig,\n ResolvedSitemapConfig,\n SitemapEntry,\n} from '../types.js'\n\nimport { getSitemapConfig, ROUTES_GROUP } from './resolved.js'\n\nconst PAGE_SIZE = 1000\n\nexport const formatLoc = (path: string, siteUrl: string, trailingSlash: boolean): string => {\n if (/^https?:\\/\\//.test(path)) {\n return path\n }\n let normalized = path.startsWith('/') ? path : `/${path}`\n if (normalized !== '/') {\n normalized = trailingSlash ? normalized.replace(/\\/*$/, '/') : normalized.replace(/\\/+$/, '')\n }\n return `${siteUrl}${normalized}`\n}\n\n/**\n * Joins cached site-relative entries onto the resolved site origin. Entries are\n * cached with relative `loc` paths so cached data is host-independent — a\n * request-derived siteUrl can never leak into the shared cache.\n */\nexport const finalizeEntries = (\n entries: SitemapEntry[],\n { siteUrl, trailingSlash }: { siteUrl: string; trailingSlash: boolean },\n): SitemapEntry[] =>\n entries.map((entry) => ({ ...entry, loc: formatLoc(entry.loc, siteUrl, trailingSlash) }))\n\nconst normalizeLastMod = (value: unknown): string | undefined => {\n if (!value) {\n return undefined\n }\n let date: Date\n if (value instanceof Date) {\n date = value\n } else if (typeof value === 'string' || typeof value === 'number') {\n date = new Date(value)\n } else {\n return undefined\n }\n return Number.isNaN(date.getTime()) ? undefined : date.toISOString()\n}\n\ntype FetchArgs = {\n config: ResolvedSitemapConfig\n payload: Payload\n req?: PayloadRequest\n}\n\nconst fetchCollectionEntries = async (\n { config, payload, req }: FetchArgs,\n // Group keys come from plugin config, so they are plain strings; existence is\n // checked at runtime below.\n slug: CollectionSlug,\n collConfig: InternalSitemapCollectionConfig,\n): Promise<SitemapEntry[]> => {\n const collection: Payload['collections'][CollectionSlug] | undefined = payload.collections[slug]\n if (!collection) {\n payload.logger.warn(`[payload-sitemap] Collection \"${slug}\" does not exist — skipping.`)\n return []\n }\n\n // `_status` only exists on the schema when drafts are enabled; querying it on a\n // non-draft collection throws a QueryError.\n const draftsEnabled = Boolean(collection.config.versions?.drafts)\n\n const conditions: Where[] = []\n if (collConfig.where) {\n conditions.push(collConfig.where)\n }\n if (config.excludeFieldPath) {\n conditions.push({ [config.excludeFieldPath]: { not_equals: true } })\n }\n if (draftsEnabled) {\n conditions.push({ _status: { equals: 'published' } })\n }\n\n const select: SelectType = { ...(collConfig.select ?? { slug: true }), updatedAt: true }\n if (typeof collConfig.lastMod === 'string') {\n select[collConfig.lastMod] = true\n }\n\n const entries: SitemapEntry[] = []\n let page = 1\n\n while (true) {\n const result = await payload.find({\n collection: slug,\n depth: 0,\n limit: PAGE_SIZE,\n overrideAccess: true,\n page,\n pagination: true,\n req,\n select,\n sort: 'createdAt',\n where: conditions.length ? { and: conditions } : undefined,\n })\n\n for (const doc of result.docs) {\n const path = await collConfig.path({ doc, req })\n if (!path) {\n continue\n }\n\n let lastmod: string | undefined\n if (collConfig.lastMod !== false) {\n const raw =\n typeof collConfig.lastMod === 'function'\n ? collConfig.lastMod(doc)\n : (doc as unknown as Record<string, unknown>)[\n typeof collConfig.lastMod === 'string' ? collConfig.lastMod : 'updatedAt'\n ]\n lastmod = normalizeLastMod(raw)\n }\n\n entries.push({\n loc: path,\n ...(lastmod ? { lastmod } : {}),\n })\n }\n\n if (!result.hasNextPage) {\n break\n }\n page += 1\n }\n\n return entries\n}\n\nconst fetchRouteEntries = async ({ config, payload, req }: FetchArgs): Promise<SitemapEntry[]> => {\n const routes =\n typeof config.routes === 'function'\n ? await config.routes({ payload, req })\n : (config.routes ?? [])\n\n return routes.map((route) => {\n const lastmod = normalizeLastMod(route.lastMod)\n return {\n loc: route.path,\n ...(lastmod ? { lastmod } : {}),\n ...(route.changeFreq ? { changefreq: route.changeFreq } : {}),\n ...(route.priority !== undefined ? { priority: route.priority } : {}),\n }\n })\n}\n\n/**\n * Cached entries for one group (a collection slug or `ROUTES_GROUP`).\n * `loc` values are site-relative — pass through `finalizeEntries` before rendering.\n */\nexport const getGroupEntries = async (\n args: { group: string } & FetchArgs,\n): Promise<SitemapEntry[]> => {\n const { config, group } = args\n return config.cache.wrap(group, () => {\n if (group === ROUTES_GROUP) {\n return fetchRouteEntries(args)\n }\n const collConfig = config.collections[group]\n if (!collConfig) {\n return Promise.resolve([])\n }\n // The assertion only matters in consumer projects, where generated types\n // narrow CollectionSlug from string to a union of known slugs.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n return fetchCollectionEntries(args, group as CollectionSlug, collConfig)\n })\n}\n\n/**\n * All sitemap entries with absolute URLs, keyed by group. Public API for SSG\n * frontends and custom delivery — reads the plugin config from the Payload\n * instance. Pass `req` (or `request` — a Fetch Request, or\n * `{ headers: await headers() }` in an RSC) so the site origin can be derived\n * from the request when it isn't configured statically.\n */\nexport const getSitemapEntries = async (\n payload: Payload,\n options?: {\n groups?: string[]\n req?: PayloadRequest\n request?: { headers?: Headers; url?: null | string }\n },\n): Promise<Record<string, SitemapEntry[]>> => {\n const config = getSitemapConfig(payload.config)\n const groups = options?.groups ?? config.groups\n const siteUrl = config.siteUrl({ request: options?.request ?? options?.req })\n const result: Record<string, SitemapEntry[]> = {}\n for (const group of groups) {\n const entries = await getGroupEntries({ config, group, payload, req: options?.req })\n result[group] = finalizeEntries(entries, { siteUrl, trailingSlash: config.trailingSlash })\n }\n return result\n}\n","import type { Payload, PayloadRequest } from 'payload'\n\nimport type { ResolvedSitemapConfig, SitemapEntry } from '../types.js'\n\nimport { getGroupEntries } from './entries.js'\n\nexport const chunkFileName = (group: string, index: number): string => `${group}-${index + 1}.xml`\n\n/**\n * Matches a chunk filename against the known groups by prefix, so group slugs\n * containing hyphens or digits parse unambiguously.\n */\nexport const matchChunkFile = (\n file: string,\n groups: string[],\n): { group: string; index: number } | null => {\n for (const group of groups) {\n if (!file.startsWith(`${group}-`)) {\n continue\n }\n const match = /^([1-9]\\d*)\\.xml$/.exec(file.slice(group.length + 1))\n if (match) {\n return { group, index: Number(match[1]) - 1 }\n }\n }\n return null\n}\n\nconst chunkSizeFor = (config: ResolvedSitemapConfig, group: string): number =>\n config.collections[group]?.chunkSize ?? config.chunkSize\n\nconst maxLastmod = (entries: SitemapEntry[]): string | undefined => {\n let max: string | undefined\n for (const entry of entries) {\n if (entry.lastmod && (!max || entry.lastmod > max)) {\n max = entry.lastmod\n }\n }\n return max\n}\n\ntype BaseArgs = {\n config: ResolvedSitemapConfig\n payload: Payload\n req?: PayloadRequest\n}\n\n/** Items for the `<sitemapindex>`: one per chunk of each non-empty group. */\nexport const getIndexItems = async (\n args: { chunkUrl: (file: string) => string } & BaseArgs,\n): Promise<Array<{ lastmod?: string; loc: string }>> => {\n const { chunkUrl, config } = args\n const items: Array<{ lastmod?: string; loc: string }> = []\n\n for (const group of config.groups) {\n const entries = await getGroupEntries({ ...args, group })\n if (!entries.length) {\n continue\n }\n const size = chunkSizeFor(config, group)\n for (let index = 0; index * size < entries.length; index++) {\n const chunk = entries.slice(index * size, (index + 1) * size)\n const lastmod = maxLastmod(chunk)\n items.push({ loc: chunkUrl(chunkFileName(group, index)), ...(lastmod ? { lastmod } : {}) })\n }\n }\n\n return items\n}\n\n/**\n * Entries for one chunk file, or `null` when the filename matches no group/range.\n * `loc` values are site-relative — pass through `finalizeEntries` before rendering.\n */\nexport const getChunkEntries = async (\n args: { file: string } & BaseArgs,\n): Promise<{ entries: SitemapEntry[]; group: string } | null> => {\n const { config, file } = args\n const match = matchChunkFile(file, config.groups)\n if (!match) {\n return null\n }\n\n const entries = await getGroupEntries({ ...args, group: match.group })\n const size = chunkSizeFor(config, match.group)\n const start = match.index * size\n if (match.index > 0 && start >= entries.length) {\n return null\n }\n\n return { entries: entries.slice(start, start + size), group: match.group }\n}\n","import type { SanitizedConfig } from 'payload'\n\nimport type { RobotsData, RobotsOptions, RobotsRule } from '../types.js'\nimport type { SiteUrlContext } from './siteUrl.js'\n\nimport { getSitemapConfig } from './resolved.js'\n\nconst toArray = (value: string | string[] | undefined): string[] =>\n value === undefined ? [] : Array.isArray(value) ? [value].flat() : [value]\n\nconst safePathname = (url: string): string | undefined => {\n try {\n return new URL(url).pathname\n } catch {\n return undefined\n }\n}\n\n/**\n * Adds `Allow` exceptions for sitemap URLs that fall under a disallowed prefix\n * (e.g. a sitemap served from `/api/...` while `/api/` is disallowed).\n */\nconst allowSitemaps = (rules: RobotsRule[], sitemaps: string[]): RobotsRule[] =>\n rules.map((rule) => {\n const disallow = toArray(rule.disallow)\n const allow = new Set(toArray(rule.allow))\n for (const sitemap of sitemaps) {\n const pathname = safePathname(sitemap)\n if (pathname && disallow.some((prefix) => prefix !== '/' && pathname.startsWith(prefix))) {\n allow.add(pathname)\n }\n }\n return { ...rule, ...(allow.size ? { allow: [...allow] } : {}) }\n })\n\nexport type BuildRobotsArgs = {\n /** Payload admin route, used for the default disallow list. @default '/admin' */\n adminRoute?: string\n /** Payload API route, used for the default disallow list. @default '/api' */\n apiRoute?: string\n options?: RobotsOptions\n /** Absolute sitemap URL(s) advertised in the output. */\n sitemaps: string[]\n}\n\nexport const buildRobotsData = ({\n adminRoute = '/admin',\n apiRoute = '/api',\n options = {},\n sitemaps,\n}: BuildRobotsArgs): RobotsData => {\n const isProduction =\n options.isProduction ??\n (process.env.VERCEL_ENV\n ? process.env.VERCEL_ENV === 'production'\n : process.env.NODE_ENV === 'production')\n\n let data: RobotsData\n if (!isProduction) {\n data = { rules: [{ disallow: '/', userAgent: '*' }], sitemaps: [] }\n } else {\n const sitemapUrls = options.sitemaps ?? sitemaps\n const rules = options.rules ?? [\n {\n disallow: [`${adminRoute}/`, `${apiRoute}/`, ...(options.disallow ?? [])],\n userAgent: '*',\n },\n ]\n data = { rules: allowSitemaps(rules, sitemapUrls), sitemaps: sitemapUrls }\n }\n\n return options.transform ? options.transform(data) : data\n}\n\nexport const renderRobotsTxt = (data: RobotsData): string => {\n const blocks = data.rules.map((rule) => {\n const lines = [\n ...toArray(rule.userAgent).map((agent) => `User-agent: ${agent}`),\n ...toArray(rule.allow).map((path) => `Allow: ${path}`),\n ...toArray(rule.disallow).map((path) => `Disallow: ${path}`),\n ...(rule.crawlDelay !== undefined ? [`Crawl-delay: ${rule.crawlDelay}`] : []),\n ]\n return lines.join('\\n')\n })\n\n const footer = [\n ...(data.host ? [`Host: ${data.host}`] : []),\n ...data.sitemaps.map((url) => `Sitemap: ${url}`),\n ]\n\n return [...blocks, ...(footer.length ? [footer.join('\\n')] : [])].join('\\n\\n') + '\\n'\n}\n\n/**\n * robots.txt for any delivery mechanism. Defaults come from the plugin config;\n * `overrides` win field-by-field, and `transform` gets the final say. Pass\n * `request` so the default sitemap URL can derive its origin from the incoming\n * request when `siteUrl` isn't configured statically.\n */\nexport const generateRobotsTxt = async (\n configInput: Promise<SanitizedConfig> | SanitizedConfig,\n overrides?: { request?: SiteUrlContext['request'] } & RobotsOptions,\n): Promise<string> => {\n const config = await configInput\n const sitemapConfig = getSitemapConfig(config)\n const { request, ...overrideOptions } = overrides ?? {}\n const options = { ...sitemapConfig.robots, ...overrideOptions }\n const sitemaps = options.sitemaps ?? [`${sitemapConfig.siteUrl({ request })}/sitemap.xml`]\n\n return renderRobotsTxt(\n buildRobotsData({\n adminRoute: config.routes.admin,\n apiRoute: config.routes.api,\n options,\n sitemaps,\n }),\n )\n}\n","import type { ChangeFrequency, SitemapEntry } from '../types.js'\n\nconst XML_ESCAPES: Record<string, string> = {\n '\"': '"',\n '&': '&',\n \"'\": ''',\n '<': '<',\n '>': '>',\n}\n\nexport const escapeXml = (value: string): string =>\n value.replace(/[&<>\"']/g, (char) => XML_ESCAPES[char])\n\nconst clampPriority = (priority: number): string => String(Math.min(1, Math.max(0, priority)))\n\nexport const buildUrlsetXml = (\n entries: SitemapEntry[],\n defaults?: { changeFreq?: ChangeFrequency; priority?: number },\n): string => {\n const body = entries\n .map((entry) => {\n const changefreq = entry.changefreq ?? defaults?.changeFreq\n const priority = entry.priority ?? defaults?.priority\n return [\n ' <url>',\n ` <loc>${escapeXml(entry.loc)}</loc>`,\n entry.lastmod ? ` <lastmod>${escapeXml(entry.lastmod)}</lastmod>` : null,\n changefreq ? ` <changefreq>${changefreq}</changefreq>` : null,\n priority !== undefined ? ` <priority>${clampPriority(priority)}</priority>` : null,\n ' </url>',\n ]\n .filter(Boolean)\n .join('\\n')\n })\n .join('\\n')\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${body}\\n</urlset>\\n`\n}\n\nexport const buildSitemapIndexXml = (items: Array<{ lastmod?: string; loc: string }>): string => {\n const body = items\n .map((item) =>\n [\n ' <sitemap>',\n ` <loc>${escapeXml(item.loc)}</loc>`,\n item.lastmod ? ` <lastmod>${escapeXml(item.lastmod)}</lastmod>` : null,\n ' </sitemap>',\n ]\n .filter(Boolean)\n .join('\\n'),\n )\n .join('\\n')\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${body}\\n</sitemapindex>\\n`\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@whatworks/payload-sitemap",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Payload plugin that generates chunked, lazily cached XML sitemaps with hook-driven invalidation and robots.txt helpers. Next.js + Vercel work out of the box; every layer is overridable for other stacks.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"LICENSE.md",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/what-works-global/payload-packages",
|
|
18
|
+
"directory": "packages/sitemap"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./next": {
|
|
27
|
+
"types": "./dist/exports/next.d.ts",
|
|
28
|
+
"import": "./dist/exports/next.js",
|
|
29
|
+
"default": "./dist/exports/next.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"next": ">=15.0.0 <17",
|
|
34
|
+
"payload": ">=3.30.0 <4"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"next": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"whatworks": {
|
|
42
|
+
"peerMatrix": {
|
|
43
|
+
"payload": {
|
|
44
|
+
"min": "3.30.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@arethetypeswrong/cli": "0.18.2",
|
|
50
|
+
"@payloadcms/db-sqlite": "3.84.1",
|
|
51
|
+
"@payloadcms/eslint-config": "3.28.0",
|
|
52
|
+
"@payloadcms/next": "3.84.1",
|
|
53
|
+
"@types/node": "22.19.9",
|
|
54
|
+
"@types/react": "19.2.9",
|
|
55
|
+
"@types/react-dom": "19.2.3",
|
|
56
|
+
"cross-env": "7.0.3",
|
|
57
|
+
"eslint": "9.23.0",
|
|
58
|
+
"eslint-config-next": "16.2.6",
|
|
59
|
+
"next": "16.2.6",
|
|
60
|
+
"payload": "3.84.1",
|
|
61
|
+
"publint": "0.3.12",
|
|
62
|
+
"react": "19.2.1",
|
|
63
|
+
"react-dom": "19.2.1",
|
|
64
|
+
"rimraf": "6.0.1",
|
|
65
|
+
"tsdown": "0.18.0",
|
|
66
|
+
"typescript": "5.7.3",
|
|
67
|
+
"vitest": "3.1.2",
|
|
68
|
+
"@whatworks/dev-fixture": "0.0.0"
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public",
|
|
72
|
+
"registry": "https://registry.npmjs.org/"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsdown",
|
|
76
|
+
"check:exports": "publint && node ../../scripts/check-package-exports.mjs",
|
|
77
|
+
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
78
|
+
"dev": "next dev dev",
|
|
79
|
+
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
80
|
+
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
81
|
+
"dev:payload": "cd dev && cross-env PAYLOAD_CONFIG_PATH=./payload.config.ts payload",
|
|
82
|
+
"generate:importmap": "pnpm dev:generate-importmap",
|
|
83
|
+
"generate:types": "pnpm dev:generate-types",
|
|
84
|
+
"lint": "eslint",
|
|
85
|
+
"lint:fix": "eslint --fix",
|
|
86
|
+
"test": "vitest run",
|
|
87
|
+
"typecheck": "tsc --noEmit",
|
|
88
|
+
"typecheck:dev": "tsc -p dev/tsconfig.json --noEmit"
|
|
89
|
+
}
|
|
90
|
+
}
|