UNPKG

dir-analysis-tool

Version:

A comprehensive cross-platform CLI tool for advanced directory analysis with file classification, duplicate detection, large file identification, interactive mode, HTML reports, and multiple export formats. Perfect for disk cleanup, storage audits, and pr

635 lines • 29 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InteractiveMode = void 0; const inquirer_1 = __importDefault(require("inquirer")); const chalk_1 = __importDefault(require("chalk")); const analyzer_1 = require("./analyzer"); const progress_1 = require("./progress"); const export_1 = require("./export"); const utils_1 = require("./utils"); const fs_1 = require("fs"); const path = __importStar(require("path")); class InteractiveMode { analyzer; currentResult = null; currentPath = process.cwd(); constructor() { this.analyzer = new analyzer_1.DirectoryAnalyzer(); } async start() { console.log(chalk_1.default.blue.bold('\nšŸš€ Welcome to Interactive Directory Analyzer!')); console.log(chalk_1.default.gray('Explore directories interactively with advanced analysis features.\n')); while (true) { const action = await this.showMainMenu(); try { switch (action) { case 'analyze': await this.analyzeDirectory(); break; case 'change_path': await this.changePath(); break; case 'view_results': await this.viewResults(); break; case 'export': await this.exportResults(); break; case 'advanced': await this.advancedOptions(); break; case 'exit': console.log(chalk_1.default.green('\nšŸ‘‹ Goodbye!')); return; } } catch (error) { console.error(chalk_1.default.red('\nāŒ Error:'), error instanceof Error ? error.message : String(error)); console.log(chalk_1.default.gray('Press any key to continue...')); await inquirer_1.default.prompt([{ type: 'input', name: 'continue', message: '' }]); } } } async showMainMenu() { console.log(chalk_1.default.cyan(`\nšŸ“‚ Current Path: ${this.currentPath}`)); if (this.currentResult) { console.log(chalk_1.default.green(`šŸ“Š Last Analysis: ${this.currentResult.files} files, ${(0, utils_1.formatSize)(this.currentResult.totalSizeBytes)}`)); } const { action } = await inquirer_1.default.prompt([ { type: 'list', name: 'action', message: 'What would you like to do?', choices: [ { name: 'šŸ” Analyze Current Directory', value: 'analyze' }, { name: 'šŸ“ Change Directory', value: 'change_path' }, ...(this.currentResult ? [{ name: 'šŸ“Š View Results', value: 'view_results' }] : []), ...(this.currentResult ? [{ name: 'šŸ’¾ Export Results', value: 'export' }] : []), { name: 'āš™ļø Advanced Options', value: 'advanced' }, { name: '🚪 Exit', value: 'exit' } ] } ]); return action; } async analyzeDirectory() { const options = await this.getAnalysisOptions(); console.log(chalk_1.default.blue('\nšŸ” Starting analysis...')); const progressCallback = progress_1.ProgressBar.createCallback(true); this.currentResult = await this.analyzer.analyze({ path: this.currentPath, recursive: options.recursive ?? true, excludePatterns: options.excludePatterns ?? [], largeSizeThreshold: options.largeSizeThreshold, enableDuplicateDetection: options.enableDuplicateDetection ?? false, progressCallback, maxDepth: options.maxDepth ?? -1, minSize: options.minSize, maxSize: options.maxSize, dateFrom: options.dateFrom, dateTo: options.dateTo, topN: options.topN, showEmptyFiles: options.showEmptyFiles ?? false }); console.log(chalk_1.default.green('āœ… Analysis complete!\n')); await this.displayBasicResults(); } async getAnalysisOptions() { const { features } = await inquirer_1.default.prompt([ { type: 'checkbox', name: 'features', message: 'Select analysis features:', choices: [ { name: 'šŸ”„ Recursive scan', value: 'recursive', checked: true }, { name: 'šŸ“Š File type classification', value: 'types', checked: true }, { name: '🚨 Large file detection', value: 'large_files' }, { name: 'šŸ”„ Duplicate detection', value: 'duplicates' }, { name: 'šŸ“­ Empty file detection', value: 'empty_files' }, { name: '🌳 Tree view generation', value: 'tree_view' }, { name: 'šŸ“ˆ Top largest files', value: 'top_files' } ] } ]); const options = { recursive: features.includes('recursive'), excludePatterns: [] }; if (features.includes('large_files')) { const { threshold } = await inquirer_1.default.prompt([ { type: 'input', name: 'threshold', message: 'Large file threshold (MB):', default: '100', validate: (input) => { const num = parseFloat(input); return num > 0 ? true : 'Please enter a positive number'; } } ]); options.largeSizeThreshold = parseFloat(threshold) * 1024 * 1024; } if (features.includes('duplicates')) { options.enableDuplicateDetection = true; } if (features.includes('empty_files')) { options.showEmptyFiles = true; } if (features.includes('top_files')) { const { topN } = await inquirer_1.default.prompt([ { type: 'input', name: 'topN', message: 'Number of top largest files to show:', default: '10', validate: (input) => { const num = parseInt(input); return num > 0 ? true : 'Please enter a positive number'; } } ]); options.topN = parseInt(topN); } const { useFilters } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'useFilters', message: 'Apply file filters (size, date)?', default: false } ]); if (useFilters) { const filters = await this.getFileFilters(); Object.assign(options, filters); } return options; } async getFileFilters() { const { filterType } = await inquirer_1.default.prompt([ { type: 'checkbox', name: 'filterType', message: 'Select filters:', choices: [ { name: 'šŸ“ Size filters', value: 'size' }, { name: 'šŸ“… Date filters', value: 'date' } ] } ]); const filters = {}; if (filterType.includes('size')) { const { minSize, maxSize } = await inquirer_1.default.prompt([ { type: 'input', name: 'minSize', message: 'Minimum file size (bytes, leave empty for no limit):', validate: (input) => { if (!input) return true; const num = parseInt(input); return num >= 0 ? true : 'Please enter a non-negative number'; } }, { type: 'input', name: 'maxSize', message: 'Maximum file size (bytes, leave empty for no limit):', validate: (input) => { if (!input) return true; const num = parseInt(input); return num >= 0 ? true : 'Please enter a non-negative number'; } } ]); if (minSize) filters.minSize = parseInt(minSize); if (maxSize) filters.maxSize = parseInt(maxSize); } if (filterType.includes('date')) { const { dateFrom, dateTo } = await inquirer_1.default.prompt([ { type: 'input', name: 'dateFrom', message: 'Files modified after (YYYY-MM-DD, leave empty for no limit):', validate: (input) => { if (!input) return true; const date = new Date(input); return !isNaN(date.getTime()) ? true : 'Please enter a valid date (YYYY-MM-DD)'; } }, { type: 'input', name: 'dateTo', message: 'Files modified before (YYYY-MM-DD, leave empty for no limit):', validate: (input) => { if (!input) return true; const date = new Date(input); return !isNaN(date.getTime()) ? true : 'Please enter a valid date (YYYY-MM-DD)'; } } ]); if (dateFrom) filters.dateFrom = new Date(dateFrom); if (dateTo) filters.dateTo = new Date(dateTo); } return filters; } async changePath() { const { newPath } = await inquirer_1.default.prompt([ { type: 'input', name: 'newPath', message: 'Enter new directory path:', default: this.currentPath, validate: async (input) => { try { const stats = await fs_1.promises.stat(input); return stats.isDirectory() ? true : 'Path is not a directory'; } catch { return 'Path does not exist'; } } } ]); this.currentPath = path.resolve(newPath); this.currentResult = null; console.log(chalk_1.default.green(`šŸ“‚ Changed to: ${this.currentPath}`)); } async displayBasicResults() { if (!this.currentResult) return; const result = this.currentResult; console.log(chalk_1.default.blue(`${utils_1.EMOJIS.folder} Directory: ${result.path}`)); console.log(chalk_1.default.green(`${utils_1.EMOJIS.package} Total Size: ${(0, utils_1.formatSize)(result.totalSizeBytes)}`)); console.log(chalk_1.default.yellow(`${utils_1.EMOJIS.folderIcon} Folders: ${result.folders}`)); console.log(chalk_1.default.cyan(`${utils_1.EMOJIS.fileIcon} Files: ${result.files}`)); console.log(chalk_1.default.magenta(`\n${utils_1.EMOJIS.types} File Types:`)); const typeEntries = [ { key: 'code', emoji: utils_1.EMOJIS.code, label: 'Code' }, { key: 'images', emoji: utils_1.EMOJIS.images, label: 'Images' }, { key: 'documents', emoji: utils_1.EMOJIS.documents, label: 'Documents' }, { key: 'videos', emoji: utils_1.EMOJIS.videos, label: 'Videos' }, { key: 'audio', emoji: utils_1.EMOJIS.audio, label: 'Audio' }, { key: 'archives', emoji: utils_1.EMOJIS.archives, label: 'Archives' }, { key: 'other', emoji: utils_1.EMOJIS.other, label: 'Other' } ]; typeEntries.forEach(({ key, emoji, label }) => { const count = result.types[key]; if (count > 0) { console.log(` ${emoji} ${label}: ${count}`); } }); } async viewResults() { if (!this.currentResult) { console.log(chalk_1.default.yellow('No results available. Run an analysis first.')); return; } const { viewType } = await inquirer_1.default.prompt([ { type: 'list', name: 'viewType', message: 'How would you like to view the results?', choices: [ { name: 'šŸ“Š Summary', value: 'summary' }, { name: '🌳 Tree View', value: 'tree' }, { name: '🚨 Large Files', value: 'large_files' }, { name: 'šŸ”„ Duplicates', value: 'duplicates' }, { name: 'šŸ“­ Empty Files', value: 'empty_files' }, { name: 'šŸ“ˆ Top Largest Files', value: 'top_files' }, { name: 'šŸ“‹ Full JSON', value: 'json' } ] } ]); switch (viewType) { case 'summary': await this.displayBasicResults(); break; case 'tree': this.displayTreeView(); break; case 'large_files': this.displayLargeFiles(); break; case 'duplicates': this.displayDuplicates(); break; case 'empty_files': this.displayEmptyFiles(); break; case 'top_files': this.displayTopFiles(); break; case 'json': console.log(JSON.stringify(this.currentResult, null, 2)); break; } console.log(chalk_1.default.gray('\nPress Enter to continue...')); await inquirer_1.default.prompt([{ type: 'input', name: 'continue', message: '' }]); } displayTreeView() { if (!this.currentResult?.treeView) { console.log(chalk_1.default.yellow('Tree view not available (dataset too large or not generated)')); return; } console.log(chalk_1.default.blue('\n🌳 Directory Tree:')); console.log(this.currentResult.treeView); } displayLargeFiles() { if (!this.currentResult?.largeFiles || this.currentResult.largeFiles.length === 0) { console.log(chalk_1.default.yellow('No large files found')); return; } console.log(chalk_1.default.red(`\n🚨 Large Files (${this.currentResult.largeFiles.length}):`)); this.currentResult.largeFiles.forEach((file, index) => { const relativePath = path.relative(this.currentResult.path, file.path); console.log(` ${index + 1}. ${relativePath} - ${chalk_1.default.yellow(file.sizeFormatted)}`); }); } displayDuplicates() { if (!this.currentResult?.duplicateGroups || this.currentResult.duplicateGroups.length === 0) { console.log(chalk_1.default.yellow('No duplicate files found')); return; } console.log(chalk_1.default.yellow(`\nšŸ”„ Duplicate Files (${this.currentResult.duplicateGroups.length} groups):`)); if (this.currentResult.duplicateStats) { console.log(`šŸ’¾ Total wasted space: ${chalk_1.default.red(this.currentResult.duplicateStats.totalWastedSpaceFormatted)}`); } this.currentResult.duplicateGroups.slice(0, 5).forEach((group, index) => { console.log(`\n Group ${index + 1}: ${group.sizeFormatted} each Ɨ ${group.files.length} files`); group.files.slice(0, 3).forEach(file => { const relativePath = path.relative(this.currentResult.path, file); console.log(` šŸ“„ ${relativePath}`); }); if (group.files.length > 3) { console.log(chalk_1.default.gray(` ... and ${group.files.length - 3} more`)); } }); } displayEmptyFiles() { if (!this.currentResult?.emptyFiles || this.currentResult.emptyFiles.length === 0) { console.log(chalk_1.default.yellow('No empty files found')); return; } console.log(chalk_1.default.yellow(`\nšŸ“­ Empty Files (${this.currentResult.emptyFiles.length}):`)); const displayCount = Math.min(10, this.currentResult.emptyFiles.length); this.currentResult.emptyFiles.slice(0, displayCount).forEach(file => { const relativePath = path.relative(this.currentResult.path, file.path); const date = file.modifiedDate.toLocaleDateString(); console.log(` šŸ“„ ${relativePath} ${chalk_1.default.gray(`(modified: ${date})`)}`); }); if (this.currentResult.emptyFiles.length > displayCount) { console.log(chalk_1.default.gray(` ... and ${this.currentResult.emptyFiles.length - displayCount} more`)); } } displayTopFiles() { if (!this.currentResult?.topLargestFiles || this.currentResult.topLargestFiles.length === 0) { console.log(chalk_1.default.yellow('No top files data available')); return; } console.log(chalk_1.default.red(`\nšŸ“ˆ Top ${this.currentResult.topLargestFiles.length} Largest Files:`)); this.currentResult.topLargestFiles.forEach((file, index) => { const relativePath = path.relative(this.currentResult.path, file.path); console.log(` ${index + 1}. ${relativePath} - ${chalk_1.default.yellow(file.sizeFormatted)}`); }); } async exportResults() { if (!this.currentResult) { console.log(chalk_1.default.yellow('No results available. Run an analysis first.')); return; } const { exportType } = await inquirer_1.default.prompt([ { type: 'list', name: 'exportType', message: 'Select export format:', choices: [ { name: 'šŸ“„ CSV - General analysis', value: 'csv' }, { name: 'šŸ“„ CSV - Large files only', value: 'csv_large' }, { name: 'šŸ“„ CSV - Duplicates only', value: 'csv_duplicates' }, { name: 'šŸ“‹ JSON - Full results', value: 'json' } ] } ]); const { filename } = await inquirer_1.default.prompt([ { type: 'input', name: 'filename', message: 'Output filename:', default: this.getDefaultFilename(exportType), validate: (input) => input.trim() ? true : 'Please enter a filename' } ]); try { let content; switch (exportType) { case 'csv': content = export_1.CSVExporter.exportAnalysis(this.currentResult); break; case 'csv_large': if (!this.currentResult.largeFiles || this.currentResult.largeFiles.length === 0) { console.log(chalk_1.default.yellow('No large files to export')); return; } content = export_1.CSVExporter.exportLargeFiles(this.currentResult.largeFiles); break; case 'csv_duplicates': if (!this.currentResult.duplicateGroups || this.currentResult.duplicateGroups.length === 0) { console.log(chalk_1.default.yellow('No duplicates to export')); return; } content = export_1.CSVExporter.exportDuplicates(this.currentResult.duplicateGroups); break; case 'json': content = JSON.stringify(this.currentResult, null, 2); break; default: throw new Error('Unknown export type'); } await fs_1.promises.writeFile(filename, content); console.log(chalk_1.default.green(`āœ… Results exported to: ${filename}`)); } catch (error) { console.error(chalk_1.default.red('Export failed:'), error instanceof Error ? error.message : String(error)); } } getDefaultFilename(exportType) { const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-'); const baseName = `dir-analysis-${timestamp}`; switch (exportType) { case 'csv': return `${baseName}.csv`; case 'csv_large': return `${baseName}-large-files.csv`; case 'csv_duplicates': return `${baseName}-duplicates.csv`; case 'json': return `${baseName}.json`; default: return `${baseName}.txt`; } } async advancedOptions() { const { option } = await inquirer_1.default.prompt([ { type: 'list', name: 'option', message: 'Advanced Options:', choices: [ { name: 'šŸ“Š Compare Two Directories', value: 'compare' }, { name: 'āš™ļø Settings', value: 'settings' }, { name: 'ā“ Help', value: 'help' }, { name: 'šŸ”™ Back to Main Menu', value: 'back' } ] } ]); switch (option) { case 'compare': await this.comparDirectories(); break; case 'settings': await this.showSettings(); break; case 'help': this.showHelp(); break; case 'back': return; } } async comparDirectories() { console.log(chalk_1.default.blue('\nšŸ“Š Directory Comparison')); console.log(chalk_1.default.gray('Compare the current directory with another directory\n')); const { otherPath } = await inquirer_1.default.prompt([ { type: 'input', name: 'otherPath', message: 'Enter path to compare with:', validate: async (input) => { try { const stats = await fs_1.promises.stat(input); return stats.isDirectory() ? true : 'Path is not a directory'; } catch { return 'Path does not exist'; } } } ]); console.log(chalk_1.default.blue('\nšŸ” Analyzing both directories...')); const progressCallback = progress_1.ProgressBar.createCallback(true); const [result1, result2] = await Promise.all([ this.analyzer.analyze({ path: this.currentPath, recursive: true, excludePatterns: [], topN: 10, progressCallback }), this.analyzer.analyze({ path: otherPath, recursive: true, excludePatterns: [], topN: 10, progressCallback }) ]); this.displayComparison(result1, result2); console.log(chalk_1.default.gray('\nPress Enter to continue...')); await inquirer_1.default.prompt([{ type: 'input', name: 'continue', message: '' }]); } displayComparison(result1, result2) { console.log(chalk_1.default.blue('\nšŸ“Š Directory Comparison Results:')); console.log(chalk_1.default.cyan(`\nšŸ“‚ Directory 1: ${result1.path}`)); console.log(` šŸ“¦ Size: ${(0, utils_1.formatSize)(result1.totalSizeBytes)}`); console.log(` šŸ“ Folders: ${result1.folders}`); console.log(` šŸ“„ Files: ${result1.files}`); console.log(chalk_1.default.cyan(`\nšŸ“‚ Directory 2: ${result2.path}`)); console.log(` šŸ“¦ Size: ${(0, utils_1.formatSize)(result2.totalSizeBytes)}`); console.log(` šŸ“ Folders: ${result2.folders}`); console.log(` šŸ“„ Files: ${result2.files}`); console.log(chalk_1.default.yellow('\nšŸ“ˆ Comparison:')); const sizeDiff = result1.totalSizeBytes - result2.totalSizeBytes; const filesDiff = result1.files - result2.files; const foldersDiff = result1.folders - result2.folders; console.log(` šŸ“¦ Size difference: ${sizeDiff >= 0 ? '+' : ''}${(0, utils_1.formatSize)(Math.abs(sizeDiff))} (${sizeDiff >= 0 ? 'Dir1 larger' : 'Dir2 larger'})`); console.log(` šŸ“„ Files difference: ${filesDiff >= 0 ? '+' : ''}${Math.abs(filesDiff)} (${filesDiff >= 0 ? 'Dir1 more' : 'Dir2 more'})`); console.log(` šŸ“ Folders difference: ${foldersDiff >= 0 ? '+' : ''}${Math.abs(foldersDiff)} (${foldersDiff >= 0 ? 'Dir1 more' : 'Dir2 more'})`); } async showSettings() { console.log(chalk_1.default.blue('\nāš™ļø Settings')); console.log(chalk_1.default.gray('Settings are configured per analysis. No persistent settings available in this version.')); console.log(chalk_1.default.gray('\nPress Enter to continue...')); await inquirer_1.default.prompt([{ type: 'input', name: 'continue', message: '' }]); } showHelp() { console.log(chalk_1.default.blue('\nā“ Help - Interactive Directory Analyzer')); console.log(chalk_1.default.gray('═'.repeat(50))); console.log(chalk_1.default.yellow('\nšŸ” Analysis Features:')); console.log(' • Recursive scanning of directories'); console.log(' • File type classification (code, images, documents, etc.)'); console.log(' • Large file detection with configurable thresholds'); console.log(' • Duplicate file detection using MD5 hashing'); console.log(' • Empty file detection'); console.log(' • Tree view visualization'); console.log(' • Top N largest files ranking'); console.log(chalk_1.default.yellow('\nšŸ“Š Filtering Options:')); console.log(' • Size filters (minimum/maximum file size)'); console.log(' • Date filters (modification date range)'); console.log(' • Exclude patterns (file/directory patterns)'); console.log(chalk_1.default.yellow('\nšŸ’¾ Export Options:')); console.log(' • CSV format (general, large files, duplicates)'); console.log(' • JSON format (complete results)'); console.log(' • Custom filename support'); console.log(chalk_1.default.yellow('\nšŸš€ Advanced Features:')); console.log(' • Directory comparison'); console.log(' • Interactive result browsing'); console.log(' • Progress tracking for large analyses'); console.log(chalk_1.default.gray('\nPress Enter to continue...')); } } exports.InteractiveMode = InteractiveMode; //# sourceMappingURL=interactive.js.map