#!/usr/bin/env node /** * Apply CLI documentation patches generated by audit-cli-documentation.js * Usage: node apply-cli-patches.js [core|enterprise|both] [--dry-run] */ import { promises as fs } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { process } from 'node:process'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Color codes const Colors = { RED: '\x1b[0;31m', GREEN: '\x1b[0;32m', YELLOW: '\x1b[1;33m', BLUE: '\x1b[0;34m', NC: '\x1b[0m', // No Color }; async function fileExists(path) { try { await fs.access(path); return true; } catch { return false; } } async function ensureDir(dir) { await fs.mkdir(dir, { recursive: true }); } async function extractFrontmatter(content) { const lines = content.split('\n'); if (lines[0] !== '---') return { frontmatter: null, content }; const frontmatterLines = []; let i = 1; while (i < lines.length && lines[i] !== '---') { frontmatterLines.push(lines[i]); i++; } if (i >= lines.length) return { frontmatter: null, content }; const frontmatterText = frontmatterLines.join('\n'); const remainingContent = lines.slice(i + 1).join('\n'); return { frontmatter: frontmatterText, content: remainingContent }; } async function getActualDocumentationPath(docPath, projectRoot) { // Check if the documentation file exists and has a source field const fullPath = join(projectRoot, docPath); if (await fileExists(fullPath)) { const content = await fs.readFile(fullPath, 'utf8'); const { frontmatter } = await extractFrontmatter(content); if (frontmatter) { // Look for source: field in frontmatter const sourceMatch = frontmatter.match(/^source:\s*(.+)$/m); if (sourceMatch) { const sourcePath = sourceMatch[1].trim(); return sourcePath; } } } return docPath; } async function applyPatches(product, dryRun = false) { const patchDir = join( dirname(__dirname), 'output', 'cli-audit', 'patches', product ); const projectRoot = join(__dirname, '..', '..'); console.log( `${Colors.BLUE}📋 Applying CLI documentation patches for ${product}${Colors.NC}` ); if (dryRun) { console.log( `${Colors.YELLOW}🔍 DRY RUN - No files will be created${Colors.NC}` ); } console.log(); // Check if patch directory exists if (!(await fileExists(patchDir))) { console.log(`${Colors.YELLOW}No patches found for ${product}.${Colors.NC}`); console.log("Run 'yarn audit:cli' first to generate patches."); return; } // Read all patch files const patchFiles = await fs.readdir(patchDir); const mdFiles = patchFiles.filter((f) => f.endsWith('.md')); if (mdFiles.length === 0) { console.log( `${Colors.YELLOW}No patch files found in ${patchDir}${Colors.NC}` ); return; } console.log(`Found ${mdFiles.length} patch file(s) to apply:\n`); // Map patch files to their destination const baseCliPath = `content/influxdb3/${product}/reference/cli/influxdb3`; const commandToFile = { 'create-database.md': `${baseCliPath}/create/database.md`, 'create-token.md': `${baseCliPath}/create/token/_index.md`, 'create-token-admin.md': `${baseCliPath}/create/token/admin.md`, 'create-trigger.md': `${baseCliPath}/create/trigger.md`, 'create-table.md': `${baseCliPath}/create/table.md`, 'create-last_cache.md': `${baseCliPath}/create/last_cache.md`, 'create-distinct_cache.md': `${baseCliPath}/create/distinct_cache.md`, 'show-databases.md': `${baseCliPath}/show/databases.md`, 'show-tokens.md': `${baseCliPath}/show/tokens.md`, 'delete-database.md': `${baseCliPath}/delete/database.md`, 'delete-table.md': `${baseCliPath}/delete/table.md`, 'query.md': `${baseCliPath}/query.md`, 'write.md': `${baseCliPath}/write.md`, }; let applied = 0; let skipped = 0; for (const patchFile of mdFiles) { const destinationPath = commandToFile[patchFile]; if (!destinationPath) { console.log( `${Colors.YELLOW}⚠️ Unknown patch file: ${patchFile}${Colors.NC}` ); continue; } // Get the actual documentation path (handles source: frontmatter) const actualPath = await getActualDocumentationPath( destinationPath, projectRoot ); const fullDestPath = join(projectRoot, actualPath); const patchPath = join(patchDir, patchFile); // Check if destination already exists if (await fileExists(fullDestPath)) { console.log( `${Colors.YELLOW}⏭️ Skipping${Colors.NC} ${patchFile} - destination already exists:` ); console.log(` ${actualPath}`); skipped++; continue; } if (dryRun) { console.log(`${Colors.BLUE}🔍 Would create${Colors.NC} ${actualPath}`); console.log(` from patch: ${patchFile}`); if (actualPath !== destinationPath) { console.log(` (resolved from: ${destinationPath})`); } applied++; } else { try { // Ensure destination directory exists await ensureDir(dirname(fullDestPath)); // Copy patch to destination const content = await fs.readFile(patchPath, 'utf8'); // Update the menu configuration based on product let updatedContent = content; if (product === 'enterprise') { updatedContent = content .replace('influxdb3/core/tags:', 'influxdb3/enterprise/tags:') .replace( 'influxdb3_core_reference:', 'influxdb3_enterprise_reference:' ); } await fs.writeFile(fullDestPath, updatedContent); console.log(`${Colors.GREEN}✅ Created${Colors.NC} ${actualPath}`); console.log(` from patch: ${patchFile}`); if (actualPath !== destinationPath) { console.log(` (resolved from: ${destinationPath})`); } applied++; } catch (error) { console.log( `${Colors.RED}❌ Error${Colors.NC} creating ${actualPath}:` ); console.log(` ${error.message}`); } } } console.log(); console.log(`${Colors.BLUE}Summary:${Colors.NC}`); console.log(`- Patches ${dryRun ? 'would be' : ''} applied: ${applied}`); console.log(`- Files skipped (already exist): ${skipped}`); console.log(`- Total patch files: ${mdFiles.length}`); if (!dryRun && applied > 0) { console.log(); console.log( `${Colors.GREEN}✨ Success!${Colors.NC} Created ${applied} new ` + 'documentation file(s).' ); console.log(); console.log('Next steps:'); console.log('1. Review the generated files and customize the content'); console.log('2. Add proper examples with placeholders'); console.log('3. Update descriptions and add any missing options'); console.log('4. Run tests: yarn test:links'); } } async function main() { const args = process.argv.slice(2); const product = args.find((arg) => ['core', 'enterprise', 'both'].includes(arg)) || 'both'; const dryRun = args.includes('--dry-run'); if (args.includes('--help') || args.includes('-h')) { console.log( 'Usage: node apply-cli-patches.js [core|enterprise|both] [--dry-run]' ); console.log(); console.log('Options:'); console.log( ' --dry-run Show what would be done without creating files' ); console.log(); console.log('Examples:'); console.log( ' node apply-cli-patches.js # Apply patches for both products' ); console.log( ' node apply-cli-patches.js core --dry-run # Preview core patches' ); console.log( ' node apply-cli-patches.js enterprise # Apply enterprise patches' ); process.exit(0); } try { if (product === 'both') { await applyPatches('core', dryRun); console.log(); await applyPatches('enterprise', dryRun); } else { await applyPatches(product, dryRun); } } catch (error) { console.error(`${Colors.RED}Error:${Colors.NC}`, error.message); process.exit(1); } } // Run if called directly if (import.meta.url === `file://${process.argv[1]}`) { main(); }