BMAD-METHOD/website/src/rehype-base-paths.js

90 lines
3.1 KiB
JavaScript

/**
* Rehype plugin to prepend base path to absolute URLs
*
* Transforms:
* /img/foo.png → /BMAD-METHOD/img/foo.png (when base is /BMAD-METHOD/)
* /downloads/file.zip → /BMAD-METHOD/downloads/file.zip
* /llms.txt → /BMAD-METHOD/llms.txt
*
* Only affects absolute paths (/) - relative paths and external URLs are unchanged.
* Does NOT process .md links (those are handled by rehype-markdown-links).
*/
import { visit } from 'unist-util-visit';
/**
* Create a rehype plugin that prepends the base path to absolute URLs.
*
* @param {Object} options - Plugin options
* @param {string} options.base - The base path to prepend (e.g., '/BMAD-METHOD/')
* @returns {function} A HAST tree transformer
*/
export default function rehypeBasePaths(options = {}) {
const base = options.base || '/';
// Normalize base: ensure it ends with / and doesn't have double slashes
const normalizedBase = base === '/' ? '/' : base.endsWith('/') ? base : base + '/';
return (tree) => {
visit(tree, 'element', (node) => {
// Process img tags with src attribute
if (node.tagName === 'img' && node.properties?.src) {
const src = node.properties.src;
if (typeof src === 'string' && src.startsWith('/') && !src.startsWith('//')) {
// Don't transform if already has the base path
if (normalizedBase !== '/' && !src.startsWith(normalizedBase)) {
node.properties.src = normalizedBase + src.slice(1);
}
}
}
// Process iframe tags with src attribute
if (node.tagName === 'iframe' && node.properties?.src) {
const src = node.properties.src;
if (typeof src === 'string' && src.startsWith('/') && !src.startsWith('//')) {
// Don't transform if already has the base path
if (normalizedBase !== '/' && !src.startsWith(normalizedBase)) {
node.properties.src = normalizedBase + src.slice(1);
}
}
}
// Process anchor tags with href attribute
if (node.tagName === 'a' && node.properties?.href) {
const href = node.properties.href;
if (typeof href !== 'string') {
return;
}
// Only transform absolute paths starting with / (but not //)
if (!href.startsWith('/') || href.startsWith('//')) {
return;
}
// Skip if already has the base path
if (normalizedBase !== '/' && href.startsWith(normalizedBase)) {
return;
}
// Skip .md links - those are handled by rehype-markdown-links
// Extract path portion (before ? and #)
const firstDelimiter = Math.min(
href.indexOf('?') === -1 ? Infinity : href.indexOf('?'),
href.indexOf('#') === -1 ? Infinity : href.indexOf('#'),
);
const pathPortion = firstDelimiter === Infinity ? href : href.substring(0, firstDelimiter);
if (pathPortion.endsWith('.md')) {
return; // Let rehype-markdown-links handle this
}
// Prepend base path
node.properties.href = normalizedBase + href.slice(1);
}
});
};
}