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-27 18:02:00 +00:00
serialize ? ( item : SitemapItem ) : SitemapItem | Promise < SitemapItem | undefined > | undefined ;
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 ) {
2022-06-23 15:31:54 +00:00
// offer suggestion for SSR users
if ( typeof config . adapter !== 'undefined' ) {
2022-06-23 15:33:20 +00:00
logger . warn (
` No pages found! We can only detect sitemap routes for "static" projects. Since you are using an SSR adapter, we recommend manually listing your sitemap routes using the "customPages" integration option. \ n \ nExample: \` sitemap({ customPages: ['https://example.com/route'] }) \` `
) ;
2022-06-23 15:31:54 +00:00
} else {
logger . warn ( ` No pages found! \ n \` ${ OUTFILE } \` not created. ` ) ;
}
2022-06-16 19:06:48 +00:00
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 ) ) ;
2022-06-27 18:02:00 +00:00
if ( serialized ) {
serializedUrls . push ( serialized ) ;
}
2022-06-16 19:06:48 +00:00
}
2022-06-27 18:02:00 +00:00
if ( serializedUrls . length === 0 ) {
logger . warn ( 'No pages found!' ) ;
return ;
}
2022-06-16 19:06:48 +00:00
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 ;