@redpanda-data/docs-extensions-and-macros
Version:
Antora extensions and macros developed for Redpanda documentation.
218 lines (190 loc) • 7.09 kB
JavaScript
/**
* MCP Tools - rpk Documentation Generation
*
* Generates rpk CLI reference documentation by building from Go source
* and parsing build tags to detect Linux-only commands.
*
* Features:
* - Builds rpk from source (requires Go)
* - Detects Linux-only commands from Go build tags
* - Supports overrides.json for description improvements
* - Generates diffs between versions for release notes
* - Publishes versioned JSON for downstream consumers
*
* OPTIMIZATION: This tool calls CLI, doesn't use LLM directly.
* - No model recommendation (CLI tool)
* - Cost comes from doc-tools CLI execution
*/
const { spawnSync } = require('child_process');
const { findRepoRoot, getDocToolsCommand, MAX_EXEC_BUFFER_SIZE, DEFAULT_COMMAND_TIMEOUT } = require('./utils');
const { getAntoraStructure } = require('./antora');
const { createJob } = require('./job-queue');
/**
* Generate rpk command documentation from source
*
* Clones redpanda source (or uses local checkout) and builds rpk to generate
* documentation. Parses Go build tags to detect Linux-only commands.
*
* @param {Object} args - Arguments
* @param {string} [args.ref] - Git ref to document (e.g., "dev", "v26.2.0", "main")
* @param {string} [args.tag] - Git tag (alias for ref, backward compatibility)
* @param {string} [args.branch] - Git branch (alias for ref, backward compatibility)
* @param {string} [args.from_source] - Path to local rpk source directory
* @param {string} [args.overrides] - Path to overrides JSON file (defaults to docs-data/rpk-overrides.json)
* @param {string} [args.diff] - Generate diff against this previous version
* @param {boolean} [args.update_whats_new] - Update whats-new.adoc with changes
* @param {boolean} [args.draft_missing] - Generate draft pages for new commands
* @param {string} [args.output_dir] - Output directory for generated AsciiDoc
* @param {string} [args.data_dir] - Directory for versioned JSON and diff files
* @param {boolean} [args.background] - Run as background job
* @returns {Object} Generation results
*/
function generateRpkDocs(args) {
const repoRoot = findRepoRoot();
const structure = getAntoraStructure(repoRoot);
if (!structure.hasDocTools) {
return {
success: false,
error: 'doc-tools not found in this repository',
suggestion: 'Navigate to the docs-extensions-and-macros repository'
};
}
// Determine version from ref, tag, or branch (backward compatibility)
// Priority: ref > tag > branch > 'dev' (default)
let version;
let refType;
if (args.ref) {
version = args.ref;
refType = args.ref.match(/^v?\d/) ? 'tag' : 'branch';
} else if (args.tag) {
version = args.tag.startsWith('v') ? args.tag : `v${args.tag}`;
refType = 'tag';
} else if (args.branch) {
version = args.branch;
refType = 'branch';
} else {
// No ref specified - default to 'dev'
version = 'dev';
refType = 'branch';
}
// Get doc-tools command (handles both local and installed)
const docTools = getDocToolsCommand(repoRoot);
// Build command arguments array
const baseArgs = ['generate', 'rpk-docs'];
// Always use --ref (defaults to 'dev' if not specified)
baseArgs.push('--ref', version);
// Add optional arguments
if (args.from_source) {
baseArgs.push('--from-source', args.from_source);
}
if (args.overrides) {
baseArgs.push('--overrides', args.overrides);
}
if (args.diff) {
let diffVersion = args.diff;
if (!diffVersion.startsWith('v')) {
diffVersion = `v${diffVersion}`;
}
baseArgs.push('--diff', diffVersion);
}
if (args.update_whats_new) {
baseArgs.push('--update-whats-new');
}
if (args.draft_missing) {
baseArgs.push('--draft-missing');
}
if (args.output_dir) {
baseArgs.push('--output-dir', args.output_dir);
}
if (args.data_dir) {
baseArgs.push('--data-dir', args.data_dir);
}
// If background mode, create job and return immediately
if (args.background) {
const cmdArgs = [docTools.program, ...docTools.getArgs(baseArgs)];
const jobId = createJob('generate_rpk_docs', cmdArgs, {
cwd: repoRoot.root
});
return {
success: true,
background: true,
job_id: jobId,
message: `rpk docs generation started in background. Use get_job_status with job_id: ${jobId} to check progress.`,
ref: version,
version: version,
ref_type: refType
};
}
// Otherwise run synchronously
try {
const result = spawnSync(docTools.program, docTools.getArgs(baseArgs), {
cwd: repoRoot.root,
encoding: 'utf8',
stdio: 'pipe',
maxBuffer: MAX_EXEC_BUFFER_SIZE,
timeout: DEFAULT_COMMAND_TIMEOUT
});
// Check for spawn errors
if (result.error) {
const err = new Error(`Failed to execute command: ${result.error.message}`);
err.stdout = result.stdout || '';
err.stderr = result.stderr || '';
err.status = result.status;
throw err;
}
// Check for non-zero exit codes
if (result.status !== 0) {
const errorMsg = result.stderr || `Command failed with exit code ${result.status}`;
const err = new Error(errorMsg);
err.stdout = result.stdout || '';
err.stderr = result.stderr || '';
err.status = result.status;
throw err;
}
const output = result.stdout;
const commandCountMatch = output.match(/(\d+) commands/i);
const filesGeneratedMatch = output.match(/Generated (\d+) files/i);
const response = {
success: true,
ref: version,
version: version,
ref_type: refType,
commands_documented: commandCountMatch ? parseInt(commandCountMatch[1]) : null,
files_generated: filesGeneratedMatch ? parseInt(filesGeneratedMatch[1]) : null,
output: output.trim()
};
// Add diff summary if available
if (args.diff) {
response.diff_from = args.diff.startsWith('v') ? args.diff : `v${args.diff}`;
response.diff_to = version;
}
response.summary = `Generated rpk documentation for ${version}`;
return response;
} catch (err) {
const response = {
success: false,
error: err.message,
// Report the ref that was resolved and attempted, even on failure, so
// callers can confirm parameter defaulting worked independently of the
// (environment-dependent) build outcome.
ref: version,
version: version,
ref_type: refType,
stdout: err.stdout || '',
stderr: err.stderr || '',
exitCode: err.status
};
// Provide helpful suggestions based on error
if (err.message.includes('Go is required')) {
response.suggestion = 'Install Go from https://go.dev/ and ensure it\'s in your PATH.';
} else if (err.message.includes('not found') || err.message.includes('ENOENT')) {
response.suggestion = 'Ensure Go and Git are installed and in your PATH.';
} else {
response.suggestion = 'Check that the version/ref exists in the Redpanda repository.';
}
return response;
}
}
module.exports = {
generateRpkDocs
};