chore(qol): audit-cli-documentation.js should dynamically get top-level commands and recurse through --help.\Both products work together:
Running node [audit-cli-documentation.js](http://_vscodecontentref_/1) both successfully audits both Core and Enterprise Core templates use Core-specific frontmatter Enterprise templates use Enterprise-specific frontmatter Fixes audit-cli-documentation.js so that it parses commands dynamically from the CLI output. Some commands () only return top-level help output, which the script had some difficulty with.That seems mostly resolved, but might rear again.pull/6282/head
parent
b2aab8ad43
commit
c708bd8658
|
|
@ -51,39 +51,9 @@ class CLIDocAuditor {
|
|||
);
|
||||
}
|
||||
|
||||
// Commands to extract help for
|
||||
this.mainCommands = [
|
||||
'create',
|
||||
'delete',
|
||||
'disable',
|
||||
'enable',
|
||||
'query',
|
||||
'show',
|
||||
'test',
|
||||
'update',
|
||||
'write',
|
||||
];
|
||||
this.subcommands = [
|
||||
'create database',
|
||||
'create token admin',
|
||||
'create token',
|
||||
'create trigger',
|
||||
'create last_cache',
|
||||
'create distinct_cache',
|
||||
'create table',
|
||||
'show databases',
|
||||
'show tokens',
|
||||
'show system',
|
||||
'delete database',
|
||||
'delete table',
|
||||
'delete trigger',
|
||||
'update database',
|
||||
'test wal_plugin',
|
||||
'test schedule_plugin',
|
||||
];
|
||||
|
||||
// Map for command tracking during option parsing
|
||||
this.commandOptionsMap = {};
|
||||
// Dynamic command discovery - populated by discoverCommands()
|
||||
this.discoveredCommands = new Map(); // command -> { subcommands: [], options: [] }
|
||||
this.commandOptionsMap = {}; // For backward compatibility
|
||||
}
|
||||
|
||||
async fileExists(path) {
|
||||
|
|
@ -154,6 +124,238 @@ class CLIDocAuditor {
|
|||
});
|
||||
}
|
||||
|
||||
async ensureContainerRunning(product) {
|
||||
const containerName = `influxdb3-${product}`;
|
||||
|
||||
// Check if container exists and is running
|
||||
const { code, stdout } = await this.runCommand('docker', [
|
||||
'compose',
|
||||
'ps',
|
||||
'--format',
|
||||
'json',
|
||||
containerName,
|
||||
]);
|
||||
|
||||
if (code !== 0) {
|
||||
console.log(`❌ Failed to check container status for ${containerName}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const containers = stdout.trim().split('\n').filter((line) => line);
|
||||
const isRunning = containers.some((line) => {
|
||||
try {
|
||||
const container = JSON.parse(line);
|
||||
return container.Name === containerName && container.State === 'running';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!isRunning) {
|
||||
console.log(`🚀 Starting ${containerName}...`);
|
||||
const startResult = await this.runCommand('docker', [
|
||||
'compose',
|
||||
'up',
|
||||
'-d',
|
||||
containerName,
|
||||
]);
|
||||
|
||||
if (startResult.code !== 0) {
|
||||
console.log(`❌ Failed to start ${containerName}`);
|
||||
console.log(startResult.stderr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for container to be ready
|
||||
console.log(`⏳ Waiting for ${containerName} to be ready...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async discoverCommands(product) {
|
||||
const containerName = `influxdb3-${product}`;
|
||||
|
||||
// Ensure container is running
|
||||
if (!(await this.ensureContainerRunning(product))) {
|
||||
throw new Error(`Failed to start container ${containerName}`);
|
||||
}
|
||||
|
||||
// Get main help to discover top-level commands
|
||||
const mainHelp = await this.runCommand('docker', [
|
||||
'compose',
|
||||
'exec',
|
||||
'-T',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
'--help',
|
||||
]);
|
||||
|
||||
if (mainHelp.code !== 0) {
|
||||
console.error(`Failed to get main help. Exit code: ${mainHelp.code}`);
|
||||
console.error(`Stdout: ${mainHelp.stdout}`);
|
||||
console.error(`Stderr: ${mainHelp.stderr}`);
|
||||
throw new Error(`Failed to get main help: ${mainHelp.stderr}`);
|
||||
}
|
||||
|
||||
// Parse main commands from help output
|
||||
const mainCommands = this.parseCommandsFromHelp(mainHelp.stdout);
|
||||
|
||||
// Also add the root command first
|
||||
this.discoveredCommands.set('influxdb3', {
|
||||
subcommands: mainCommands,
|
||||
options: this.parseOptionsFromHelp(mainHelp.stdout),
|
||||
helpText: mainHelp.stdout,
|
||||
});
|
||||
|
||||
// For backward compatibility
|
||||
this.commandOptionsMap['influxdb3'] = this.parseOptionsFromHelp(mainHelp.stdout);
|
||||
|
||||
// Discover subcommands and options for each main command
|
||||
for (const command of mainCommands) {
|
||||
await this.discoverSubcommands(containerName, command, [command]);
|
||||
}
|
||||
}
|
||||
|
||||
parseCommandsFromHelp(helpText) {
|
||||
const commands = [];
|
||||
// Strip ANSI color codes first
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const cleanHelpText = helpText.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
const lines = cleanHelpText.split('\n');
|
||||
let inCommandsSection = false;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Look for any Commands section
|
||||
if (trimmed.includes('Commands:') || trimmed === 'Resource Management:' ||
|
||||
trimmed === 'System Management:') {
|
||||
inCommandsSection = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop at next section (but don't stop on management sections)
|
||||
if (inCommandsSection && /^[A-Z][a-z]+:$/.test(trimmed) &&
|
||||
!trimmed.includes('Commands:') &&
|
||||
trimmed !== 'Resource Management:' &&
|
||||
trimmed !== 'System Management:') {
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse command lines (typically indented with command name)
|
||||
if (inCommandsSection && /^\s+[a-z]/.test(line)) {
|
||||
const match = line.match(/^\s+([a-z][a-z0-9_-]*)/);
|
||||
if (match) {
|
||||
commands.push(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
parseOptionsFromHelp(helpText) {
|
||||
const options = [];
|
||||
const lines = helpText.split('\n');
|
||||
let inOptionsSection = false;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Look for Options: section
|
||||
if (trimmed === 'Options:') {
|
||||
inOptionsSection = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop at next section
|
||||
if (inOptionsSection && /^[A-Z][a-z]+:$/.test(trimmed)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse option lines
|
||||
if (inOptionsSection && /^\s*-/.test(line)) {
|
||||
const optionMatch = line.match(/--([a-z][a-z0-9-]*)/);
|
||||
if (optionMatch) {
|
||||
options.push(`--${optionMatch[1]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
async discoverSubcommands(containerName, commandPath, commandParts) {
|
||||
// Get help for this command
|
||||
|
||||
// First try with --help
|
||||
let helpResult = await this.runCommand('docker', [
|
||||
'compose',
|
||||
'exec',
|
||||
'-T',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
...commandParts,
|
||||
'--help',
|
||||
]);
|
||||
|
||||
// If --help returns main help or fails, try without --help
|
||||
if (helpResult.code !== 0 || helpResult.stdout.includes('InfluxDB 3 Core Server and Command Line Tools')) {
|
||||
helpResult = await this.runCommand('docker', [
|
||||
'compose',
|
||||
'exec',
|
||||
'-T',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
...commandParts,
|
||||
]);
|
||||
}
|
||||
|
||||
if (helpResult.code !== 0) {
|
||||
// Check if stderr contains useful help information
|
||||
if (helpResult.stderr && helpResult.stderr.includes('Usage:') && helpResult.stderr.includes('Commands:')) {
|
||||
// Use stderr as the help text since it contains the command usage info
|
||||
helpResult = { code: 0, stdout: helpResult.stderr, stderr: '' };
|
||||
} else {
|
||||
// Command might not exist or might not have subcommands
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the result is still the main help, skip this command
|
||||
if (helpResult.stdout.includes('InfluxDB 3 Core Server and Command Line Tools')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const helpText = helpResult.stdout;
|
||||
const subcommands = this.parseCommandsFromHelp(helpText);
|
||||
const options = this.parseOptionsFromHelp(helpText);
|
||||
|
||||
// Store the command info
|
||||
const fullCommand = `influxdb3 ${commandParts.join(' ')}`;
|
||||
this.discoveredCommands.set(fullCommand, {
|
||||
subcommands,
|
||||
options,
|
||||
helpText,
|
||||
});
|
||||
|
||||
// For backward compatibility
|
||||
this.commandOptionsMap[fullCommand] = options;
|
||||
|
||||
// Recursively discover subcommands (but limit depth)
|
||||
if (subcommands.length > 0 && commandParts.length < 3) {
|
||||
for (const subcommand of subcommands) {
|
||||
await this.discoverSubcommands(
|
||||
containerName,
|
||||
`${commandPath} ${subcommand}`,
|
||||
[...commandParts, subcommand]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async extractCurrentCLI(product, outputFile) {
|
||||
process.stdout.write(
|
||||
`Extracting current CLI help from influxdb3-${product}...`
|
||||
|
|
@ -164,57 +366,30 @@ class CLIDocAuditor {
|
|||
if (this.version === 'local') {
|
||||
const containerName = `influxdb3-${product}`;
|
||||
|
||||
// Check if container is running
|
||||
const { code, stdout } = await this.runCommand('docker', [
|
||||
'ps',
|
||||
'--format',
|
||||
'{{.Names}}',
|
||||
]);
|
||||
if (code !== 0 || !stdout.includes(containerName)) {
|
||||
// Ensure container is running and discover commands
|
||||
if (!(await this.ensureContainerRunning(product))) {
|
||||
console.log(` ${Colors.RED}✗${Colors.NC}`);
|
||||
console.log(`Error: Container ${containerName} is not running.`);
|
||||
console.log(`Start it with: docker compose up -d influxdb3-${product}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract comprehensive help
|
||||
let fileContent = '';
|
||||
// Discover all commands dynamically
|
||||
await this.discoverCommands(product);
|
||||
|
||||
// Main help
|
||||
const mainHelp = await this.runCommand('docker', [
|
||||
'exec',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
'--help',
|
||||
]);
|
||||
fileContent += mainHelp.code === 0 ? mainHelp.stdout : mainHelp.stderr;
|
||||
|
||||
// Extract all subcommand help
|
||||
for (const cmd of this.mainCommands) {
|
||||
fileContent += `\n\n===== influxdb3 ${cmd} --help =====\n`;
|
||||
const cmdHelp = await this.runCommand('docker', [
|
||||
'exec',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
cmd,
|
||||
'--help',
|
||||
]);
|
||||
fileContent += cmdHelp.code === 0 ? cmdHelp.stdout : cmdHelp.stderr;
|
||||
// Generate comprehensive help output
|
||||
let fileContent = `===== influxdb3 --help =====\n`;
|
||||
|
||||
// Add root command help first
|
||||
const rootCommand = this.discoveredCommands.get('influxdb3');
|
||||
if (rootCommand) {
|
||||
fileContent += rootCommand.helpText;
|
||||
}
|
||||
|
||||
// Extract detailed subcommand help
|
||||
for (const subcmd of this.subcommands) {
|
||||
fileContent += `\n\n===== influxdb3 ${subcmd} --help =====\n`;
|
||||
const cmdParts = [
|
||||
'exec',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
...subcmd.split(' '),
|
||||
'--help',
|
||||
];
|
||||
const subcmdHelp = await this.runCommand('docker', cmdParts);
|
||||
fileContent +=
|
||||
subcmdHelp.code === 0 ? subcmdHelp.stdout : subcmdHelp.stderr;
|
||||
// Add all other discovered command help
|
||||
for (const [command, info] of this.discoveredCommands) {
|
||||
if (command !== 'influxdb3') {
|
||||
fileContent += `\n\n===== ${command} --help =====\n`;
|
||||
fileContent += info.helpText;
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(outputFile, fileContent);
|
||||
|
|
@ -233,7 +408,8 @@ class CLIDocAuditor {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Extract help from specific version
|
||||
// For version-specific images, we'll use a simpler approach
|
||||
// since we can't easily discover commands without a running container
|
||||
let fileContent = '';
|
||||
|
||||
// Main help
|
||||
|
|
@ -246,8 +422,12 @@ class CLIDocAuditor {
|
|||
]);
|
||||
fileContent += mainHelp.code === 0 ? mainHelp.stdout : mainHelp.stderr;
|
||||
|
||||
// Extract subcommand help
|
||||
for (const cmd of this.mainCommands) {
|
||||
// Parse main commands and get their help
|
||||
const mainCommands = this.parseCommandsFromHelp(
|
||||
mainHelp.code === 0 ? mainHelp.stdout : mainHelp.stderr
|
||||
);
|
||||
|
||||
for (const cmd of mainCommands) {
|
||||
fileContent += `\n\n===== influxdb3 ${cmd} --help =====\n`;
|
||||
const cmdHelp = await this.runCommand('docker', [
|
||||
'run',
|
||||
|
|
@ -258,6 +438,25 @@ class CLIDocAuditor {
|
|||
'--help',
|
||||
]);
|
||||
fileContent += cmdHelp.code === 0 ? cmdHelp.stdout : cmdHelp.stderr;
|
||||
|
||||
// Try to get subcommands
|
||||
const subcommands = this.parseCommandsFromHelp(
|
||||
cmdHelp.code === 0 ? cmdHelp.stdout : cmdHelp.stderr
|
||||
);
|
||||
|
||||
for (const subcmd of subcommands) {
|
||||
fileContent += `\n\n===== influxdb3 ${cmd} ${subcmd} --help =====\n`;
|
||||
const subcmdHelp = await this.runCommand('docker', [
|
||||
'run',
|
||||
'--rm',
|
||||
image,
|
||||
'influxdb3',
|
||||
cmd,
|
||||
subcmd,
|
||||
'--help',
|
||||
]);
|
||||
fileContent += subcmdHelp.code === 0 ? subcmdHelp.stdout : subcmdHelp.stderr;
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(outputFile, fileContent);
|
||||
|
|
@ -284,8 +483,10 @@ class CLIDocAuditor {
|
|||
.trim();
|
||||
output += `## ${currentCommand}\n\n`;
|
||||
inOptions = false;
|
||||
// Initialize options list for this command
|
||||
this.commandOptionsMap[currentCommand] = [];
|
||||
// Initialize options list for this command if not exists
|
||||
if (!this.commandOptionsMap[currentCommand]) {
|
||||
this.commandOptionsMap[currentCommand] = [];
|
||||
}
|
||||
}
|
||||
// Detect options sections
|
||||
else if (line.trim() === 'Options:') {
|
||||
|
|
@ -343,7 +544,7 @@ class CLIDocAuditor {
|
|||
const lines = content.split('\n');
|
||||
let inCommand = false;
|
||||
let helpText = [];
|
||||
const commandHeader = `===== influxdb3 ${command} --help =====`;
|
||||
const commandHeader = `===== influxdb3 ${command} --help`;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i] === commandHeader) {
|
||||
|
|
@ -361,7 +562,7 @@ class CLIDocAuditor {
|
|||
return helpText.join('\n').trim();
|
||||
}
|
||||
|
||||
async generateDocumentationTemplate(command, helpText) {
|
||||
async generateDocumentationTemplate(command, helpText, product) {
|
||||
// Parse the help text to extract description and options
|
||||
const lines = helpText.split('\n');
|
||||
let description = '';
|
||||
|
|
@ -402,14 +603,18 @@ class CLIDocAuditor {
|
|||
}
|
||||
}
|
||||
|
||||
// Generate product-specific frontmatter
|
||||
const productTag = product === 'enterprise' ? 'influxdb3/enterprise' : 'influxdb3/core';
|
||||
const menuRef = product === 'enterprise' ? 'influxdb3_enterprise_reference' : 'influxdb3_core_reference';
|
||||
|
||||
// Generate markdown template
|
||||
let template = `---
|
||||
title: influxdb3 ${command}
|
||||
description: >
|
||||
The \`influxdb3 ${command}\` command ${description.toLowerCase()}.
|
||||
influxdb3/core/tags: [cli]
|
||||
${productTag}/tags: [cli]
|
||||
menu:
|
||||
influxdb3_core_reference:
|
||||
${menuRef}:
|
||||
parent: influxdb3 cli
|
||||
weight: 201
|
||||
---
|
||||
|
|
@ -587,22 +792,8 @@ Replace the following:
|
|||
let missingCount = 0;
|
||||
const missingDocs = [];
|
||||
|
||||
// Map commands to expected documentation files
|
||||
const commandToFile = {
|
||||
'create database': 'create/database.md',
|
||||
'create token': 'create/token/_index.md',
|
||||
'create token admin': 'create/token/admin.md',
|
||||
'create trigger': 'create/trigger.md',
|
||||
'create table': 'create/table.md',
|
||||
'create last_cache': 'create/last_cache.md',
|
||||
'create distinct_cache': 'create/distinct_cache.md',
|
||||
'show databases': 'show/databases.md',
|
||||
'show tokens': 'show/tokens.md',
|
||||
'delete database': 'delete/database.md',
|
||||
'delete table': 'delete/table.md',
|
||||
query: 'query.md',
|
||||
write: 'write.md',
|
||||
};
|
||||
// Build command to file mapping dynamically from discovered commands
|
||||
const commandToFile = this.buildCommandToFileMapping();
|
||||
|
||||
// Extract commands from CLI help
|
||||
const content = await fs.readFile(cliFile, 'utf8');
|
||||
|
|
@ -666,7 +857,8 @@ Replace the following:
|
|||
const helpText = await this.extractCommandHelp(content, command);
|
||||
const docTemplate = await this.generateDocumentationTemplate(
|
||||
command,
|
||||
helpText
|
||||
helpText,
|
||||
product
|
||||
);
|
||||
|
||||
// Save patch file
|
||||
|
|
@ -847,6 +1039,54 @@ Replace the following:
|
|||
}
|
||||
}
|
||||
|
||||
buildCommandToFileMapping() {
|
||||
// Build a mapping from discovered commands to expected documentation files
|
||||
const mapping = {};
|
||||
|
||||
// Common patterns for command to file mapping
|
||||
const patterns = {
|
||||
'create database': 'create/database.md',
|
||||
'create token': 'create/token/_index.md',
|
||||
'create token admin': 'create/token/admin.md',
|
||||
'create trigger': 'create/trigger.md',
|
||||
'create table': 'create/table.md',
|
||||
'create last_cache': 'create/last_cache.md',
|
||||
'create distinct_cache': 'create/distinct_cache.md',
|
||||
'show databases': 'show/databases.md',
|
||||
'show tokens': 'show/tokens.md',
|
||||
'show system': 'show/system.md',
|
||||
'delete database': 'delete/database.md',
|
||||
'delete table': 'delete/table.md',
|
||||
'delete trigger': 'delete/trigger.md',
|
||||
'update database': 'update/database.md',
|
||||
'test wal_plugin': 'test/wal_plugin.md',
|
||||
'test schedule_plugin': 'test/schedule_plugin.md',
|
||||
query: 'query.md',
|
||||
write: 'write.md',
|
||||
};
|
||||
|
||||
// Add discovered commands that match patterns
|
||||
for (const [command, info] of this.discoveredCommands) {
|
||||
const cleanCommand = command.replace('influxdb3 ', '');
|
||||
if (patterns[cleanCommand]) {
|
||||
mapping[cleanCommand] = patterns[cleanCommand];
|
||||
} else if (cleanCommand !== '' && cleanCommand.includes(' ')) {
|
||||
// Generate file path for subcommands
|
||||
const parts = cleanCommand.split(' ');
|
||||
if (parts.length === 2) {
|
||||
mapping[cleanCommand] = `${parts[0]}/${parts[1]}.md`;
|
||||
} else if (parts.length === 3) {
|
||||
mapping[cleanCommand] = `${parts[0]}/${parts[1]}/${parts[2]}.md`;
|
||||
}
|
||||
} else if (cleanCommand !== '' && !cleanCommand.includes(' ')) {
|
||||
// Single command
|
||||
mapping[cleanCommand] = `${cleanCommand}.md`;
|
||||
}
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
async run() {
|
||||
console.log(
|
||||
`${Colors.BLUE}🔍 InfluxDB 3 CLI Documentation Audit${Colors.NC}`
|
||||
|
|
|
|||
Loading…
Reference in New Issue