yargs-file-commands
Version:
A yargs helper function that lets you define your commands structure via directory and file naming conventions.
110 lines (98 loc) • 3.14 kB
text/typescript
import { readdir, stat } from 'fs/promises';
import path, { join } from 'path';
/**
* Options for directory scanning
* @interface ScanDirectoryOptions
*/
export interface ScanDirectoryOptions {
/** File extensions to consider when scanning for command files */
extensions?: string[];
/** Regular expressions for patterns to ignore when scanning directories */
ignorePatterns?: RegExp[];
/** Logging verbosity level */
logLevel?: 'info' | 'debug';
/** Prefix for log messages */
logPrefix?: string;
}
/**
* Recursively scans a directory for command files
* @async
* @param {string} dirPath - The directory path to scan
* @param {ScanDirectoryOptions} options - Scanning configuration options
* @returns {Promise<string[]>} Array of full paths to command files
*
* @description
* Performs a recursive directory scan, filtering files based on:
* - Ignore patterns (skips matching files/directories)
* - File extensions (only includes matching files)
* The scan is performed in parallel for better performance.
*/
export const scanDirectory = async (
dirPath: string,
commandDir: string,
options: ScanDirectoryOptions = {}
): Promise<string[]> => {
const {
ignorePatterns = [],
extensions = ['.js', '.ts'],
logLevel = 'info',
logPrefix = ''
} = options;
try {
const entries = await readdir(dirPath);
const commandPaths: string[] = [];
for (const entry of entries) {
const fullPath = join(dirPath, entry);
const localPath = fullPath.replace(commandDir, '');
// apply ignore pattern and early return if matched
const shouldIgnore = ignorePatterns.some((pattern) =>
pattern.test(localPath)
);
if (shouldIgnore) {
if (logLevel === 'debug') {
console.debug(
`${logPrefix}${localPath} - ignoring because it matches ignorePattern: ${ignorePatterns
.filter((pattern) => pattern.test(localPath))
.join(', ')}`
);
}
continue;
}
const stats = await stat(fullPath);
if (stats.isDirectory()) {
if (logLevel === 'debug') {
console.debug(
`${logPrefix}${localPath} - directory, scanning for commands:`
);
}
commandPaths.push(
...(await scanDirectory(fullPath, commandDir, {
...options,
logPrefix: `${logPrefix} `
}))
);
continue;
}
const extension = path.extname(fullPath);
if (!extensions.includes(extension)) {
if (logLevel === 'debug') {
console.debug(
`${logPrefix}${localPath} - ignoring as its extension, ${extension}, doesn't match required extension: ${extensions.join(
', '
)}`
);
}
continue;
}
if (logLevel === 'debug') {
console.debug(`${logPrefix}${localPath} - possible command file`);
}
commandPaths.push(fullPath);
}
return commandPaths;
} catch (error) {
throw new Error(
`${logPrefix}Failed to scan directory ${dirPath}: ${error}`
);
}
};