UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

268 lines (242 loc) 8.42 kB
/** * @file Centralized command-line argument parser for CLI tool consistency * @description Single responsibility: Provide unified argument parsing across all CLI interfaces * * This module implements a centralized argument parsing system that ensures consistent * command-line interfaces across all AgentSqripts CLI tools. It provides common options, * validation patterns, and parsing logic while allowing tool-specific customization * through option merging and override capabilities. * * Design rationale: * - Centralized parsing prevents CLI inconsistencies across tools * - Common options reduce learning curve for users switching between tools * - Extensible design allows tool-specific options while maintaining consistency * - Type coercion and validation prevent common CLI input errors * - Default value system provides sensible out-of-box behavior */ const path = require('path'); /** * Common argument parser configuration with validation and type coercion * * Configuration structure rationale: * - flags: Multiple flag variants (long and short) improve user experience * - parser: Type coercion functions ensure correct data types and validation * - default: Sensible defaults reduce required CLI arguments * - description: Consistent help text across all tools * - boolean: Special handling for boolean flags that don't require values * * Common options design philosophy: * - Help options using standard conventions (--help, -h) * - Output format standardization across all analysis tools * - File extension filtering with intelligent defaults for JavaScript projects * - Verbosity control for debugging and detailed analysis * - Performance controls (max files) for large projects * - Result limiting (max top) for focused output */ const COMMON_OPTIONS = { help: { flags: ['--help', '-h'], description: 'Show help message' }, outputFormat: { flags: ['--output-format', '-f'], values: ['json', 'summary', 'detailed'], default: 'summary', description: 'Output format' }, extensions: { flags: ['--extensions', '--ext'], parser: (value) => value.split(',').map(ext => ext.trim()), default: ['.js', '.ts', '.jsx', '.tsx'], description: 'File extensions to analyze' }, verbose: { flags: ['--verbose', '-v'], boolean: true, description: 'Show detailed output' }, maxFiles: { flags: ['--max-files'], parser: parseInt, description: 'Maximum number of files to analyze' }, maxTop: { flags: ['--max-top'], parser: parseInt, default: 10, description: 'Maximum number of top issues to show' } }; /** * Parse command line arguments with validation and error handling * @param {Array} args - Process arguments array (including node, script.js, ...) * @param {Object} config - Configuration object with defaults and flags * @returns {Object} { options, targetPath, error } - Parsed results or error */ function parseArgs(args = [], config = {}) { const { defaults = {}, flags = {} } = config; const options = { ...defaults }; let targetPath = null; let error = null; // Helper function to convert kebab-case to camelCase function kebabToCamel(str) { return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase()); } // Handle both process.argv format and direct arguments format let cleanArgs; if (args.length >= 2 && (args[0].includes('node') || args[1].includes('.js'))) { // process.argv format: ['node', 'script.js', ...] cleanArgs = args.slice(2); } else { // Direct arguments format: ['.', '--flag', ...] cleanArgs = args; } for (let i = 0; i < cleanArgs.length; i++) { const arg = cleanArgs[i]; // Handle flags if (arg.startsWith('--')) { const rawFlagName = arg.replace('--', ''); const flagName = kebabToCamel(rawFlagName); const flagConfig = flags[flagName]; if (flagConfig) { if (flagConfig.type === 'boolean') { options[flagName] = true; } else if (i + 1 < cleanArgs.length) { const value = cleanArgs[++i]; // Handle list type if (flagConfig.type === 'list') { options[flagName] = value.split(',').map(item => item.trim()); } else { options[flagName] = value; } // Validate if validator provided if (flagConfig.validate && !flagConfig.validate(options[flagName])) { error = `Invalid value for ${flagName}: ${value}`; break; } } else { error = `Missing value for flag: ${arg}`; break; } } else { // Unknown flag, ignore for now if (i + 1 < cleanArgs.length && !cleanArgs[i + 1].startsWith('--')) { i++; // Skip the value too } } } else if (!arg.startsWith('-')) { // Non-flag argument is the target path if (!targetPath) { targetPath = arg; } } } // Check if target path is required when no arguments or only flags provided // But don't require path if help flag is present const hasHelpFlag = cleanArgs.some(arg => arg === '--help' || arg === '-h'); if (!targetPath && cleanArgs.length === 0 && !hasHelpFlag) { error = 'Missing required target path argument'; } return { options, targetPath, error }; } /** * Legacy parseArgs function for backward compatibility * @param {Array} args - Process arguments (process.argv.slice(2)) * @param {Object} toolOptions - Tool-specific options to merge with common ones * @returns {Object} Parsed options and target path */ function parseArgsLegacy(args = [], toolOptions = {}) { const allOptions = { ...COMMON_OPTIONS, ...toolOptions }; const options = {}; // Set defaults Object.entries(allOptions).forEach(([key, config]) => { if (config.default !== undefined) { options[key] = config.default; } if (config.boolean) { options[key] = false; } }); let targetPath = '.'; let mode = 'project'; for (let i = 0; i < args.length; i++) { const arg = args[i]; let handled = false; // Check each option for (const [key, config] of Object.entries(allOptions)) { if (config.flags && config.flags.includes(arg)) { if (config.boolean) { options[key] = true; } else if (i + 1 < args.length) { const value = args[++i]; if (config.parser) { options[key] = config.parser(value); } else if (config.values && config.values.includes(value)) { options[key] = value; } else { options[key] = value; } } handled = true; break; } } // If not an option, it's the target path if (!handled && !arg.startsWith('-')) { targetPath = arg; } } // Determine mode based on target if (targetPath && targetPath !== '.') { try { const stats = require('fs').statSync(targetPath); mode = stats.isDirectory() ? 'project' : 'file'; } catch (error) { // If path doesn't exist yet, default to project mode mode = 'project'; } } return { targetPath: path.resolve(targetPath), mode, options }; } /** * Build help text from options configuration * @param {Object} toolOptions - Tool-specific options * @param {Object} toolInfo - Tool information (name, description, examples) * @returns {string} Help text */ function buildHelpText(toolOptions = {}, toolInfo = {}) { const allOptions = { ...COMMON_OPTIONS, ...toolOptions }; const { name = 'analyze', description = '', examples = [] } = toolInfo; let help = `Usage: ${name} [path] [options]\n\n`; if (description) { help += `${description}\n\n`; } help += 'Options:\n'; Object.entries(allOptions).forEach(([key, config]) => { const flags = config.flags.join(', '); const desc = config.description || ''; const defaultVal = config.default ? ` (default: ${JSON.stringify(config.default)})` : ''; help += ` ${flags.padEnd(25)} ${desc}${defaultVal}\n`; }); if (examples.length > 0) { help += '\nExamples:\n'; examples.forEach(example => { help += ` ${example}\n`; }); } return help; } module.exports = { parseArgs, parseArgsLegacy, buildHelpText, COMMON_OPTIONS };