UNPKG

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

Version:

Antora extensions and macros developed for Redpanda documentation.

296 lines (248 loc) 9.28 kB
/** * Scan rpk Go source code for hidden and deprecated commands * * Extracts commands marked with Hidden: true or Deprecated: "message" * to ensure they're included in documentation with proper deprecation notices. */ const fs = require('fs'); const path = require('path'); /** * Scan a Go file for cobra command definitions with deprecation/hidden status * @param {string} filePath - Path to Go file * @returns {Object|null} Command metadata if deprecated/hidden, null otherwise */ function scanGoFile(filePath) { const content = fs.readFileSync(filePath, 'utf8'); // Look for NewCommand function that returns *cobra.Command const newCommandMatch = content.match(/func NewCommand\([^)]*\)\s*\*cobra\.Command\s*\{/); if (!newCommandMatch) { return null; } // Extract the cobra.Command struct definition // Handle nested braces and multiline strings let cmdBlock = ''; let braceCount = 0; let startIndex = content.indexOf('&cobra.Command{'); if (startIndex === -1) { return null; } startIndex += '&cobra.Command{'.length; for (let i = startIndex; i < content.length; i++) { const char = content[i]; if (char === '{') braceCount++; if (char === '}') { if (braceCount === 0) break; braceCount--; } cmdBlock += char; } // Extract fields - handle multiline strings with backticks or quotes const useMatch = cmdBlock.match(/Use:\s*"([^"]+)"/); const shortMatch = cmdBlock.match(/Short:\s*"([^"]+)"/); const longMatch = cmdBlock.match(/Long:\s*`([^`]+)`|Long:\s*"([^"]+)"/s); const hiddenMatch = cmdBlock.match(/Hidden:\s*true/); const deprecatedMatch = cmdBlock.match(/Deprecated:\s*"([^"]+(?:[^"\\]|\\.)*)"/s); // Only return if hidden or deprecated if (!hiddenMatch && !deprecatedMatch) { return null; } const result = { use: useMatch ? useMatch[1] : null, short: shortMatch ? shortMatch[1] : null, long: longMatch ? (longMatch[1] || longMatch[2]) : null, hidden: !!hiddenMatch, deprecated: !!deprecatedMatch, deprecatedMessage: deprecatedMatch ? deprecatedMatch[1].replace(/\n\s*/g, ' ').trim() : null }; return result; } /** * Build command path from directory structure * @param {string} filePath - Path to Go file (relative to rpk root) * @param {string} rpkRoot - Path to rpk source root * @returns {string} Command path (e.g., "rpk redpanda admin") */ function buildCommandPath(filePath, rpkRoot) { const relativePath = path.relative(rpkRoot, filePath); const parts = relativePath.split(path.sep); // Remove filename, and filter out structural directories // Structure is either: // pkg/cli/<group>/<command>/file.go // cmd/rpk/<command>/file.go const commandParts = []; let inCommandPath = false; for (const part of parts) { if (part.endsWith('.go')) continue; // Start collecting after 'cli' or after 'rpk' in cmd if (part === 'cli' || (part === 'rpk' && parts.includes('cmd'))) { inCommandPath = true; continue; } if (inCommandPath) { commandParts.push(part); } } // Build path: rpk + directory hierarchy return 'rpk ' + commandParts.join(' '); } /** * Recursively scan directory for deprecated commands * @param {string} dir - Directory to scan * @param {string} rpkRoot - Path to rpk source root * @param {Array} results - Accumulator for results */ function scanDirectory(dir, rpkRoot, results = []) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { scanDirectory(fullPath, rpkRoot, results); } else if (entry.isFile() && entry.name.endsWith('.go')) { const metadata = scanGoFile(fullPath); if (metadata) { const commandPath = buildCommandPath(fullPath, rpkRoot); results.push({ commandPath, filePath: path.relative(rpkRoot, fullPath), ...metadata }); } } } return results; } /** * Extract deprecation info and convert to override format * @param {Object} cmdMetadata - Command metadata from source scan * @returns {Object} Override object for rpk-overrides.json */ function buildOverrideFromDeprecation(cmdMetadata) { const override = { deprecated: true, _note: `Hidden: ${cmdMetadata.hidden}, found by scanning Go source` }; if (cmdMetadata.deprecatedMessage) { // Parse the deprecation message to extract replacement info const message = cmdMetadata.deprecatedMessage; // Common patterns: // "use `rpk cluster` subcommands; see ..." // "use `rpk cluster brokers` instead" if (message.includes('use `') && message.includes('`')) { // Extract the replacement command const cmdMatch = message.match(/use `([^`]+)`/); if (cmdMatch) { const replacementCmd = cmdMatch[1]; // Try to build xref if (replacementCmd.startsWith('rpk ')) { const parts = replacementCmd.split(' '); const xrefPath = parts.slice(1).join('/'); const filename = parts.slice(1).join('-'); override.replacement = `Use xref:reference:rpk/rpk-${xrefPath}/rpk-${filename}.adoc[\`${replacementCmd}\`] instead.`; } else { override.replacement = `Use \`${replacementCmd}\` instead.`; } } // Add full deprecation message if it has additional context if (message.length > 50 || message.includes(';')) { override.deprecatedMessage = message; } } else { override.deprecatedMessage = message; } } return override; } /** * Scan rpk source for deprecated/hidden commands * @param {string} sourcePath - Path to rpk source (src/go/rpk) * @returns {Object} Map of command paths to override metadata */ function scanDeprecatedCommands(sourcePath) { console.log('Scanning Go source for deprecated/hidden commands...'); // Scan both cmd/rpk (old structure) and pkg/cli (current structure) const dirsToScan = [ path.join(sourcePath, 'cmd', 'rpk'), path.join(sourcePath, 'pkg', 'cli') ]; const results = []; for (const dir of dirsToScan) { if (fs.existsSync(dir)) { console.log(`Scanning ${dir}...`); scanDirectory(dir, sourcePath, results); } } console.log(`Found ${results.length} deprecated/hidden command(s):`); const overrides = {}; for (const cmd of results) { console.log(` - ${cmd.commandPath}`); if (cmd.deprecatedMessage) { console.log(` Deprecated: ${cmd.deprecatedMessage.substring(0, 80)}${cmd.deprecatedMessage.length > 80 ? '...' : ''}`); } if (cmd.hidden) { console.log(` Hidden: true`); } overrides[cmd.commandPath] = buildOverrideFromDeprecation(cmd); } return overrides; } /** * Merge deprecated commands into existing tree * @param {Object} tree - Command tree from rpk --print-tree * @param {Object} deprecatedOverrides - Overrides from source scanning * @param {Object} existingOverrides - Existing overrides.json data * @returns {Object} Tree with deprecated commands added */ function mergeDeprecatedCommands(tree, deprecatedOverrides, existingOverrides) { // For now, just return the overrides to be merged into overrides.json // The actual command data will come from the old docs or manual creation console.log('\nMerging deprecated commands into overrides...'); if (!existingOverrides.commands) { existingOverrides.commands = {}; } let added = 0; let updated = 0; for (const [cmdPath, metadata] of Object.entries(deprecatedOverrides)) { if (!existingOverrides.commands[cmdPath]) { existingOverrides.commands[cmdPath] = metadata; added++; } else { // Merge, keeping existing content but adding deprecation metadata existingOverrides.commands[cmdPath] = { ...existingOverrides.commands[cmdPath], deprecated: metadata.deprecated, replacement: metadata.replacement || existingOverrides.commands[cmdPath].replacement, deprecatedMessage: metadata.deprecatedMessage || existingOverrides.commands[cmdPath].deprecatedMessage, _note: metadata._note }; updated++; } } console.log(`Added ${added} new deprecated commands`); console.log(`Updated ${updated} existing commands with deprecation info`); return existingOverrides; } module.exports = { scanDeprecatedCommands, mergeDeprecatedCommands, scanGoFile, buildCommandPath }; // CLI usage if (require.main === module) { const sourcePath = process.argv[2]; const overridesPath = process.argv[3]; if (!sourcePath) { console.error('Usage: node scan-deprecated-commands.js <rpk-source-path> [overrides-json-path]'); process.exit(1); } const deprecated = scanDeprecatedCommands(sourcePath); if (overridesPath) { const existingOverrides = JSON.parse(fs.readFileSync(overridesPath, 'utf8')); const updated = mergeDeprecatedCommands(null, deprecated, existingOverrides); fs.writeFileSync(overridesPath, JSON.stringify(updated, null, 2)); console.log(`\nUpdated overrides file: ${overridesPath}`); } else { console.log('\nDeprecated commands (JSON):'); console.log(JSON.stringify(deprecated, null, 2)); } }