@raven-js/glean
Version:
Glean documentation gold from your codebase - JSDoc parsing, validation, and beautiful doc generation
106 lines (94 loc) • 3.64 kB
JavaScript
/**
* @author Anonyfox <max@anonyfox.com>
* @license MIT
* @see {@link https://github.com/Anonyfox/ravenjs}
* @see {@link https://ravenjs.dev}
* @see {@link https://anonyfox.com}
*/
/**
* @file Sitemap data extraction for documentation generator
*
* Generates XML sitemap data from Package structure for SEO optimization.
* Includes all documentation pages with proper priority and change frequency.
*/
/**
* Extract sitemap data from package instance for SEO optimization with proper priorities.
*
* Generates XML sitemap URLs for homepage, module pages, and entity pages with
* appropriate change frequencies and priority rankings.
*
* @param {{name: string, modules: Array<{importPath: string, isDefault: boolean, publicEntities: Array<{name: string}>}>}} packageInstance - Package instance with modules and entities
* @param {string} baseUrl - Base URL for the documentation site
* @param {Object} [options] - Additional options
* @param {Object} [options.urlBuilder] - URL builder for base path handling
* @returns {{urls: Array<{loc: string, lastmod: string, changefreq: string, priority: string}>, totalUrls: number, generatedAt: string}} Sitemap data with URLs, priorities, and metadata
*
* @example
* // Generate sitemap for documentation site
* const sitemap = extractSitemapData(packageInstance, 'https://docs.mypackage.com');
* console.log(sitemap.totalUrls, sitemap.urls[0].priority);
*/
export function extractSitemapData(
packageInstance,
baseUrl = "https://docs.example.com",
options = {},
) {
const { urlBuilder } = options;
const urls = [];
// Package overview page - highest priority
const homeUrl = urlBuilder ? /** @type {any} */ (urlBuilder).homeUrl() : "/";
urls.push({
loc: `${baseUrl}${homeUrl}`,
lastmod: new Date().toISOString().split("T")[0],
changefreq: "weekly",
priority: "1.0",
});
// Module directory page
const modulesUrl = urlBuilder
? /** @type {any} */ (urlBuilder).modulesUrl()
: "/modules/";
urls.push({
loc: `${baseUrl}${modulesUrl}`,
lastmod: new Date().toISOString().split("T")[0],
changefreq: "weekly",
priority: "0.9",
});
// Module overview pages
for (const module of /** @type {any} */ (packageInstance).modules) {
// Extract module name from import path, following the same logic as module overview
// This ensures consistency with how modules are accessed via URLs
let moduleName = module.importPath.split("/").pop() || "index";
// Handle main module (package name) - use package name for consistency with integration tests
if (moduleName === packageInstance.name) {
moduleName = packageInstance.name;
}
// Always use the extracted module name for URLs, following the same logic as module overview
// Integration tests show that modules are accessible at /modules/utils/, /modules/api/, etc.
const moduleUrl = urlBuilder
? /** @type {any} */ (urlBuilder).moduleUrl(moduleName)
: `/modules/${moduleName}/`;
urls.push({
loc: `${baseUrl}${moduleUrl}`,
lastmod: new Date().toISOString().split("T")[0],
changefreq: "monthly",
priority: module.isDefault ? "0.8" : "0.7",
});
// Entity pages within modules
for (const entity of module.publicEntities) {
const entityUrl = urlBuilder
? /** @type {any} */ (urlBuilder).entityUrl(moduleName, entity.name)
: `/modules/${moduleName}/${encodeURIComponent(entity.name)}/`;
urls.push({
loc: `${baseUrl}${entityUrl}`,
lastmod: new Date().toISOString().split("T")[0],
changefreq: "monthly",
priority: "0.6",
});
}
}
return {
urls,
totalUrls: urls.length,
generatedAt: new Date().toISOString(),
};
}