UNPKG

yargs-file-commands

Version:

A yargs helper function that lets you define your commands structure via directory and file naming conventions.

91 lines 4.55 kB
import { readdir } from 'node:fs/promises'; import path, { join } from 'node:path'; /** * System-level ignore patterns that ALWAYS apply regardless of user configuration. * These patterns match system files and hidden files that should never be treated as command files. * This ensures compatibility across macOS (.DS_Store), Linux, and Windows. */ const SYSTEM_IGNORE_PATTERNS = [ /^[.].*/, // Any file or directory starting with a dot (hidden files) ]; /** * Default ignore patterns for directory scanning (optional, can be overridden). * These patterns match common files that should not be treated as command files. * System ignore patterns are always applied in addition to these defaults. */ const DEFAULT_IGNORE_PATTERNS = [ /\.(?:test|spec)\.[jt]s$/, // Test files /__(?:test|spec)__/, // Test directories /\.d\.ts$/, // TypeScript declaration files ]; /** * 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, commandDir, options = {}) => { const { ignorePatterns = DEFAULT_IGNORE_PATTERNS, extensions = ['.js', '.ts'], logLevel = 'info', logPrefix = '', } = options; // Always merge system ignore patterns with user-provided patterns // System patterns are checked first to ensure system files are never included const allIgnorePatterns = [...SYSTEM_IGNORE_PATTERNS, ...ignorePatterns]; try { const entries = await readdir(dirPath, { withFileTypes: true }); // First, filter out entries that match ignore patterns const entriesToProcess = entries.filter((entry) => { const fullPath = join(dirPath, entry.name); const localPath = fullPath.replace(commandDir, ''); // Apply ignore patterns - system patterns are always checked first const matchingPattern = allIgnorePatterns.find((pattern) => pattern.test(localPath)); if (matchingPattern) { if (logLevel === 'debug') { const isSystemPattern = SYSTEM_IGNORE_PATTERNS.some((pattern) => pattern.test(localPath)); // biome-ignore lint/security/noSecrets: This is not a secret, it's a descriptive string for logging const patternType = isSystemPattern ? 'system ignore pattern' : 'ignorePattern'; console.debug(`${logPrefix}${localPath} - ignoring because it matches ${patternType}: ${matchingPattern.toString()}`); } return false; } return true; }); // Process all entries in parallel const entryResults = await Promise.all(entriesToProcess.map(async (entry) => { const fullPath = join(dirPath, entry.name); const localPath = fullPath.replace(commandDir, ''); if (entry.isDirectory()) { if (logLevel === 'debug') { console.debug(`${logPrefix}${localPath} - directory, scanning for commands:`); } return scanDirectory(fullPath, commandDir, { ...options, logPrefix: `${logPrefix} `, }); } 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(', ')}`); } return []; } if (logLevel === 'debug') { console.debug(`${logPrefix}${localPath} - possible command file`); } return [fullPath]; })); // Flatten the results return entryResults.flat(); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`${logPrefix}Failed to scan directory ${dirPath}: ${errorMessage}. Ensure the directory exists and is accessible.`); } }; //# sourceMappingURL=scanDirectory.js.map