Skip to content

Commit 1d4f6b9

Browse files
authored
feat(utils): add sitemapFilter option to parseSitemap (#3557)
1 parent b23319b commit 1d4f6b9

2 files changed

Lines changed: 40 additions & 0 deletions

File tree

packages/utils/src/internals/sitemap.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,13 @@ export interface ParseSitemapOptions {
194194
* @default true
195195
*/
196196
reportNetworkErrors?: boolean;
197+
/**
198+
* Optional filter for nested sitemap URLs discovered in sitemap index files.
199+
* Called with the URL of each child sitemap before it is fetched.
200+
* Return `true` to include the sitemap, `false` to skip it.
201+
* If not provided, all nested sitemaps are followed.
202+
*/
203+
nestedSitemapFilter?: (sitemapUrl: string) => boolean;
197204
}
198205

199206
export async function* parseSitemap<T extends ParseSitemapOptions>(
@@ -209,6 +216,7 @@ export async function* parseSitemap<T extends ParseSitemapOptions>(
209216
sitemapRetries = 3,
210217
networkTimeouts,
211218
reportNetworkErrors = true,
219+
nestedSitemapFilter,
212220
} = options ?? {};
213221

214222
const sources = [...initialSources];
@@ -340,6 +348,11 @@ export async function* parseSitemap<T extends ParseSitemapOptions>(
340348

341349
for await (const item of items) {
342350
if (item.type === 'sitemapUrl' && !visitedSitemapUrls.has(item.url)) {
351+
if (nestedSitemapFilter && !nestedSitemapFilter(item.url)) {
352+
log.debug(`Skipping sitemap ${item.url} due to nestedSitemapFilter.`);
353+
continue;
354+
}
355+
343356
sources.push({ type: 'url', url: item.url, depth: (source.depth ?? 0) + 1 });
344357
if (emitNestedSitemaps) {
345358
yield { loc: item.url, originSitemapUrl: null } as any;

packages/utils/test/sitemap.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,33 @@ describe('Sitemap', () => {
294294
);
295295
});
296296

297+
it('respects nestedSitemapFilter when following sitemap indexes', async () => {
298+
const items: SitemapUrl[] = [];
299+
300+
for await (const item of parseSitemap(
301+
[{ type: 'url', url: 'http://not-exists.com/sitemap_parent.xml' }],
302+
undefined,
303+
{
304+
nestedSitemapFilter: (url) => !url.includes('sitemap_child_2'),
305+
},
306+
)) {
307+
items.push(item);
308+
}
309+
310+
expect(items).toHaveLength(5);
311+
expect(items.every((item) => item.originSitemapUrl === 'http://not-exists.com/sitemap_child.xml')).toBe(true);
312+
});
313+
314+
it('follows all nested sitemaps when nestedSitemapFilter is not provided', async () => {
315+
const items: SitemapUrl[] = [];
316+
317+
for await (const item of parseSitemap([{ type: 'url', url: 'http://not-exists.com/sitemap_parent.xml' }])) {
318+
items.push(item);
319+
}
320+
321+
expect(items).toHaveLength(10);
322+
});
323+
297324
it('does not break on invalid xml', async () => {
298325
const sitemap = await Sitemap.load('http://not-exists.com/not_actual_xml.xml');
299326
expect(sitemap.urls).toEqual([]);

0 commit comments

Comments
 (0)