versionedReferenceLinks.js
1 /** 2 * Remark plugin to automatically add version context to cross-doc-instance links 3 * 4 * Transforms: 5 * - /reference/slug -> /reference/{version}/slug (from docs to reference) 6 * - /docs/slug -> /docs/{version}/slug (from reference to docs) 7 * Where {version} is automatically determined from the current doc's version context 8 * 9 * Note: The latest version does NOT get a version prefix in the URL 10 */ 11 12 const path = require('path'); 13 const fs = require('fs'); 14 15 // Cache the latest version at module level, but allow it to be read lazily 16 let latestVersion = null; 17 18 // Dynamically read the latest stable version from versions.json 19 function getLatestVersion() { 20 if (latestVersion !== null) { 21 return latestVersion; 22 } 23 24 try { 25 const versionsPath = path.join(__dirname, '../../versions.json'); 26 const versions = JSON.parse(fs.readFileSync(versionsPath, 'utf8')); 27 // Filter out unstable versions and get the latest stable version 28 const stableVersions = versions.filter(version => !version.includes('-unstable')); 29 latestVersion = stableVersions[0] 30 return latestVersion; 31 } catch (error) { 32 console.warn('[versionedReferenceLinks] Could not read versions.json:', error.message); 33 return null; 34 } 35 } 36 37 function versionedReferenceLinks() { 38 // Get the latest version once when the plugin is initialized 39 const latestVersion = getLatestVersion(); 40 41 return (tree, file) => { 42 // Read the latest version inside the processor function 43 const currentLatestVersion = getLatestVersion(); 44 45 // Try multiple ways to get the version, in order of reliability: 46 // 1. versionMetadata.versionName (Docusaurus 3.x) 47 // 2. version from file.data (older Docusaurus) 48 // 3. Extract from file path 49 let version = 50 file.data?.versionMetadata?.versionName || 51 file.data?.version || 52 null; 53 54 // If not in metadata, extract from file path 55 if (!version) { 56 // file.history[0] or file.path contains the file path 57 const filePath = file.history?.[0] || file.path || ''; 58 59 // Match patterns like: versioned_docs/version-2.19/ or reference_versioned_docs/version-2.19/ 60 // Handle both relative and absolute paths, and both / and \ separators 61 const versionMatch = filePath.match(/(?:reference_)?versioned_docs[/\\]version-([^/\\]+)[/\\]/); 62 63 if (versionMatch) { 64 version = versionMatch[1]; // e.g., "2.19" 65 } else if ( 66 // Check if file is in the base docs/ or reference/ folder (not versioned) 67 /[/\\]docs[/\\]/.test(filePath) && !/versioned_docs/.test(filePath) || 68 /[/\\]reference[/\\]/.test(filePath) && !/reference_versioned_docs/.test(filePath) 69 ) { 70 // Current/unreleased version (in the main docs/reference folder, not versioned) 71 version = 'current'; 72 } else { 73 // Fallback: assume current version 74 version = 'current'; 75 } 76 } 77 78 // Manually visit all nodes in the tree 79 const visit = (node, callback) => { 80 callback(node); 81 if (node.children) { 82 node.children.forEach(child => visit(child, callback)); 83 } 84 }; 85 86 visit(tree, (node) => { 87 // Check if this is a link node 88 if (node.type === 'link' || node.type === 'linkReference') { 89 if (node.url && node.url.startsWith('/reference/')) { 90 // Check if it already has a version 91 const hasVersion = /^\/reference\/(next|v?\d+\.\d+)\//.test(node.url); 92 93 if (!hasVersion) { 94 // Latest version: no version prefix needed (served at /reference/) 95 // Current/next version: add /next/ 96 // Other versions: add version number 97 if (version === currentLatestVersion) { 98 // Latest version - no changes needed, keep as /reference/... 99 return; 100 } else if (version === 'current') { 101 // Current version - add /next/ 102 node.url = node.url.replace('/reference/', '/reference/next/'); 103 } else { 104 // Older version - add version number 105 node.url = node.url.replace('/reference/', `/reference/${version}/`); 106 } 107 } 108 } else if (node.url && node.url.startsWith('/docs/')) { 109 // Handle links from reference pages to docs pages 110 // Check if it already has a version 111 const hasVersion = /^\/docs\/(next|v?\d+\.\d+)\//.test(node.url); 112 113 if (!hasVersion) { 114 // Latest version: no version prefix needed (served at /docs/) 115 // Current/next version: add /next/ 116 // Other versions: add version number 117 if (version === currentLatestVersion) { 118 // Latest version - no changes needed, keep as /docs/... 119 return; 120 } else if (version === 'current') { 121 // Current version - add /next/ 122 node.url = node.url.replace('/docs/', '/docs/next/'); 123 } else { 124 // Older version - add version number 125 node.url = node.url.replace('/docs/', `/docs/${version}/`); 126 } 127 } 128 } 129 } 130 }); 131 }; 132 } 133 134 module.exports = versionedReferenceLinks;