UNPKG

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

Version:

Antora extensions and macros developed for Redpanda documentation.

1,230 lines (1,131 loc) 68.7 kB
#!/usr/bin/env node 'use strict' // Load environment variables from .env file if it exists require('dotenv').config() const { spawnSync } = require('child_process') const os = require('os') const { Command, Option } = require('commander') const path = require('path') const fs = require('fs') // Import extracted utility modules const { findRepoRoot, fail, commonOptions } = require('../cli-utils/doc-tools-utils') const { requireTool, requireCmd, verifyCrdDependencies, verifyHelmDependencies, verifyPropertyDependencies, verifyMetricsDependencies } = require('../cli-utils/dependencies') const { runClusterDocs, diffDirs, generatePropertyComparisonReport, updatePropertyOverridesWithVersion, cleanupOldDiffs } = require('../cli-utils/diff-utils') // Import other utilities const { determineDocsBranch } = require('../cli-utils/self-managed-docs-branch.js') const fetchFromGithub = require('../tools/fetch-from-github.js') const { urlToXref } = require('../cli-utils/convert-doc-links.js') const { getAntoraValue, setAntoraValue } = require('../cli-utils/antora-utils') // -------------------------------------------------------------------- // Main CLI Definition // -------------------------------------------------------------------- const programCli = new Command() const pkg = require('../package.json') programCli .name('doc-tools') .description('Redpanda Document Automation CLI') .version(pkg.version) // ==================================================================== // TOP-LEVEL COMMANDS // ==================================================================== /** * install-test-dependencies * * @description * Installs all packages and dependencies required for documentation testing workflows. * This includes Redpanda Docker images, Python virtual environments for property extraction, * and other test dependencies. * * @why * Setting up a documentation environment requires multiple dependencies across different * package managers (npm, pip, Docker). This command automates the entire setup process. * * @example * # Set up a new documentation environment * npx doc-tools install-test-dependencies * * # Use in CI/CD before running doc tests * - run: npx doc-tools install-test-dependencies * - run: npm test * * @requirements * - Node.js and npm * - Python 3.9 or higher * - Docker (for some dependencies) */ programCli .command('install-test-dependencies') .description('Install packages for doc test workflows') .action(() => { const scriptPath = path.join(__dirname, '../cli-utils/install-test-dependencies.sh') const result = spawnSync(scriptPath, { stdio: 'inherit', shell: true }) process.exit(result.status) }) /** * get-redpanda-version * * @description * Fetches the latest Redpanda version from GitHub releases. Can retrieve either stable * releases or beta/RC versions. Returns the version in format "v25.3.1" which can be * used directly with other doc-tools commands. * * @why * Documentation must reference the correct current version. This command ensures version * numbers are accurate and can be used in CI/CD pipelines or before generating * version-specific documentation. The version is fetched from GitHub releases, which is * the source of truth for Redpanda releases. * * @example * # Get latest stable version * npx doc-tools get-redpanda-version * # Output: v25.3.1 * * # Get latest beta/RC version * npx doc-tools get-redpanda-version --beta * # Output: v26.1.1-rc1 * * # Auto-detect from antora.yml prerelease flag * cd docs-site * npx doc-tools get-redpanda-version --from-antora * * # Use in CI/CD or scripts * VERSION=$(npx doc-tools get-redpanda-version) * npx doc-tools generate property-docs --tag $VERSION * * @requirements * - Internet connection to access GitHub API * - GitHub API rate limits apply (60 requests/hour unauthenticated) */ programCli .command('get-redpanda-version') .description('Print the latest Redpanda version') .option('--beta', 'Return the latest RC (beta) version if available') .option('--from-antora', 'Read prerelease flag from local antora.yml') .action(async (options) => { try { await require('../tools/get-redpanda-version.js')(options) } catch (err) { console.error(`Error: ${err.message}`) process.exit(1) } }) /** * get-console-version * * @description * Fetches the latest Redpanda Console version from GitHub releases. Can retrieve either * stable releases or beta versions. Returns the version in format "v2.7.2" which can be * used for documentation references and Docker image tags. * * @why * Console is released separately from Redpanda core. This command keeps Console * documentation in sync with releases and provides the correct version for Docker * Compose files and deployment documentation. * * @example * # Get latest stable Console version * npx doc-tools get-console-version * # Output: v2.7.2 * * # Get latest beta version * npx doc-tools get-console-version --beta * # Output: v2.8.0-beta1 * * # Auto-detect from antora.yml prerelease flag * cd docs-site * npx doc-tools get-console-version --from-antora * * # Use in Docker Compose documentation * CONSOLE_VERSION=$(npx doc-tools get-console-version) * echo "image: redpandadata/console:$CONSOLE_VERSION" * * @requirements * - Internet connection to access GitHub API * - GitHub API rate limits apply (60 requests/hour unauthenticated) */ programCli .command('get-console-version') .description('Print the latest Console version') .option('--beta', 'Return the latest beta version if available') .option('--from-antora', 'Read prerelease flag from local antora.yml') .action(async (options) => { try { await require('../tools/get-console-version.js')(options) } catch (err) { console.error(`Error: ${err.message}`) process.exit(1) } }) /** * link-readme * * @description * Creates a symbolic link from a project's README.adoc file into the Antora documentation * structure. This allows project README files to be included in the documentation site * without duplication. The command creates the necessary directory structure and establishes * a symlink in docs/modules/<module>/pages/ that points to the project's README.adoc. * * @why * Documentation repositories often contain multiple sub-projects (like labs or examples) * that have their own README files. Rather than manually copying these files into the * Antora structure (which creates maintenance burden), symlinks keep the content in one * place while making it available to Antora. Changes to the project README automatically * appear in the docs site. * * @example * # Link a lab project README into documentation * npx doc-tools link-readme \ * --subdir labs/docker-compose \ * --target docker-compose-lab.adoc * * # Link multiple lab READMEs * npx doc-tools link-readme -s labs/kubernetes -t k8s-lab.adoc * npx doc-tools link-readme -s labs/terraform -t terraform-lab.adoc * * # The symlink structure created: * # docs/modules/labs/pages/docker-compose-lab.adoc -> ../../../../labs/docker-compose/README.adoc * * @requirements * - Must run from repository root * - Target project must have README.adoc file * - Operating system must support symbolic links */ programCli .command('link-readme') .description('Symlink a README.adoc into docs/modules/<module>/pages/') .requiredOption('-s, --subdir <subdir>', 'Relative path to the lab project subdirectory') .requiredOption('-t, --target <filename>', 'Name of the target AsciiDoc file in pages/') .action((options) => { const repoRoot = findRepoRoot() const normalized = options.subdir.replace(/\/+$/, '') const moduleName = normalized.split('/')[0] const projectDir = path.join(repoRoot, normalized) const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages') const sourceFile = path.join(projectDir, 'README.adoc') const destLink = path.join(pagesDir, options.target) if (!fs.existsSync(projectDir)) { console.error(`Error: Project directory not found: ${projectDir}`) process.exit(1) } if (!fs.existsSync(sourceFile)) { console.error(`Error: README.adoc not found in ${projectDir}`) process.exit(1) } fs.mkdirSync(pagesDir, { recursive: true }) const relPath = path.relative(pagesDir, sourceFile) try { if (fs.existsSync(destLink)) { const stat = fs.lstatSync(destLink) if (stat.isSymbolicLink()) fs.unlinkSync(destLink) else fail(`Destination already exists and is not a symlink: ${destLink}`) } fs.symlinkSync(relPath, destLink) console.log(`Done: Linked ${relPath} → ${destLink}`) } catch (err) { fail(`Failed to create symlink: ${err.message}`) } }) /** * fetch * * @description * Downloads specific files or directories from GitHub repositories without cloning the entire * repository. Uses the GitHub API to fetch content and saves it to a local directory. Useful * for grabbing examples, configuration files, or documentation snippets from other repositories. * Supports both individual files and entire directories. * * @why * Documentation often needs to reference or include files from other repositories (examples, * configuration templates, code samples). Cloning entire repositories is inefficient when you * only need specific files. This command provides targeted fetching, saving bandwidth and time. * It's particularly useful in CI/CD pipelines where you need specific assets without full clones. * * @example * # Fetch a specific configuration file * npx doc-tools fetch \ * --owner redpanda-data \ * --repo redpanda \ * --remote-path docker/docker-compose.yml \ * --save-dir examples/ * * # Fetch an entire directory of examples * npx doc-tools fetch \ * -o redpanda-data \ * -r connect-examples \ * -p pipelines/mongodb \ * -d docs/modules/examples/attachments/ * * # Fetch with custom filename * npx doc-tools fetch \ * -o redpanda-data \ * -r helm-charts \ * -p charts/redpanda/values.yaml \ * -d examples/ \ * --filename redpanda-values-example.yaml * * @requirements * - Internet connection to access GitHub API * - GitHub API rate limits apply (60 requests/hour unauthenticated, 5000 with token) * - For private repositories: GitHub token with repo permissions */ programCli .command('fetch') .description('Fetch a file or directory from GitHub and save it locally') .requiredOption('-o, --owner <owner>', 'GitHub repo owner or org') .requiredOption('-r, --repo <repo>', 'GitHub repo name') .requiredOption('-p, --remote-path <path>', 'Path in the repo to fetch') .requiredOption('-d, --save-dir <dir>', 'Local directory to save into') .option('-f, --filename <name>', 'Custom filename to save as') .action(async (options) => { try { await fetchFromGithub( options.owner, options.repo, options.remotePath, options.saveDir, options.filename ) console.log(`Done: Fetched to ${options.saveDir}`) } catch (err) { console.error(`Error: ${err.message}`) process.exit(1) } }) /** * setup-mcp * * @description * Configures the Redpanda Docs MCP (Model Context Protocol) server for Claude Code or * Claude Desktop. Automatically detects the installed application, updates the appropriate * configuration file, and enables Claude to use doc-tools commands through natural conversation. * Supports both production (npm package) and local development modes. * * @why * Manual MCP configuration requires editing JSON configuration files in the correct location * with the correct schema. This command handles all setup automatically, including path * detection, configuration merging, and validation. It enables AI-assisted documentation * workflows where writers can use natural language to run doc-tools commands. * * @example * # Auto-detect and configure for Claude Code or Desktop * npx doc-tools setup-mcp * * # Configure for local development (run from this repository) * cd /path/to/docs-extensions-and-macros * npx doc-tools setup-mcp --local * * # Force update existing configuration * npx doc-tools setup-mcp --force * * # Target specific application * npx doc-tools setup-mcp --target code * npx doc-tools setup-mcp --target desktop * * # Check current configuration status * npx doc-tools setup-mcp --status * * # After setup, restart Claude Code and use natural language * "What's the latest Redpanda version?" * "Generate property docs for v25.3.1" * * @requirements * - Claude Code or Claude Desktop must be installed * - For --local mode: must run from docs-extensions-and-macros repository * - After setup: restart Claude Code/Desktop to load the MCP server */ programCli .command('setup-mcp') .description('Configure the Redpanda Docs MCP server for Claude Code/Desktop') .option('--force', 'Force update even if already configured', false) .option('--target <type>', 'Target application: auto, code, or desktop', 'auto') .option('--local', 'Use local development mode (requires running from this repo)', false) .option('--status', 'Show current MCP server configuration status', false) .action(async (options) => { try { const { setupMCP, showStatus, printNextSteps } = require('../cli-utils/setup-mcp.js') if (options.status) { showStatus() return } const result = await setupMCP({ force: options.force, target: options.target, local: options.local }) if (result.success) { printNextSteps(result) process.exit(0) } else { console.error(`Error: Setup failed: ${result.error}`) process.exit(1) } } catch (err) { console.error(`Error: ${err.message}`) process.exit(1) } }) /** * @description Validate the MCP server configuration including tools and resources. * @why Use this command to verify the MCP server is properly configured. * @example * # Validate MCP configuration * npx doc-tools validate-mcp * @requirements None. */ programCli .command('validate-mcp') .description('Validate MCP server configuration (tools, resources)') .action(() => { const { validateMcpConfiguration, formatValidationResults } = require('./mcp-tools/mcp-validation') // Tools are defined in doc-tools-mcp.js - we validate the structure const tools = [ { name: 'get_antora_structure', description: 'Get Antora documentation structure' }, { name: 'get_redpanda_version', description: 'Get latest Redpanda version' }, { name: 'get_console_version', description: 'Get latest Console version' }, { name: 'generate_property_docs', description: 'Generate property documentation' }, { name: 'generate_metrics_docs', description: 'Generate metrics documentation' }, { name: 'generate_rpk_docs', description: 'Generate rpk CLI documentation' }, { name: 'generate_rpcn_connector_docs', description: 'Generate connector documentation' }, { name: 'generate_helm_docs', description: 'Generate Helm chart documentation' }, { name: 'generate_crd_docs', description: 'Generate CRD documentation' }, { name: 'generate_cloud_regions', description: 'Generate cloud regions documentation' }, { name: 'generate_bundle_openapi', description: 'Bundle OpenAPI specifications' }, { name: 'review_generated_docs', description: 'Review generated documentation' }, { name: 'run_doc_tools_command', description: 'Run raw doc-tools command' }, { name: 'get_job_status', description: 'Get background job status' }, { name: 'list_jobs', description: 'List background jobs' } ] const resources = [ { uri: 'redpanda://personas', name: 'Repository Personas', description: 'Target audience personas loaded from docs-data/personas.yaml' } ] try { console.log('Validating MCP configuration...') const validation = validateMcpConfiguration({ tools, resources }) const output = formatValidationResults(validation, { tools, resources }) console.log('\n' + output) if (!validation.valid) { process.exit(1) } } catch (err) { console.error(`Error: Validation failed: ${err.message}`) process.exit(1) } }) /** * @description Show MCP server version information including available tools * and optionally usage statistics from previous sessions. * @why Use this command to see what MCP capabilities are available. * @example * # Show version and capabilities * npx doc-tools mcp-version * * # Show with usage statistics * npx doc-tools mcp-version --stats * @requirements None. */ programCli .command('mcp-version') .description('Show MCP server version and configuration information') .option('--stats', 'Show usage statistics if available', false) .action((options) => { const packageJson = require('../package.json') const tools = [ { name: 'get_antora_structure', description: 'Get Antora documentation structure' }, { name: 'get_redpanda_version', description: 'Get latest Redpanda version' }, { name: 'get_console_version', description: 'Get latest Console version' }, { name: 'generate_property_docs', description: 'Generate property documentation' }, { name: 'generate_metrics_docs', description: 'Generate metrics documentation' }, { name: 'generate_rpk_docs', description: 'Generate rpk CLI documentation' }, { name: 'generate_rpcn_connector_docs', description: 'Generate connector documentation' }, { name: 'generate_helm_docs', description: 'Generate Helm chart documentation' }, { name: 'generate_crd_docs', description: 'Generate CRD documentation' }, { name: 'generate_cloud_regions', description: 'Generate cloud regions documentation' }, { name: 'generate_bundle_openapi', description: 'Bundle OpenAPI specifications' }, { name: 'review_generated_docs', description: 'Structural validation of generated docs' }, { name: 'run_doc_tools_command', description: 'Run raw doc-tools command' }, { name: 'get_job_status', description: 'Get background job status' }, { name: 'list_jobs', description: 'List background jobs' } ] const resources = [ { uri: 'redpanda://personas', name: 'Repository Personas', description: 'Loaded from docs-data/personas.yaml' } ] console.log('Redpanda Doc Tools MCP Server') console.log('='.repeat(60)) console.log(`Server version: ${packageJson.version}`) console.log('') console.log(`Tools (${tools.length} available):`) tools.forEach(tool => { console.log(` - ${tool.name}`) console.log(` ${tool.description}`) }) console.log('') console.log(`Resources (${resources.length} available):`) resources.forEach(resource => { console.log(` - ${resource.name}`) console.log(` URI: ${resource.uri}`) console.log(` ${resource.description}`) }) console.log('') console.log('Note: Prompts and most resources have been migrated to') console.log('the docs-team-standards Claude Code plugin.') console.log('') if (options.stats) { const statsPath = path.join(os.tmpdir(), 'mcp-usage-stats.json') if (fs.existsSync(statsPath)) { try { const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8')) console.log('Usage Statistics:') console.log('='.repeat(60)) if (stats.tools && Object.keys(stats.tools).length > 0) { console.log('\nTool Usage:') Object.entries(stats.tools) .sort(([, a], [, b]) => b.count - a.count) .forEach(([name, data]) => { console.log(` ${name}:`) console.log(` Invocations: ${data.count}`) if (data.errors > 0) { console.log(` Errors: ${data.errors}`) } }) } } catch (err) { console.error('Failed to parse usage statistics:', err.message) } } else { console.log('No usage statistics available yet.') console.log('Statistics are exported when the MCP server shuts down.') } } console.log('') console.log('For more information, see:') console.log(' mcp/WRITER_EXTENSION_GUIDE.adoc') console.log(' mcp/README.adoc') }) // ==================================================================== // GENERATE SUBCOMMAND GROUP // ==================================================================== const automation = new Command('generate').description('Run docs automations') /** * generate metrics-docs * * @description * Generates comprehensive metrics reference documentation by running Redpanda in Docker and * scraping the `/public_metrics` Prometheus endpoint. Starts a Redpanda cluster with the * specified version, waits for it to be ready, collects all exposed metrics, parses the * Prometheus format, and generates categorized AsciiDoc documentation. Optionally compares * metrics between versions to identify new, removed, or changed metrics. * * @why * Redpanda exposes hundreds of metrics for monitoring and observability. Manual documentation * of metrics is error-prone and becomes outdated as new metrics are added or existing ones * change. This automation ensures metrics documentation accurately reflects what Redpanda * actually exports at each version. Running Redpanda in Docker and scraping metrics directly * is the only reliable way to capture the complete and accurate metrics set. * * @example * # Basic: Generate metrics docs for a specific version * npx doc-tools generate metrics-docs --tag v25.3.1 * * # Compare metrics between versions to see what changed * npx doc-tools generate metrics-docs \ * --tag v25.3.1 \ * --diff v25.2.1 * * # Use custom Docker repository * npx doc-tools generate metrics-docs \ * --tag v25.3.1 \ * --docker-repo docker.redpanda.com/redpandadata/redpanda * * # Full workflow: document new release * VERSION=$(npx doc-tools get-redpanda-version) * npx doc-tools generate metrics-docs --tag $VERSION * * @requirements * - Docker must be installed and running * - Port 9644 must be available (Redpanda metrics endpoint) * - Sufficient disk space for Docker image * - Internet connection to pull Docker images */ automation .command('metrics-docs') .description('Generate JSON and AsciiDoc documentation for Redpanda metrics. Defaults to branch "dev" if neither --tag nor --branch is specified.') .option('-t, --tag <tag>', 'Git tag for released content (GA/beta)') .option('-b, --branch <branch>', 'Branch name for in-progress content') .option('--docker-repo <repo>', 'Docker repository to use', commonOptions.dockerRepo) .option('--console-tag <tag>', 'Redpanda Console version to use', commonOptions.consoleTag) .option('--console-docker-repo <repo>', 'Docker repository for Console', commonOptions.consoleDockerRepo) .option('--diff <oldTag>', 'Also diff autogenerated metrics from <oldTag> → <tag>') .action((options) => { verifyMetricsDependencies() if (options.tag && options.branch) { console.error('Error: Cannot specify both --tag and --branch') process.exit(1) } const newTag = options.tag || options.branch || 'dev' const oldTag = options.diff if (oldTag) { const oldDir = path.join('autogenerated', oldTag, 'metrics') if (!fs.existsSync(oldDir)) { console.log(`Generating metrics docs for old tag ${oldTag}…`) runClusterDocs('metrics', oldTag, options) } } console.log(`Generating metrics docs for new tag ${newTag}…`) runClusterDocs('metrics', newTag, options) if (oldTag) { diffDirs('metrics', oldTag, newTag) } process.exit(0) }) /** * generate rpcn-connector-docs * * @description * Generates complete reference documentation for Redpanda Connect (formerly Benthos) connectors, * processors, and components. Parses component templates and configuration schemas, reads * connector metadata from CSV, and generates AsciiDoc documentation for each component. Supports * diffing changes between versions and automatically updating what's new documentation. Can also * generate Bloblang function documentation. * * @why * Redpanda Connect has hundreds of connectors (inputs, outputs, processors) with complex * configuration schemas. Each component's documentation lives in its source code as struct * tags and comments. Manual documentation is impossible to maintain. This automation extracts * documentation directly from code, ensuring accuracy and completeness. The diff capability * automatically identifies new connectors and changed configurations for release notes. * * @example * # Basic: Generate all connector docs * npx doc-tools generate rpcn-connector-docs * * # Generate docs and automatically update what's new page * npx doc-tools generate rpcn-connector-docs --update-whats-new * * # Include Bloblang function documentation * npx doc-tools generate rpcn-connector-docs --include-bloblang * * # Fetch latest connector data using rpk * npx doc-tools generate rpcn-connector-docs --fetch-connectors * * # Full workflow with diff and what's new update * npx doc-tools generate rpcn-connector-docs \ * --update-whats-new \ * --include-bloblang * * @requirements * - rpk and rpk connect must be installed * - Internet connection for fetching connector data * - Node.js for parsing and generation */ automation .command('rpcn-connector-docs') .description('Generate RPCN connector docs and diff changes since the last version') .option('-d, --data-dir <path>', 'Directory where versioned connect JSON files live', path.resolve(process.cwd(), 'docs-data')) .option('--old-data <path>', 'Optional override for old data file (for diff)') .option('--update-whats-new', 'Update whats-new.adoc with new section from diff JSON') .option('-f, --fetch-connectors', 'Fetch latest connector data using rpk') .option('--connect-version <version>', 'Connect version to fetch (requires --fetch-connectors)') .option('-m, --draft-missing', 'Generate full-doc drafts for connectors missing in output') .option('--template-main <path>', 'Main Handlebars template', path.resolve(__dirname, '../tools/redpanda-connect/templates/connector.hbs')) .option('--template-intro <path>', 'Intro section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/intro.hbs')) .option('--template-fields <path>', 'Fields section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/fields-partials.hbs')) .option('--template-examples <path>', 'Examples section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/examples-partials.hbs')) .option('--template-bloblang <path>', 'Custom Handlebars template for bloblang function/method partials') .option('--overrides <path>', 'Optional JSON file with overrides', 'docs-data/overrides.json') .option('--include-bloblang', 'Include Bloblang functions and methods in generation') .option('--cloud-version <version>', 'Cloud binary version (default: auto-detect latest)') .option('--cgo-version <version>', 'cgo binary version (default: same as cloud-version)') .option('--skip-intermediate', 'Skip intermediate release processing (legacy mode - only compare latest vs last documented)') .option('--from-version <version>', 'Override starting version instead of using antora.yml (useful for backfilling)') .action(async (options) => { requireTool('rpk', { versionFlag: '--version', help: 'rpk is not installed. Install rpk: https://docs.redpanda.com/current/get-started/rpk-install/' }) requireTool('rpk connect', { versionFlag: '--version', help: 'rpk connect is not installed. Run rpk connect install before continuing.' }) const { handleRpcnConnectorDocs } = require('../tools/redpanda-connect/rpcn-connector-docs-handler.js') await handleRpcnConnectorDocs(options) }) /** * generate property-docs * * @description * Generates comprehensive reference documentation for Redpanda cluster and topic configuration * properties. Clones the Redpanda repository at a specified version, runs a Python extractor * to parse C++ configuration code, and outputs JSON data files with all property metadata * (descriptions, types, defaults, constraints). Optionally generates consolidated AsciiDoc * partials for direct inclusion in documentation sites. * * @why * Property definitions in the C++ source code are the single source of truth for Redpanda * configuration. Manual documentation becomes outdated quickly. This automation ensures docs * stay perfectly in sync with implementation by extracting properties directly from code, * including type information, default values, and constraints that would be error-prone to * maintain manually. * * @example * # Basic: Extract properties to JSON only (default) * npx doc-tools generate property-docs --tag v25.3.1 * * # Generate AsciiDoc partials for documentation site * npx doc-tools generate property-docs --tag v25.3.1 --generate-partials * * # Include Cloud support tags (requires GitHub token) * export GITHUB_TOKEN=ghp_xxx * npx doc-tools generate property-docs \ * --tag v25.3.1 \ * --generate-partials \ * --cloud-support * * # Compare properties between versions * npx doc-tools generate property-docs \ * --tag v25.3.1 \ * --diff v25.2.1 * * # Use custom output directory * npx doc-tools generate property-docs \ * --tag v25.3.1 \ * --output-dir docs/modules/reference * * # Full workflow: document new release * VERSION=$(npx doc-tools get-redpanda-version) * npx doc-tools generate property-docs \ * --tag $VERSION \ * --generate-partials \ * --cloud-support * * @requirements * - Python 3.9 or higher * - Git * - Internet connection to clone Redpanda repository * - For --cloud-support: GitHub token with repo permissions (GITHUB_TOKEN env var) * - For --cloud-support: Python packages pyyaml and requests */ automation .command('property-docs') .description( 'Generate JSON and consolidated AsciiDoc partials for Redpanda configuration properties. ' + 'Defaults to branch "dev" if neither --tag nor --branch is specified.' ) .option('-t, --tag <tag>', 'Git tag for released content (GA/beta)') .option('-b, --branch <branch>', 'Branch name for in-progress content') .option('--diff <oldTag>', 'Diff properties against <oldTag> and restore removed deprecated properties. Recommended for accurate output; falls back to latest-redpanda-tag from antora.yml if not specified') .option('--overrides <path>', 'Optional JSON file with property description overrides', 'docs-data/property-overrides.json') .option('--output-dir <dir>', 'Where to write all generated files', 'modules/reference') .option('--cloud-support', 'Add AsciiDoc tags to generated property docs to indicate which ones are supported in Redpanda Cloud. This data is fetched from the cloudv2 repository so requires a GitHub token with repo permissions. Set the token as an environment variable using GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN', true) .option('--template-property <path>', 'Custom Handlebars template for individual property sections') .option('--template-topic-property <path>', 'Custom Handlebars template for topic property sections') .option('--template-topic-property-mappings <path>', 'Custom Handlebars template for topic property mappings table') .option('--template-deprecated <path>', 'Custom Handlebars template for deprecated properties page') .option('--template-deprecated-property <path>', 'Custom Handlebars template for individual deprecated property sections') .option('--generate-partials', 'Generate consolidated property partials') .option('--partials-dir <path>', 'Directory for property partials (relative to output-dir)', 'partials') .action((options) => { verifyPropertyDependencies() if (options.tag && options.branch) { console.error('Error: Cannot specify both --tag and --branch') process.exit(1) } const newTag = options.tag || options.branch || 'dev' if (options.cloudSupport) { console.log('Validating cloud support dependencies...') const { getGitHubToken } = require('../cli-utils/github-token') const token = getGitHubToken() if (!token) { console.error('Error: Cloud support requires a GitHub token') console.error(' Set: export GITHUB_TOKEN=your_token_here') console.error(' Or disable cloud support with: --no-cloud-support') process.exit(1) } console.log('Done: GitHub token validated') } let oldTag = options.diff if (!oldTag) { oldTag = getAntoraValue('asciidoc.attributes.latest-redpanda-tag') if (oldTag) { console.log(`Using latest-redpanda-tag from Antora attributes for --diff: ${oldTag}`) } } if (!oldTag) { console.warn('Warning: No previous version specified (--diff) and no latest-redpanda-tag found in Antora attributes.') console.warn(' Deprecated properties that were removed from source (v26.1+) will not be detected.') console.warn(' For accurate output, specify --diff <previous-tag> or set latest-redpanda-tag in antora.yml.') } const overridesPath = options.overrides const outputDir = options.outputDir const cwd = path.resolve(__dirname, '../tools/property-extractor') const make = (tag, overrides, templates = {}, outDir = 'modules/reference/', { skipPartials = false } = {}) => { console.log(`Building property docs for ${tag}…`) const args = ['build', `TAG=${tag}`] const env = { ...process.env } if (overrides) env.OVERRIDES = path.resolve(overrides) if (options.cloudSupport) env.CLOUD_SUPPORT = '1' if (templates.property) env.TEMPLATE_PROPERTY = path.resolve(templates.property) if (templates.topicProperty) env.TEMPLATE_TOPIC_PROPERTY = path.resolve(templates.topicProperty) if (templates.topicPropertyMappings) env.TEMPLATE_TOPIC_PROPERTY_MAPPINGS = path.resolve(templates.topicPropertyMappings) if (templates.deprecated) env.TEMPLATE_DEPRECATED = path.resolve(templates.deprecated) if (templates.deprecatedProperty) env.TEMPLATE_DEPRECATED_PROPERTY = path.resolve(templates.deprecatedProperty) env.OUTPUT_JSON_DIR = path.resolve(outDir, 'attachments') env.OUTPUT_AUTOGENERATED_DIR = path.resolve(outDir) if (options.generatePartials && !skipPartials) { env.GENERATE_PARTIALS = '1' env.OUTPUT_PARTIALS_DIR = path.resolve(outDir, options.partialsDir || 'partials') } const r = spawnSync('make', args, { cwd, stdio: 'inherit', env }) if (r.error) { console.error(`Error: ${r.error.message}`) process.exit(1) } if (r.status !== 0) process.exit(r.status) } const templates = { property: options.templateProperty, topicProperty: options.templateTopicProperty, topicPropertyMappings: options.templateTopicPropertyMappings, deprecated: options.templateDeprecated, deprecatedProperty: options.templateDeprecatedProperty } const tagsAreSame = oldTag && newTag && oldTag === newTag const needsDiff = oldTag && !tagsAreSame // Phase 1: Extract JSON from C++ source. // When a diff is needed, skip AsciiDoc generation during extraction so we // can merge removed deprecated properties first and generate only once. if (needsDiff) { make(oldTag, overridesPath, templates, outputDir, { skipPartials: true }) make(newTag, overridesPath, templates, outputDir, { skipPartials: true }) } else { make(newTag, overridesPath, templates, outputDir) } // Phase 2: Compare old vs new and merge removed deprecated properties into // the new JSON so they appear in the generated documentation. if (needsDiff) { const diffOutputDir = overridesPath ? path.dirname(path.resolve(overridesPath)) : outputDir generatePropertyComparisonReport(oldTag, newTag, diffOutputDir) try { const diffReportPath = path.join(diffOutputDir, `redpanda-property-changes-${oldTag}-to-${newTag}.json`) if (fs.existsSync(diffReportPath)) { const diffData = JSON.parse(fs.readFileSync(diffReportPath, 'utf8')) const { printPRSummary } = require('../tools/property-extractor/pr-summary-formatter') printPRSummary(diffData) if (overridesPath && fs.existsSync(overridesPath)) { updatePropertyOverridesWithVersion(overridesPath, diffData, newTag) } } } catch (err) { console.warn(`Warning: Failed to generate PR summary: ${err.message}`) } cleanupOldDiffs(diffOutputDir) // Phase 3: Generate AsciiDoc once from the complete JSON (includes merged deprecated properties) if (options.generatePartials) { const updatedJsonPath = path.resolve(outputDir, 'attachments', `redpanda-properties-${newTag}.json`) if (fs.existsSync(updatedJsonPath)) { process.env.GENERATE_PARTIALS = '1' process.env.OUTPUT_PARTIALS_DIR = path.resolve(outputDir, options.partialsDir || 'partials') const { generateAllDocs } = require('../tools/property-extractor/generate-handlebars-docs') console.log('Generating AsciiDoc from complete property data…') generateAllDocs(updatedJsonPath, path.resolve(outputDir)) } } } if (!options.diff && !tagsAreSame) { const tagSuccess = setAntoraValue('asciidoc.attributes.latest-redpanda-tag', newTag) if (tagSuccess) console.log(`Done: Updated Antora latest-redpanda-tag to: ${newTag}`) const versionWithoutV = newTag.startsWith('v') ? newTag.slice(1) : newTag const versionSuccess = setAntoraValue('asciidoc.attributes.full-version', versionWithoutV) if (versionSuccess) console.log(`Done: Updated Antora full-version to: ${versionWithoutV}`) try { const jsonDir = path.resolve(outputDir, 'attachments') const propertyFiles = fs.readdirSync(jsonDir) .filter(f => /^redpanda-properties-v[\d.]+\.json$/.test(f)) .sort() const keepFile = `redpanda-properties-${newTag}.json` const filesToDelete = propertyFiles.filter(f => f !== keepFile) if (filesToDelete.length > 0) { console.log('🧹 Cleaning up old property JSON files...') filesToDelete.forEach(file => { fs.unlinkSync(path.join(jsonDir, file)) console.log(` Deleted: ${file}`) }) } } catch (err) { console.warn(`Warning: Failed to cleanup old property JSON files: ${err.message}`) } } process.exit(0) }) /** * generate rpk-docs * * @description * Generates comprehensive CLI reference documentation for RPK (Redpanda Keeper), the official * Redpanda command-line tool. Starts Redpanda in Docker (RPK is bundled with Redpanda), executes * `rpk --help` for all commands and subcommands recursively, parses the help output, and generates * structured AsciiDoc documentation for each command with usage, flags, and descriptions. * Optionally compares RPK commands between versions to identify new or changed commands. * * @why * RPK has dozens of commands and subcommands with complex flags and options. The built-in help * text is the source of truth for RPK's CLI interface. Manual documentation becomes outdated as * RPK evolves. This automation extracts documentation directly from RPK's help output, ensuring * accuracy. Running RPK from Docker guarantees the exact version being documented, and diffing * between versions automatically highlights CLI changes for release notes. * * @example * # Basic: Generate RPK docs for a specific version * npx doc-tools generate rpk-docs --tag v25.3.1 * * # Compare RPK commands between versions * npx doc-tools generate rpk-docs \ * --tag v25.3.1 \ * --diff v25.2.1 * * # Use custom Docker repository * npx doc-tools generate rpk-docs \ * --tag v25.3.1 \ * --docker-repo docker.redpanda.com/redpandadata/redpanda * * # Full workflow: document new release * VERSION=$(npx doc-tools get-redpanda-version) * npx doc-tools generate rpk-docs --tag $VERSION * * @requirements * - Docker must be installed and running * - Sufficient disk space for Docker image * - Internet connection to pull Docker images */ automation .command('rpk-docs') .description('Generate AsciiDoc documentation for rpk CLI commands. Defaults to branch "dev" if neither --tag nor --branch is specified.') .option('-t, --tag <tag>', 'Git tag for released content (GA/beta)') .option('-b, --branch <branch>', 'Branch name for in-progress content') .option('--docker-repo <repo>', 'Docker repository to use', commonOptions.dockerRepo) .option('--console-tag <tag>', 'Redpanda Console version to use', commonOptions.consoleTag) .option('--console-docker-repo <repo>', 'Docker repository for Console', commonOptions.consoleDockerRepo) .option('--diff <oldTag>', 'Also diff autogenerated rpk docs from <oldTag> → <tag>') .action((options) => { verifyMetricsDependencies() if (options.tag && options.branch) { console.error('Error: Cannot specify both --tag and --branch') process.exit(1) } const newTag = options.tag || options.branch || 'dev' const oldTag = options.diff if (oldTag) { const oldDir = path.join('autogenerated', oldTag, 'rpk') if (!fs.existsSync(oldDir)) { console.log(`Generating rpk docs for old tag ${oldTag}…`) runClusterDocs('rpk', oldTag, options) } } console.log(`Generating rpk docs for new tag ${newTag}…`) runClusterDocs('rpk', newTag, options) if (oldTag) { diffDirs('rpk', oldTag, newTag) } process.exit(0) }) /** * generate helm-spec * * @description * Generates Helm chart reference documentation by parsing values.yaml files and README.md * documentation from Helm chart repositories. Supports both local chart directories and * GitHub URLs. Extracts all configuration options with their types, defaults, and descriptions, * and generates comprehensive AsciiDoc documentation. Can process single charts or entire * chart repositories with multiple charts. * * @why * Helm charts have complex configuration with hundreds of values. The values.yaml file and * chart README contain the configuration options, but they're not in a documentation-friendly * format. This automation parses the YAML structure and README documentation to generate * comprehensive reference documentation. Supporting both local and GitHub sources allows * documenting charts from any source without manual cloning. * * @example * # Generate docs from GitHub repository * npx doc-tools generate helm-spec \ * --chart-dir https://github.com/redpanda-data/helm-charts \ * --tag v5.9.0 \ * --output-dir modules/deploy/pages * * # Generate docs from local chart directory * npx doc-tools generate helm-spec \ * --chart-dir ./charts/redpanda \ * --output-dir docs/modules/deploy/pages * * # Use custom README and output suffix * npx doc-tools generate helm-spec \ * --chart-dir https://github.com/redpanda-data/helm-charts \ * --tag v5.9.0 \ * --readme docs/README.md \ * --output-suffix -values.adoc * * @requirements * - For GitHub URLs: Git and internet connection * - For local charts: Chart directory must contain Chart.yaml * - README.md file in chart directory (optional but recommended) * - helm-docs and pandoc must be installed */ automation .command('helm-spec') .description('Generate AsciiDoc documentation for Helm charts. Requires either --tag or --branch for GitHub URLs.') .option('--chart-dir <dir|url>', 'Chart directory or GitHub URL', 'https://github.com/redpanda-data/redpanda-operator/charts') .option('-t, --tag <tag>', 'Git tag for released content') .option('-b, --branch <branch>', 'Branch name for in-progress content') .option('--readme <file>', 'Relative README.md path inside each chart dir', 'README.md') .option('--output-dir <dir>', 'Where to write generated AsciiDoc files', 'modules/reference/pages') .option('--output-suffix <suffix>', 'Suffix to append to each chart name', '-helm-spec.adoc') .action((opts) => { verifyHelmDependencies() let root = opts.chartDir let tmpClone = null if (/^https?:\/\/github\.com\//.test(root)) { if (!opts.tag && !opts.branch) { console.error('Error: When using a GitHub URL you must pass either --tag or --branch') process.exit(1) } if (opts.tag && opts.branch) { console.error('Error: Cannot specify both --tag and --branch') process.exit(1) } let gitRef = opts.tag || opts.branch if (opts.tag && !gitRef.startsWith('v')) { gitRef = `v${gitRef}` console.log(`ℹ️ Auto-prepending "v" to tag: ${gitRef}`) } const u = new URL(root) const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean) if (parts.length < 2) { console.error(`Error: Invalid GitHub URL: ${root}`) process.exit(1) } const [owner, repo, ...sub] = parts const repoUrl = `https://${u.host}/${owner}/${repo}.git` if (opts.tag && owner === 'redpanda-data' && repo === 'redpanda-operator') { if (!gitRef.startsWith('operator/')) { gitRef = `operator/${gitRef}` console.log(`ℹ️ Auto-prepending "operator/" to tag: ${gitRef}`) } } console.log(`Verifying ${repoUrl}@${gitRef}…`) const ok = spawnSync( 'git', ['ls-remote', '--exit-code', repoUrl, `refs/heads/${gitRef}`, `refs/tags/${gitRef}`], { stdio: 'ignore' } ).status === 0 if (!ok) { console.error(`Error: ${gitRef} not found on ${repoUrl}`) process.exit(1) } const { getAuthenticatedGitHubUrl, hasGitHubToken } = require('../cli-utils/github-token') tmpClone = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-')) let cloneUrl = repoUrl if (hasGitHubToken() && repoUrl.includes('github.com')) { cloneUrl = getAuthenticatedGitHubUrl(repoUrl) console.log(`Cloning ${repoUrl}@${gitRef} → ${tmpClone} (authenticated)`) } else { console.log(`Cloning ${repoUrl}@${gitRef} → ${tmpClone}`) } if (spawnSync('git', ['clone', '--depth', '1', '--branch', gitRef, cloneUrl, tmpClone], { stdio: 'inherit' }).status !== 0) { console.error('Error: git clone failed') process.exit(1) } root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone } if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) { console.error(`Error: Chart root not found: ${root}`) process.exit(1) } let charts = [] if (fs.existsSync(path.join(root, 'Chart.yaml'))) { charts = [root] } else { charts = fs.readdirSync(root) .map((n) => path.join(root, n)) .filter((p) => fs.existsSync(path.join(p, 'Chart.yaml'))) } if (charts.length === 0) { console.error(`Error: No charts found under: ${root}`) process.exit(1) } const outDir = path.resolve(opts.outputDir) fs.mkdirSync(outDir, { recursive: true }) for (const chartPath of charts) { const name = path.basename(chartPath) console.log(`Processing chart "${name}"…`) console.log(`helm-docs in ${chartPath}`) let r = spawnSync('helm-docs', { cwd: chartPath, stdio: 'inherit' }) if (r.status !== 0) process.exit(r.status) const md = path.join(chartPath, opts.readme) if (!fs.existsSync(md)) { console.error(`Error: README not found: ${md}`) process.exit(1) } const outFile = path.join(outDir, `k-${name}${opts.outputSuffix}`) console.log(`pandoc ${md} → ${outFile}`) fs.mkdirSync(path.dirname(outFile), { recursive: true }) r = spawnSync('pandoc', [md, '-t', 'asciidoc', '-o', outFile], { stdio: 'inherit' }) if (r.status !== 0) process.exit(r.status) let doc = fs.readFileSync(outFile, 'utf8') const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g doc = doc .replace(/(\[\d+\])\]\./g, '$1\\].') .replace(/(\[\d+\])\]\]/g, '$1\\]\\]') .replace(/^=== +(https?:\/\/[^\[]*)\[([^\]]*)\]/gm, '=== link:++$1++[$2]') .replace(/^== # (.*)$/gm, '= $1') .replace(/^== description: (.*)$/gm, ':description: $1') .replace(xrefRe, (match) => { let urlPart = match let bracketPart = '' const m = match.match(/^([^\[]+)(\[[^\]]*\])$/) if (m) { urlPart = m[1] bracketPart = m[2] } if (urlPart.endsWith('#')) return match try { const xref = urlToXref(urlPart) return bracketPart ? `${xref}${bracketPart}` : `${xref}[]` } catch (err) { console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`) return match } }) fs.writeFileSync(outFile, doc, 'utf8') console.log(`Done: Wrote ${outFile}`) } if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true }) }) /** * generate cloud-regions * * @description * Generates a formatted table of Redpanda Cloud regions, tiers, and availability information * by fetching data from