UNPKG

loqatevars

Version:

Locate JavaScript files with 'const' or 'process.env' usage in LLM-generated codebases

204 lines (185 loc) 8.31 kB
#!/usr/bin/env node /** * @fileoverview This script provides a command-line interface (CLI) for the loqatevars tool. * It allows users to scan directories for specific file patterns, identify files containing * 'const' variables or 'process.env' references, and view detailed analysis reports. * * @author Your Name * @version 1.0.0 */ /** * The `loqatevars` CLI is designed to be a powerful tool for code analysis and maintenance. * It helps developers quickly locate files that may contain environment-specific configurations * or hard-coded constants, which can be critical in large-scale applications. * * The CLI supports two main commands: * - `scan`: Performs a basic scan and returns a list of matching files. * - `detailed`: Provides a more comprehensive analysis, including statistics and reasons for each match. * * The script is built using `yargs` for command-line argument parsing and provides flexible * options for ignoring files, specifying extensions, and enabling debug logging. * * Scalability Considerations: * - The current implementation reads file paths into memory, which may not be ideal for * extremely large directories. Future optimizations could involve streaming file paths * to reduce memory consumption. * - The `detailed` command, in particular, could be memory-intensive if a large number of * files are matched. A more scalable approach might involve processing files in smaller * batches or using a more efficient data structure for storing results. */ // Import scanning functions directly from lib to avoid circular dependency when // index.js also requires this module const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const { findMatchingFiles, findMatchingFilesDetailed } = require('./lib/utils.js'); const { AppError, OperationalError, ProgrammerError } = require('./lib/errors'); const localVars = require('./config/localVars'); /** * The main entry point of the CLI application. This function is responsible for: * 1. Parsing command-line arguments using `yargs`. * 2. Configuring the available commands (`scan`, `detailed`, `help`) and options (`--ignore`, `--extensions`, `--debug`). * 3. Executing the requested command based on user input. * 4. Handling and logging any errors that occur during execution, with specific handling for different error types. * * The function is designed to be asynchronous to support non-blocking I/O operations, * which is crucial for performance in a Node.js environment. */ async function main() { const rawArgs = hideBin(process.argv); const isHelpCommand = rawArgs[0] === 'help'; const yargsInstance = yargs(rawArgs) // set up CLI commands and options /** * The 'scan' command is the default command of the CLI. * It scans the specified directory for files that match the given criteria * and prints a list of matching file paths to the console. * This command is designed for quick and simple analysis. */ .command('scan [directory]', 'Scan directory for files (default command)', (yargs) => { return yargs.positional('directory', { describe: 'Directory to scan', default: process.cwd() }); }) /** * The 'detailed' command provides a more in-depth analysis of the scanned files. * It includes statistics such as the number of scanned files, matching files, * and a list of ignored file patterns. It also provides the reason for each match, * which can be helpful for understanding why a particular file was flagged. */ .command('detailed [directory]', 'Show detailed analysis with statistics', (yargs) => { return yargs.positional('directory', { describe: 'Directory to scan', default: process.cwd() }); }) .command('help', 'Show help') /** * The '--ignore' option allows users to specify a comma-separated list of file patterns to ignore during the scan. * This is useful for excluding irrelevant files, such as configuration files or third-party libraries. * The default value ignores 'localVars.js' files at any depth, which is a common convention for local environment variables. */ .option('ignore', { alias: 'i', describe: 'Comma-separated list of files to ignore', default: '**/localVars.js', // default pattern skips localVars.js at any path depth type: 'string' }) .option('extensions', { alias: 'e', describe: 'Comma-separated list of extensions to scan', default: '.js', type: 'string' }) .option('debug', { describe: 'Enable debug logging', type: 'boolean', default: false }) .help() .alias('help', 'h') .exitProcess(false); const argv = yargsInstance.argv; if (isHelpCommand || argv.help) { // show help and exit yargsInstance.showHelp('log'); if (localVars.NODE_ENV !== 'test') process.exit(0); // exit success outside tests return; } const command = argv._[0] || 'scan'; if (!['scan', 'detailed', 'help'].includes(command)) { console.log(`Unknown command: ${command}`); yargsInstance.showHelp('log'); return; } // Resolve directory argument after parsing to ensure we have a usable path const directory = argv.directory || process.cwd(); // ensure we have a directory to scan // Support repeated --ignore options or comma separated list const ignoreInput = Array.isArray(argv.ignore) ? argv.ignore.join(',') : String(argv.ignore); const ignoreFiles = ignoreInput .split(',') .map(f => String(f).trim()) .filter(f => f); // remove any empty items // Support repeated --extensions options or comma separated list const extInput = Array.isArray(argv.extensions) ? argv.extensions.join(',') : String(argv.extensions); const extensions = extInput .split(',') .map(ext => String(ext).trim()) .filter(ext => ext) .map(ext => ext.startsWith('.') ? ext : `.${ext}`); // normalise extension format if (argv.debug) { require('debug').enable('loqatevars:*'); } try { if (command === 'detailed') { const result = await findMatchingFilesDetailed(directory, ignoreFiles, extensions); console.log('\n=== loqatevars Detailed Analysis ==='); console.log(`Directory: ${directory}`); console.log(`Scanned files: ${result.summary.scannedFiles}`); console.log(`Matching files: ${result.summary.matchingFiles}`); if (result.summary.ignoredFiles.length > 0) { console.log(`Ignored file patterns: ${result.summary.ignoredFiles.join(', ')}`); } console.log('\n--- Matching Files ---'); if (result.matches.length === 0) { console.log('No files found containing "const" variables or "process.env".'); } else { result.matches.forEach(match => { const classification = match.reason; console.log(`- ${match.relativePath} (Reason: ${classification})`); }); console.log('\n--- Concatenated Paths ---'); console.log(result.matches.map(m => m.relativePath).join('\n')); } } else { const matches = await findMatchingFiles(directory, ignoreFiles, extensions); if (matches.length > 0) { console.log(matches.join('\n')); console.log(`\nFound ${matches.length} file${matches.length === 1 ? '' : 's'}`); } else { console.log('No files found containing const or process.env'); } } } catch (error) { if (error instanceof OperationalError) { console.error('Operational Error:', error.message); if (localVars.NODE_ENV !== 'test') process.exit(1); } else if (error instanceof ProgrammerError) { console.error('Programmer Error:', error.message); console.error(error.stack); if (localVars.NODE_ENV !== 'test') process.exit(1); } else if (error instanceof AppError) { console.error('Operational Error:', error.message); if (localVars.NODE_ENV !== 'test') process.exit(1); } else { console.error('Unexpected Error:', error.message); console.error(error.stack); if (localVars.NODE_ENV !== 'test') process.exit(1); } } } module.exports = { main }; if (require.main === module) { main(); }