2022-03-18 22:35:45 +00:00
|
|
|
import type { AstroConfig, AstroIntegration } from 'astro';
|
2022-06-20 19:31:39 +00:00
|
|
|
import {
|
|
|
|
EnumChangefreq,
|
|
|
|
LinkItem as LinkItemBase,
|
|
|
|
simpleSitemapAndIndex,
|
|
|
|
SitemapItemLoose,
|
|
|
|
} from 'sitemap';
|
2022-06-16 19:08:44 +00:00
|
|
|
import { fileURLToPath } from 'url';
|
2022-06-16 19:06:48 +00:00
|
|
|
import { ZodError } from 'zod';
|
2022-03-18 22:35:45 +00:00
|
|
|
|
2022-06-21 15:29:18 +00:00
|
|
|
import { generateSitemap } from './generate-sitemap.js';
|
|
|
|
import { Logger } from './utils/logger.js';
|
|
|
|
import { validateOptions } from './validate-options.js';
|
2022-06-16 19:06:48 +00:00
|
|
|
|
2022-06-20 19:29:53 +00:00
|
|
|
export type ChangeFreq = EnumChangefreq;
|
2022-06-16 19:06:48 +00:00
|
|
|
export type SitemapItem = Pick<
|
|
|
|
SitemapItemLoose,
|
|
|
|
'url' | 'lastmod' | 'changefreq' | 'priority' | 'links'
|
|
|
|
>;
|
|
|
|
export type LinkItem = LinkItemBase;
|
|
|
|
|
|
|
|
export type SitemapOptions =
|
2022-04-02 18:29:59 +00:00
|
|
|
| {
|
2022-06-10 18:16:08 +00:00
|
|
|
filter?(page: string): boolean;
|
2022-06-16 19:06:48 +00:00
|
|
|
customPages?: string[];
|
2022-04-02 18:29:59 +00:00
|
|
|
canonicalURL?: string;
|
2022-06-16 19:06:48 +00:00
|
|
|
|
|
|
|
i18n?: {
|
|
|
|
defaultLocale: string;
|
|
|
|
locales: Record<string, string>;
|
|
|
|
};
|
|
|
|
// number of entries per sitemap file
|
|
|
|
entryLimit?: number;
|
|
|
|
|
|
|
|
// sitemap specific
|
|
|
|
changefreq?: ChangeFreq;
|
|
|
|
lastmod?: Date;
|
|
|
|
priority?: number;
|
|
|
|
|
|
|
|
// called for each sitemap item just before to save them on disk, sync or async
|
2022-06-20 19:29:53 +00:00
|
|
|
serialize?(item: SitemapItem): SitemapItem | Promise<SitemapItem>;
|
2022-04-02 18:29:59 +00:00
|
|
|
}
|
|
|
|
| undefined;
|
|
|
|
|
2022-06-16 19:06:48 +00:00
|
|
|
function formatConfigErrorMessage(err: ZodError) {
|
|
|
|
const errorList = err.issues.map((issue) => ` ${issue.path.join('.')} ${issue.message + '.'}`);
|
|
|
|
return errorList.join('\n');
|
2022-03-18 22:35:45 +00:00
|
|
|
}
|
|
|
|
|
2022-06-16 19:06:48 +00:00
|
|
|
const PKG_NAME = '@astrojs/sitemap';
|
|
|
|
const OUTFILE = 'sitemap-index.xml';
|
|
|
|
|
|
|
|
const createPlugin = (options?: SitemapOptions): AstroIntegration => {
|
2022-03-18 22:35:45 +00:00
|
|
|
let config: AstroConfig;
|
|
|
|
return {
|
2022-06-16 19:06:48 +00:00
|
|
|
name: PKG_NAME,
|
|
|
|
|
2022-03-18 22:35:45 +00:00
|
|
|
hooks: {
|
2022-06-16 19:06:48 +00:00
|
|
|
'astro:config:done': async ({ config: cfg }) => {
|
|
|
|
config = cfg;
|
2022-03-18 22:35:45 +00:00
|
|
|
},
|
2022-06-16 19:06:48 +00:00
|
|
|
|
|
|
|
'astro:build:done': async ({ dir, pages }) => {
|
|
|
|
const logger = new Logger(PKG_NAME);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const opts = validateOptions(config.site, options);
|
|
|
|
|
|
|
|
const { filter, customPages, canonicalURL, serialize, entryLimit } = opts;
|
|
|
|
|
|
|
|
let finalSiteUrl: URL;
|
|
|
|
if (canonicalURL) {
|
|
|
|
finalSiteUrl = new URL(canonicalURL);
|
|
|
|
if (!finalSiteUrl.pathname.endsWith('/')) {
|
|
|
|
finalSiteUrl.pathname += '/'; // normalizes the final url since it's provided by user
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// `validateOptions` forces to provide `canonicalURL` or `config.site` at least.
|
|
|
|
// So step to check on empty values of `canonicalURL` and `config.site` is dropped.
|
|
|
|
finalSiteUrl = new URL(config.base, config.site);
|
|
|
|
}
|
|
|
|
|
|
|
|
let pageUrls = pages.map((p) => {
|
|
|
|
const path = finalSiteUrl.pathname + p.pathname;
|
|
|
|
return new URL(path, finalSiteUrl).href;
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (filter) {
|
|
|
|
pageUrls = pageUrls.filter(filter);
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
logger.error(`Error filtering pages\n${(err as any).toString()}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (customPages) {
|
|
|
|
pageUrls = [...pageUrls, ...customPages];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pageUrls.length === 0) {
|
|
|
|
logger.warn(`No data for sitemap.\n\`${OUTFILE}\` is not created.`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let urlData = generateSitemap(pageUrls, finalSiteUrl.href, opts);
|
|
|
|
|
|
|
|
if (serialize) {
|
|
|
|
try {
|
2022-06-20 19:29:53 +00:00
|
|
|
const serializedUrls: SitemapItem[] = [];
|
2022-06-16 19:06:48 +00:00
|
|
|
for (const item of urlData) {
|
|
|
|
const serialized = await Promise.resolve(serialize(item));
|
|
|
|
serializedUrls.push(serialized);
|
|
|
|
}
|
|
|
|
urlData = serializedUrls;
|
|
|
|
} catch (err) {
|
|
|
|
logger.error(`Error serializing pages\n${(err as any).toString()}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await simpleSitemapAndIndex({
|
|
|
|
hostname: finalSiteUrl.href,
|
|
|
|
destinationDir: fileURLToPath(dir),
|
|
|
|
sourceData: urlData,
|
|
|
|
limit: entryLimit,
|
|
|
|
gzip: false,
|
|
|
|
});
|
|
|
|
logger.success(`\`${OUTFILE}\` is created.`);
|
|
|
|
} catch (err) {
|
|
|
|
if (err instanceof ZodError) {
|
|
|
|
logger.warn(formatConfigErrorMessage(err));
|
|
|
|
} else {
|
|
|
|
throw err;
|
|
|
|
}
|
2022-05-12 20:19:58 +00:00
|
|
|
}
|
2022-03-18 22:35:45 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
2022-06-16 19:06:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export default createPlugin;
|