Skip to content
5 changes: 5 additions & 0 deletions .changeset/eighty-falcons-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/sitemap": patch
Comment thread
florian-lefebvre marked this conversation as resolved.
Outdated
---

Added option to prefix sitemap (default is still sitemap-\*)
Comment thread
ktym4a marked this conversation as resolved.
Outdated
31 changes: 22 additions & 9 deletions packages/integrations/sitemap/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { AstroConfig, AstroIntegration } from 'astro';
import path from 'node:path';
import path, { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { EnumChangefreq, LinkItem as LinkItemBase, SitemapItemLoose } from 'sitemap';
import { simpleSitemapAndIndex } from 'sitemap';
import { SitemapAndIndexStream, SitemapStream, streamToPromise } from 'sitemap';
import { ZodError } from 'zod';

import { generateSitemap } from './generate-sitemap.js';
import { validateOptions } from './validate-options.js';
import { createWriteStream } from 'fs';
Comment thread
ktym4a marked this conversation as resolved.
Outdated
import { Readable } from 'node:stream';

export { EnumChangefreq as ChangeFreqEnum } from 'sitemap';
export type ChangeFreq = `${EnumChangefreq}`;
Expand All @@ -33,6 +35,8 @@ export type SitemapOptions =
lastmod?: Date;
priority?: number;

prefix?: string;

// called for each sitemap item just before to save them on disk, sync or async
serialize?(item: SitemapItem): SitemapItem | Promise<SitemapItem | undefined> | undefined;
}
Expand All @@ -44,7 +48,6 @@ function formatConfigErrorMessage(err: ZodError) {
}

const PKG_NAME = '@astrojs/sitemap';
const OUTFILE = 'sitemap-index.xml';
const STATUS_CODE_PAGES = new Set(['404', '500']);

function isStatusCodePage(pathname: string): boolean {
Expand Down Expand Up @@ -77,7 +80,8 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {

const opts = validateOptions(config.site, options);

const { filter, customPages, serialize, entryLimit } = opts;
const { filter, customPages, serialize, entryLimit, prefix = 'sitemap-' } = opts;
const OUTFILE = `${prefix}index.xml`;

let finalSiteUrl: URL;
if (config.site) {
Expand Down Expand Up @@ -166,13 +170,22 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
}
}
const destDir = fileURLToPath(dir);
await simpleSitemapAndIndex({
hostname: finalSiteUrl.href,
destinationDir: destDir,
sourceData: urlData,

const sms = new SitemapAndIndexStream({
limit: entryLimit,
gzip: false,
getSitemapStream: (i) => {
const sitemapStream = new SitemapStream({ hostname: finalSiteUrl.href });
const fileName = `${prefix}${i}.xml`;

const ws = sitemapStream.pipe(createWriteStream(resolve(destDir + fileName)));

return [new URL(fileName, finalSiteUrl.href).toString(), sitemapStream, ws];
},
});

sms.pipe(createWriteStream(resolve(destDir + OUTFILE)));
await streamToPromise(Readable.from(urlData).pipe(sms));
sms.end();
logger.info(`\`${OUTFILE}\` created at \`${path.relative(process.cwd(), destDir)}\``);
} catch (err) {
if (err instanceof ZodError) {
Expand Down
2 changes: 2 additions & 0 deletions packages/integrations/sitemap/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export const SitemapOptionsSchema = z
changefreq: z.nativeEnum(ChangeFreq).optional(),
lastmod: z.date().optional(),
priority: z.number().min(0).max(1).optional(),

prefix: z.string().optional(),
})
.strict()
.default(SITEMAP_CONFIG_DEFAULTS);
71 changes: 71 additions & 0 deletions packages/integrations/sitemap/test/prefix.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { loadFixture, readXML } from './test-utils.js';
import { expect } from 'chai';
import { sitemap } from './fixtures/static/deps.mjs';

describe('Prefix support', () => {
/** @type {import('./test-utils.js').Fixture} */
let fixture;
const prefix = 'test-';

describe('Static', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/static/',
integrations: [
sitemap(),
sitemap({
prefix,
}),
],
});
await fixture.build();
});

it('Content is same', async () => {
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
const prefixData = await readXML(fixture.readFile(`/${prefix}0.xml`));
expect(prefixData).to.deep.equal(data);
});

it('Index file load correct sitemap', async () => {
const data = await readXML(fixture.readFile('/sitemap-index.xml'));
const sitemapUrl = data.sitemapindex.sitemap[0].loc[0];
expect(sitemapUrl).to.equal('http://example.com/sitemap-0.xml');

const prefixData = await readXML(fixture.readFile(`/${prefix}index.xml`));
const prefixSitemapUrl = prefixData.sitemapindex.sitemap[0].loc[0];
expect(prefixSitemapUrl).to.equal(`http://example.com/${prefix}0.xml`);
});
});

describe('SSR', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/ssr/',
integrations: [
sitemap(),
sitemap({
prefix,
}),
],
});
await fixture.build();
});

it('Content is same', async () => {
const data = await readXML(fixture.readFile('/client/sitemap-0.xml'));
const prefixData = await readXML(fixture.readFile(`/client/${prefix}0.xml`));
expect(prefixData).to.deep.equal(data);
});

it('Index file load correct sitemap', async () => {
const data = await readXML(fixture.readFile('/client/sitemap-index.xml'));
const sitemapUrl = data.sitemapindex.sitemap[0].loc[0];
expect(sitemapUrl).to.equal('http://example.com/sitemap-0.xml');

const prefixData = await readXML(fixture.readFile(`/client/${prefix}index.xml`));
const prefixSitemapUrl = prefixData.sitemapindex.sitemap[0].loc[0];
expect(prefixSitemapUrl).to.equal(`http://example.com/${prefix}0.xml`);
});
});
});