Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/generators/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import llmsTxt from './llms-txt/index.mjs';
import manPage from './man-page/index.mjs';
import metadata from './metadata/index.mjs';
import oramaDb from './orama-db/index.mjs';
import sitemap from './sitemap/index.mjs';
import web from './web/index.mjs';

export const publicGenerators = {
Expand All @@ -27,6 +28,7 @@ export const publicGenerators = {
'api-links': apiLinks,
'orama-db': oramaDb,
'llms-txt': llmsTxt,
sitemap,
web,
};

Expand Down
81 changes: 81 additions & 0 deletions src/generators/sitemap/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';

import dedent from 'dedent';

import { BASE_URL } from '../../constants.mjs';

/**
* This generator generates a sitemap.xml file for search engine optimization
*
* @typedef {Array<ApiDocMetadataEntry>} Input
*
* @type {GeneratorMetadata<Input, string>}
*/
export default {
name: 'sitemap',

version: '1.0.0',

description: 'Generates a sitemap.xml file for search engine optimization',

dependsOn: 'metadata',

/**
* Generates a sitemap.xml file
*
* @param {Input} entries
* @param {Partial<GeneratorOptions>} options
* @returns {Promise<string>}
*/
async generate(entries, { output }) {
const lastmod = new Date().toISOString().split('T')[0];

const apiPages = entries
.filter(entry => entry.heading.depth === 1)
.map(entry => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extract this anonymous function to a dedicated function.

const path = entry.api_doc_source.replace(/^doc\//, '/docs/latest/');
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URLs in the sitemap should use .html extensions instead of .md extensions. The api_doc_source field contains paths like "doc/api/test.md", but the actual published documentation uses HTML files. Other generators in the codebase (web, legacy-html, orama-db, jsx-ast) all generate URLs with .html extensions. The path transformation should be: entry.api_doc_source.replace(/^doc//, '/docs/latest/').replace(/.md$/, '.html')

Suggested change
const path = entry.api_doc_source.replace(/^doc\//, '/docs/latest/');
const path = entry.api_doc_source
.replace(/^doc\//, '/docs/latest/')
.replace(/\.md$/, '.html');

Copilot uses AI. Check for mistakes.
const url = new URL(path, BASE_URL).href;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const url = new URL(path, BASE_URL).href;
const { href: url } = new URL(path, BASE_URL);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer object deconstruction.


return {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: all of this could be inlined (all in one line object creation instead of multi line)

loc: url,
lastmod,
changefreq: 'weekly',
priority: '0.8',
};
});

apiPages.push({
loc: new URL('/docs/latest/api/', BASE_URL).href,
lastmod,
changefreq: 'daily',
priority: '1.0',
});

const template = await readFile(
join(import.meta.dirname, 'template.xml'),
'utf-8'
);

const urlset = apiPages
.map(
page => dedent`
<url>
Copy link
Member

@ovflowd ovflowd Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use template files for this and then do simple key->value substitution. Or use proper rss/feed libraries OR xml libraries.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use proper rss/feed libraries OR xml libraries.

You can probably use hast (but I'm also fine with it this way), seeing as the majority of it is a template

<loc>${page.loc}</loc>
<lastmod>${page.lastmod}</lastmod>
<changefreq>${page.changefreq}</changefreq>
<priority>${page.priority}</priority>
</url>
`
)
.join('\n');

const sitemap = template.replace('__URLSET__', urlset);

if (output) {
await writeFile(join(output, 'sitemap.xml'), sitemap, 'utf-8');
}

return sitemap;
},
};
4 changes: 4 additions & 0 deletions src/generators/sitemap/template.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
__URLSET__
</urlset>
Loading