#!/usr/bin/env node /** * Transforms plugin READMEs from influxdb3_plugins to docs-v2 format. * Maintains consistency while applying documentation-specific enhancements. */ import { promises as fs } from 'fs'; import path from 'path'; import yaml from 'js-yaml'; /** * Load the mapping configuration file. */ async function loadMappingConfig(configPath = 'docs_mapping.yaml') { try { const content = await fs.readFile(configPath, 'utf8'); return yaml.load(content); } catch (error) { if (error.code === 'ENOENT') { console.error(`āŒ Error: Configuration file '${configPath}' not found`); } else { console.error(`āŒ Error parsing YAML configuration: ${error.message}`); } process.exit(1); } } /** * Remove the emoji metadata line from content. */ function removeEmojiMetadata(content) { // Remove the emoji line (it's already in the plugin's JSON metadata) const pattern = /^⚔.*?šŸ”§.*?$\n*/gm; return content.replace(pattern, ''); } /** * Convert relative links to GitHub URLs. */ function convertRelativeLinks(content, pluginName) { const baseUrl = `https://github.com/influxdata/influxdb3_plugins/blob/master/influxdata/${pluginName}/`; // Convert TOML file links content = content.replace( /\[([^\]]+\.toml)\]\(([^)]+\.toml)\)/g, (match, linkText, linkPath) => `[${linkText}](${baseUrl}${linkPath})` ); // Convert Python file links content = content.replace( /\[([^\]]+\.py)\]\(([^)]+\.py)\)/g, (match, linkText, linkPath) => `[${linkText}](${baseUrl}${linkPath})` ); // Convert main README reference content = content.replace( '[influxdb3_plugins/README.md](/README.md)', '[influxdb3_plugins/README.md](https://github.com/influxdata/influxdb3_plugins/blob/master/README.md)' ); return content; } /** * Replace product references with Hugo shortcodes. */ function addProductShortcodes(content) { // Replace various forms of InfluxDB 3 references const replacements = [ [/InfluxDB 3 Core\/Enterprise/g, '{{% product-name %}}'], [/InfluxDB 3 Core and InfluxDB 3 Enterprise/g, '{{% product-name %}}'], [/InfluxDB 3 Core, InfluxDB 3 Enterprise/g, '{{% product-name %}}'], // Be careful not to replace in URLs or code blocks [/(? [!WARNING] > #### Requires existing schema > > By design, the plugin returns an error if the schema doesn't exist or doesn't contain the expected columns. `; } else if (pluginName === 'downsampler') { schemaSection = `## Schema management Each downsampled record includes three additional metadata columns: - \`record_count\` — the number of original points compressed into this single downsampled row - \`time_from\` — the minimum timestamp among the original points in the interval - \`time_to\` — the maximum timestamp among the original points in the interval `; } else { return content; } // Insert after Configuration section if (content.includes('## Installation steps')) { content = content.replace( '## Installation steps', schemaSection + '\n## Installation steps' ); } return content; } /** * Apply all transformations to convert README for docs-v2. */ function transformContent(content, pluginName, config) { // Apply transformations in order content = removeEmojiMetadata(content); content = convertRelativeLinks(content, pluginName); content = addProductShortcodes(content); content = enhanceOpeningParagraph(content); content = fixCodeBlockFormatting(content); // Add schema requirements if applicable if ( config.additional_sections && config.additional_sections.includes('schema_requirements') ) { content = addSchemaRequirements(content, pluginName); } // Add logging section content = addLoggingSection(content); // Replace support section content = replaceSupportSection(content); return content; } /** * Process a single plugin README. * Returns true if successful, false otherwise. */ async function processPlugin(pluginName, mapping, dryRun = false) { const sourcePath = mapping.source; const targetPath = mapping.target; try { // Check if source exists await fs.access(sourcePath); } catch (error) { console.error(`āŒ Source not found: ${sourcePath}`); return false; } try { // Read source content const content = await fs.readFile(sourcePath, 'utf8'); // Transform content const transformed = transformContent(content, pluginName, mapping); if (dryRun) { console.log(`āœ… Would process ${pluginName}`); console.log(` Source: ${sourcePath}`); console.log(` Target: ${targetPath}`); return true; } // Ensure target directory exists await fs.mkdir(path.dirname(targetPath), { recursive: true }); // Write transformed content await fs.writeFile(targetPath, transformed, 'utf8'); console.log(`āœ… Processed ${pluginName}`); console.log(` Source: ${sourcePath}`); console.log(` Target: ${targetPath}`); return true; } catch (error) { console.error(`āŒ Error processing ${pluginName}: ${error.message}`); return false; } } /** * Check if docs-v2 repository is accessible. */ async function validateDocsV2Path() { try { await fs.access('../..'); return true; } catch (error) { console.warn('āš ļø Warning: docs-v2 repository structure not detected'); console.warn( ' Make sure you are running this from docs-v2/helper-scripts/influxdb3-plugins' ); return false; } } /** * Parse command line arguments. */ function parseArgs() { const args = process.argv.slice(2); const options = { config: 'docs_mapping.yaml', plugin: null, dryRun: false, validate: false, help: false, }; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case '--config': options.config = args[++i]; break; case '--plugin': options.plugin = args[++i]; break; case '--dry-run': options.dryRun = true; break; case '--validate': options.validate = true; break; case '--help': case '-h': options.help = true; break; default: console.error(`Unknown argument: ${arg}`); options.help = true; break; } } return options; } /** * Show help message. */ function showHelp() { console.log(` Transform plugin READMEs from influxdb3_plugins to docs-v2 format Usage: node port_to_docs.js [options] Options: --config Path to mapping configuration file (default: docs_mapping.yaml) --plugin Process only specified plugin --dry-run Show what would be done without making changes --validate Validate configuration only --help, -h Show this help message Examples: node port_to_docs.js # Process all plugins node port_to_docs.js --plugin basic_transformation # Process specific plugin node port_to_docs.js --dry-run # Preview changes node port_to_docs.js --validate # Check configuration `); } /** * Main transformation function. */ async function main() { const options = parseArgs(); if (options.help) { showHelp(); process.exit(0); } // Load configuration const config = await loadMappingConfig(options.config); if (!config || !config.plugins) { console.error('āŒ Invalid configuration file'); process.exit(1); } // Validate configuration if (options.validate) { console.log('Validating configuration...'); let valid = true; for (const [pluginName, mapping] of Object.entries(config.plugins)) { if (!mapping.source || !mapping.target) { console.error( `āŒ Invalid mapping for ${pluginName}: missing source or target` ); valid = false; continue; } try { await fs.access(mapping.source); } catch (error) { console.warn( `āš ļø Source not found for ${pluginName}: ${mapping.source}` ); } } if (valid) { console.log('āœ… Configuration is valid'); } process.exit(valid ? 0 : 1); } // Check if we're in the right location if (!options.dryRun && !(await validateDocsV2Path())) { console.log( '\nTo use this script, ensure you are in the correct directory:' ); console.log(' cd docs-v2/helper-scripts/influxdb3-plugins'); process.exit(1); } // Process plugins let pluginsToProcess = Object.entries(config.plugins); if (options.plugin) { if (!config.plugins[options.plugin]) { console.error(`āŒ Plugin '${options.plugin}' not found in configuration`); process.exit(1); } pluginsToProcess = [[options.plugin, config.plugins[options.plugin]]]; } console.log( `${options.dryRun ? 'DRY RUN: ' : ''}Processing ${pluginsToProcess.length} plugin(s)...\n` ); let successCount = 0; let errorCount = 0; for (const [pluginName, mapping] of pluginsToProcess) { if (await processPlugin(pluginName, mapping, options.dryRun)) { successCount++; } else { errorCount++; } } // Print summary console.log('\n' + '='.repeat(60)); console.log('TRANSFORMATION SUMMARY'); console.log('='.repeat(60)); console.log(`Successfully processed: ${successCount}`); console.log(`Errors: ${errorCount}`); if (errorCount === 0) { console.log('\nāœ… All plugins processed successfully!'); if (!options.dryRun) { console.log('\nNext steps:'); console.log('1. Review the generated documentation in docs-v2'); console.log('2. Test that all links work correctly'); console.log('3. Verify product shortcodes render properly'); console.log('4. Commit changes in both repositories'); } } else { console.log(`\nāŒ ${errorCount} plugin(s) failed to process`); process.exit(1); } } // Handle unhandled promise rejections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); process.exit(1); }); // Run main function <<<<<<< HEAD if (import.meta.url === `file://${process.argv[1]}`) { ======= if (import.meta.url.endsWith(process.argv[1])) { >>>>>>> origin/master main().catch((error) => { console.error('āŒ Fatal error:', error.message); process.exit(1); }); } export { transformContent, processPlugin, loadMappingConfig };