loqatevars
Version:
Locate JavaScript files with 'const' or 'process.env' usage in LLM-generated codebases
204 lines (185 loc) • 8.31 kB
JavaScript
/**
* @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();
}