/ docs-website / src / remark / versionedReferenceLinks.js
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;