UNPKG

@redpanda-data/docs-extensions-and-macros

Version:

Antora extensions and macros developed for Redpanda documentation.

154 lines (137 loc) 4.92 kB
/** * MCP Tools - Antora Structure * * OPTIMIZATION: This tool uses caching and should use Haiku model. * - Caches structure for 1 hour (rarely changes) * - No complex reasoning required (just directory scanning) * - Recommended model: haiku */ const fs = require('fs'); const path = require('path'); const yaml = require('js-yaml'); const { MAX_RECURSION_DEPTH, DEFAULT_SKIP_DIRS, PLAYBOOK_NAMES } = require('./utils'); const cache = require('./cache'); /** * Get Antora structure information for the current repository * @param {string|{root: string, detected: boolean, type: string|null}} repoRoot - Repository root path or info object * @param {string[]} [skipDirs=DEFAULT_SKIP_DIRS] - Directories to skip during search * @param {Object} [options] - Additional options * @param {boolean} [options.skipCache] - Skip cache and force fresh scan * @returns {Object} Antora structure information */ function getAntoraStructure(repoRoot, skipDirs = DEFAULT_SKIP_DIRS, options = {}) { const rootPath = typeof repoRoot === 'string' ? repoRoot : repoRoot.root; const repoInfo = typeof repoRoot === 'object' ? repoRoot : { root: repoRoot, detected: true, type: null }; // Check cache first (1 hour TTL) const cacheKey = `antora-structure:${rootPath}`; if (!options.skipCache) { const cached = cache.get(cacheKey); if (cached) { return { ...cached, _cached: true, _cacheHit: true }; } } const playbookPath = PLAYBOOK_NAMES .map(name => path.join(rootPath, name)) .find(p => fs.existsSync(p)); let playbookContent = null; if (playbookPath) { try { playbookContent = yaml.load(fs.readFileSync(playbookPath, 'utf8')); } catch (err) { console.error(`Warning: Failed to parse playbook at ${playbookPath}: ${err.message}`); } } const antoraYmls = []; const findAntoraYmls = (dir, depth = 0, visited = new Set()) => { if (depth > MAX_RECURSION_DEPTH || !fs.existsSync(dir)) return; try { const realPath = fs.realpathSync(dir); if (visited.has(realPath)) return; visited.add(realPath); const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (skipDirs.includes(entry.name)) continue; const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { findAntoraYmls(fullPath, depth + 1, visited); } else if (entry.name === 'antora.yml') { antoraYmls.push(fullPath); } } } catch (err) { console.error(`Warning: Failed to read directory ${dir}: ${err.message}`); } }; findAntoraYmls(rootPath); const components = antoraYmls.map(ymlPath => { try { const content = yaml.load(fs.readFileSync(ymlPath, 'utf8')); const componentDir = path.dirname(ymlPath); const modulesDir = path.join(componentDir, 'modules'); const modules = fs.existsSync(modulesDir) ? fs.readdirSync(modulesDir).filter(m => { const stat = fs.statSync(path.join(modulesDir, m)); return stat.isDirectory(); }) : []; return { name: content.name, version: content.version, title: content.title, path: componentDir, modules: modules.map(moduleName => { const modulePath = path.join(modulesDir, moduleName); return { name: moduleName, path: modulePath, pages: fs.existsSync(path.join(modulePath, 'pages')), partials: fs.existsSync(path.join(modulePath, 'partials')), examples: fs.existsSync(path.join(modulePath, 'examples')), attachments: fs.existsSync(path.join(modulePath, 'attachments')), images: fs.existsSync(path.join(modulePath, 'images')), }; }), }; } catch (err) { return { error: `Failed to parse ${ymlPath}: ${err.message}` }; } }); const result = { repoRoot: rootPath, repoInfo, playbook: playbookContent, playbookPath, components, hasDocTools: (() => { // Check if we're in the source repo (docs-extensions-and-macros) if (fs.existsSync(path.join(rootPath, 'bin', 'doc-tools.js'))) { return true; } // Check if doc-tools is available via npx or as installed dependency try { const { execSync } = require('child_process'); execSync('npx doc-tools --version', { stdio: 'ignore', timeout: 5000, cwd: rootPath }); return true; } catch { return false; } })(), // Metadata for cost optimization _modelRecommendation: 'haiku', _reasoning: 'Simple directory scanning, no complex reasoning required' }; // Cache for 1 hour cache.set(cacheKey, result, 60 * 60 * 1000); return result; } module.exports = { getAntoraStructure };