appclean 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/.github/workflows/publish.yml +41 -0
  2. package/.github/workflows/test.yml +37 -0
  3. package/ACTION_CHECKLIST.md +342 -0
  4. package/APPCLEAN_SUMMARY.md +309 -0
  5. package/CHANGELOG.md +205 -0
  6. package/CODE_OF_CONDUCT.md +49 -0
  7. package/CODE_REVIEW_REPORT.md +447 -0
  8. package/COMMUNITY_POSTS.md +307 -0
  9. package/CONTRIBUTING.md +121 -0
  10. package/DEPLOYMENT_GUIDE.md +345 -0
  11. package/DEPLOYMENT_STATUS.md +182 -0
  12. package/EXECUTIVE_REPORT.md +393 -0
  13. package/GITHUB_OPTIMIZATION.md +383 -0
  14. package/INDEX.md +165 -0
  15. package/LICENSE +21 -0
  16. package/MARKETING_SUMMARY.md +352 -0
  17. package/NPM_PACKAGE_OPTIMIZATION.md +281 -0
  18. package/NPM_PUBLISH.md +116 -0
  19. package/PROJECT_SUMMARY.txt +249 -0
  20. package/QUICKSTART.md +219 -0
  21. package/README.md +548 -0
  22. package/SECURITY.md +104 -0
  23. package/SETUP_GITHUB.md +237 -0
  24. package/TESTING_SUMMARY.md +379 -0
  25. package/dist/core/appUpdateChecker.d.ts +23 -0
  26. package/dist/core/appUpdateChecker.d.ts.map +1 -0
  27. package/dist/core/appUpdateChecker.js +159 -0
  28. package/dist/core/appUpdateChecker.js.map +1 -0
  29. package/dist/core/detector.d.ts +13 -0
  30. package/dist/core/detector.d.ts.map +1 -0
  31. package/dist/core/detector.js +99 -0
  32. package/dist/core/detector.js.map +1 -0
  33. package/dist/core/duplicateFileFinder.d.ts +14 -0
  34. package/dist/core/duplicateFileFinder.d.ts.map +1 -0
  35. package/dist/core/duplicateFileFinder.js +80 -0
  36. package/dist/core/duplicateFileFinder.js.map +1 -0
  37. package/dist/core/orphanedDependencyDetector.d.ts +19 -0
  38. package/dist/core/orphanedDependencyDetector.d.ts.map +1 -0
  39. package/dist/core/orphanedDependencyDetector.js +148 -0
  40. package/dist/core/orphanedDependencyDetector.js.map +1 -0
  41. package/dist/core/performanceOptimizer.d.ts +37 -0
  42. package/dist/core/performanceOptimizer.d.ts.map +1 -0
  43. package/dist/core/performanceOptimizer.js +128 -0
  44. package/dist/core/performanceOptimizer.js.map +1 -0
  45. package/dist/core/permissionHandler.d.ts +9 -0
  46. package/dist/core/permissionHandler.d.ts.map +1 -0
  47. package/dist/core/permissionHandler.js +89 -0
  48. package/dist/core/permissionHandler.js.map +1 -0
  49. package/dist/core/pluginSystem.d.ts +39 -0
  50. package/dist/core/pluginSystem.d.ts.map +1 -0
  51. package/dist/core/pluginSystem.js +120 -0
  52. package/dist/core/pluginSystem.js.map +1 -0
  53. package/dist/core/removalRecorder.d.ts +32 -0
  54. package/dist/core/removalRecorder.d.ts.map +1 -0
  55. package/dist/core/removalRecorder.js +79 -0
  56. package/dist/core/removalRecorder.js.map +1 -0
  57. package/dist/core/remover.d.ts +15 -0
  58. package/dist/core/remover.d.ts.map +1 -0
  59. package/dist/core/remover.js +225 -0
  60. package/dist/core/remover.js.map +1 -0
  61. package/dist/core/reportGenerator.d.ts +9 -0
  62. package/dist/core/reportGenerator.d.ts.map +1 -0
  63. package/dist/core/reportGenerator.js +328 -0
  64. package/dist/core/reportGenerator.js.map +1 -0
  65. package/dist/core/scheduledCleanup.d.ts +38 -0
  66. package/dist/core/scheduledCleanup.d.ts.map +1 -0
  67. package/dist/core/scheduledCleanup.js +127 -0
  68. package/dist/core/scheduledCleanup.js.map +1 -0
  69. package/dist/core/serviceFileDetector.d.ts +18 -0
  70. package/dist/core/serviceFileDetector.d.ts.map +1 -0
  71. package/dist/core/serviceFileDetector.js +136 -0
  72. package/dist/core/serviceFileDetector.js.map +1 -0
  73. package/dist/core/verificationModule.d.ts +14 -0
  74. package/dist/core/verificationModule.d.ts.map +1 -0
  75. package/dist/core/verificationModule.js +102 -0
  76. package/dist/core/verificationModule.js.map +1 -0
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +333 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/managers/brewManager.d.ts +10 -0
  82. package/dist/managers/brewManager.d.ts.map +1 -0
  83. package/dist/managers/brewManager.js +130 -0
  84. package/dist/managers/brewManager.js.map +1 -0
  85. package/dist/managers/customManager.d.ts +8 -0
  86. package/dist/managers/customManager.d.ts.map +1 -0
  87. package/dist/managers/customManager.js +139 -0
  88. package/dist/managers/customManager.js.map +1 -0
  89. package/dist/managers/linuxManager.d.ts +10 -0
  90. package/dist/managers/linuxManager.d.ts.map +1 -0
  91. package/dist/managers/linuxManager.js +191 -0
  92. package/dist/managers/linuxManager.js.map +1 -0
  93. package/dist/managers/npmManager.d.ts +10 -0
  94. package/dist/managers/npmManager.d.ts.map +1 -0
  95. package/dist/managers/npmManager.js +119 -0
  96. package/dist/managers/npmManager.js.map +1 -0
  97. package/dist/types/index.d.ts +44 -0
  98. package/dist/types/index.d.ts.map +1 -0
  99. package/dist/types/index.js +3 -0
  100. package/dist/types/index.js.map +1 -0
  101. package/dist/ui/guiServer.d.ts +10 -0
  102. package/dist/ui/guiServer.d.ts.map +1 -0
  103. package/dist/ui/guiServer.js +134 -0
  104. package/dist/ui/guiServer.js.map +1 -0
  105. package/dist/ui/menu.d.ts +6 -0
  106. package/dist/ui/menu.d.ts.map +1 -0
  107. package/dist/ui/menu.js +93 -0
  108. package/dist/ui/menu.js.map +1 -0
  109. package/dist/ui/prompts.d.ts +13 -0
  110. package/dist/ui/prompts.d.ts.map +1 -0
  111. package/dist/ui/prompts.js +161 -0
  112. package/dist/ui/prompts.js.map +1 -0
  113. package/dist/utils/filesystem.d.ts +13 -0
  114. package/dist/utils/filesystem.d.ts.map +1 -0
  115. package/dist/utils/filesystem.js +152 -0
  116. package/dist/utils/filesystem.js.map +1 -0
  117. package/dist/utils/logger.d.ts +12 -0
  118. package/dist/utils/logger.d.ts.map +1 -0
  119. package/dist/utils/logger.js +49 -0
  120. package/dist/utils/logger.js.map +1 -0
  121. package/dist/utils/platform.d.ts +9 -0
  122. package/dist/utils/platform.d.ts.map +1 -0
  123. package/dist/utils/platform.js +75 -0
  124. package/dist/utils/platform.js.map +1 -0
  125. package/jest.config.js +20 -0
  126. package/logo.svg +60 -0
  127. package/package.json +55 -0
  128. package/setup-github.sh +125 -0
  129. package/src/core/appUpdateChecker.ts +220 -0
  130. package/src/core/detector.ts +133 -0
  131. package/src/core/duplicateFileFinder.ts +113 -0
  132. package/src/core/orphanedDependencyDetector.ts +195 -0
  133. package/src/core/performanceOptimizer.ts +209 -0
  134. package/src/core/permissionHandler.ts +121 -0
  135. package/src/core/pluginSystem.ts +194 -0
  136. package/src/core/removalRecorder.ts +146 -0
  137. package/src/core/remover.ts +280 -0
  138. package/src/core/reportGenerator.ts +354 -0
  139. package/src/core/scheduledCleanup.ts +204 -0
  140. package/src/core/serviceFileDetector.ts +181 -0
  141. package/src/core/verificationModule.ts +140 -0
  142. package/src/index.ts +449 -0
  143. package/src/managers/brewManager.ts +149 -0
  144. package/src/managers/customManager.ts +167 -0
  145. package/src/managers/linuxManager.ts +210 -0
  146. package/src/managers/npmManager.ts +137 -0
  147. package/src/types/index.ts +59 -0
  148. package/src/ui/guiServer.ts +155 -0
  149. package/src/ui/menu.ts +100 -0
  150. package/src/ui/prompts.ts +177 -0
  151. package/src/utils/filesystem.ts +145 -0
  152. package/src/utils/logger.ts +48 -0
  153. package/src/utils/platform.ts +75 -0
  154. package/tsconfig.json +20 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * GUI Server for AppClean
3
+ * Provides web-based GUI interface for macOS, Linux, and Windows
4
+ * v1.2.0 Feature
5
+ */
6
+
7
+ import { createServer } from 'http';
8
+ import { Logger } from '../utils/logger';
9
+
10
+ export class GUIServer {
11
+ private port: number = 3000;
12
+ private server: any;
13
+
14
+ constructor(port: number = 3000) {
15
+ this.port = port;
16
+ }
17
+
18
+ /**
19
+ * Start GUI server
20
+ */
21
+ async start(): Promise<void> {
22
+ Logger.info(`Starting AppClean GUI server on port ${this.port}...`);
23
+
24
+ this.server = createServer((req, res) => {
25
+ if (req.url === '/') {
26
+ res.writeHead(200, { 'Content-Type': 'text/html' });
27
+ res.end(this.getIndexHTML());
28
+ } else if (req.url?.startsWith('/api/')) {
29
+ this.handleAPIRequest(req, res);
30
+ } else {
31
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
32
+ res.end('Not Found');
33
+ }
34
+ });
35
+
36
+ this.server.listen(this.port, () => {
37
+ Logger.success(`GUI server running at http://localhost:${this.port}`);
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Stop GUI server
43
+ */
44
+ async stop(): Promise<void> {
45
+ if (this.server) {
46
+ this.server.close();
47
+ Logger.info('GUI server stopped');
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Handle API requests
53
+ */
54
+ private handleAPIRequest(req: any, res: any): void {
55
+ // API endpoints for GUI
56
+ res.writeHead(200, { 'Content-Type': 'application/json' });
57
+ res.end(JSON.stringify({ message: 'API endpoint' }));
58
+ }
59
+
60
+ /**
61
+ * Get index HTML for GUI
62
+ */
63
+ private getIndexHTML(): string {
64
+ return `
65
+ <!DOCTYPE html>
66
+ <html lang="en">
67
+ <head>
68
+ <meta charset="UTF-8">
69
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
70
+ <title>AppClean GUI - v1.2.0</title>
71
+ <style>
72
+ * {
73
+ margin: 0;
74
+ padding: 0;
75
+ box-sizing: border-box;
76
+ }
77
+ body {
78
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
79
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
80
+ min-height: 100vh;
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ padding: 20px;
85
+ }
86
+ .container {
87
+ background: white;
88
+ border-radius: 12px;
89
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
90
+ max-width: 800px;
91
+ width: 100%;
92
+ padding: 40px;
93
+ }
94
+ h1 {
95
+ color: #333;
96
+ margin-bottom: 10px;
97
+ }
98
+ .version {
99
+ color: #666;
100
+ font-size: 14px;
101
+ margin-bottom: 30px;
102
+ }
103
+ .feature-badge {
104
+ display: inline-block;
105
+ background: #667eea;
106
+ color: white;
107
+ padding: 8px 16px;
108
+ border-radius: 20px;
109
+ font-size: 12px;
110
+ font-weight: 600;
111
+ margin-bottom: 20px;
112
+ }
113
+ p {
114
+ color: #666;
115
+ line-height: 1.6;
116
+ margin-bottom: 20px;
117
+ }
118
+ .coming-soon {
119
+ background: #f0f4ff;
120
+ border-left: 4px solid #667eea;
121
+ padding: 20px;
122
+ border-radius: 4px;
123
+ margin-top: 30px;
124
+ }
125
+ </style>
126
+ </head>
127
+ <body>
128
+ <div class="container">
129
+ <h1>🎨 AppClean GUI</h1>
130
+ <p class="version">v1.2.0 - GUI Application Feature</p>
131
+ <span class="feature-badge">Coming Soon</span>
132
+
133
+ <p>
134
+ A beautiful, intuitive graphical user interface for AppClean, bringing the power of
135
+ intelligent app removal to users who prefer a visual interface.
136
+ </p>
137
+
138
+ <div class="coming-soon">
139
+ <h3>🚀 Features Coming in v1.2.0</h3>
140
+ <ul style="margin-left: 20px; margin-top: 10px;">
141
+ <li>✨ Modern, responsive GUI design</li>
142
+ <li>🖥️ Cross-platform support (macOS, Linux, Windows)</li>
143
+ <li>🔍 Visual app search and discovery</li>
144
+ <li>📊 Beautiful artifact visualization</li>
145
+ <li>🗑️ Drag-and-drop app removal</li>
146
+ <li>📈 Real-time removal progress</li>
147
+ <li>📋 Interactive report viewer</li>
148
+ </ul>
149
+ </div>
150
+ </div>
151
+ </body>
152
+ </html>
153
+ `;
154
+ }
155
+ }
package/src/ui/menu.ts ADDED
@@ -0,0 +1,100 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+
4
+ export type MainMenuAction =
5
+ | 'search'
6
+ | 'browse'
7
+ | 'list-all'
8
+ | 'help'
9
+ | 'exit';
10
+
11
+ export async function showMainMenu(): Promise<MainMenuAction> {
12
+ const { action } = await inquirer.prompt([
13
+ {
14
+ type: 'list',
15
+ name: 'action',
16
+ message: 'What would you like to do?',
17
+ choices: [
18
+ { name: '🔍 Search for an app', value: 'search' },
19
+ { name: '📋 List all installed apps', value: 'list-all' },
20
+ { name: '❓ Help', value: 'help' },
21
+ { name: '❌ Exit', value: 'exit' },
22
+ ],
23
+ },
24
+ ]);
25
+
26
+ return action;
27
+ }
28
+
29
+ export async function showAppMenu(): Promise<'remove' | 'details' | 'back'> {
30
+ const { action } = await inquirer.prompt([
31
+ {
32
+ type: 'list',
33
+ name: 'action',
34
+ message: 'What would you like to do?',
35
+ choices: [
36
+ { name: '📊 View details and artifacts', value: 'details' },
37
+ { name: '🗑️ Remove this app', value: 'remove' },
38
+ { name: '⬅️ Back to search', value: 'back' },
39
+ ],
40
+ },
41
+ ]);
42
+
43
+ return action;
44
+ }
45
+
46
+ export function showHeader(): void {
47
+ console.clear();
48
+ console.log(
49
+ chalk.cyan(`
50
+ ╔═══════════════════════════════════════════════════════════╗
51
+ ║ ║
52
+ ║ ${chalk.bold('AppClean - App Uninstaller')} ║
53
+ ║ Remove apps and all their artifacts ║
54
+ ║ ║
55
+ ╚═══════════════════════════════════════════════════════════╝
56
+ `)
57
+ );
58
+ }
59
+
60
+ export function showHelp(): void {
61
+ console.log(
62
+ chalk.cyan(`
63
+ ${chalk.bold('AppClean - Help')}
64
+
65
+ ${chalk.bold('Features:')}
66
+ • Search and find all installed applications
67
+ • Identify installation method (npm, brew, apt, custom, etc)
68
+ • Locate all related files, configs, and caches
69
+ • Safely remove apps with dry-run preview
70
+ • Create backups before deletion
71
+ • Cross-platform support (macOS, Linux, Windows)
72
+
73
+ ${chalk.bold('Commands:')}
74
+ appclean Start interactive mode
75
+ appclean search <name> Search for an app
76
+ appclean remove <name> Remove an app
77
+ appclean list List all apps
78
+ appclean analyze <name> Show app artifacts
79
+
80
+ ${chalk.bold('Options:')}
81
+ --dry-run Preview without deleting
82
+ --backup Create backup before removal
83
+ --help Show this help message
84
+ --version Show version
85
+
86
+ ${chalk.bold('Examples:')}
87
+ appclean search nodejs Search for nodejs
88
+ appclean remove myapp --dry-run Preview removal
89
+ appclean list | grep npm Find npm-installed apps
90
+
91
+ ${chalk.bold('Safety:')}
92
+ • Always previews what will be deleted
93
+ • Use --dry-run first to see what will happen
94
+ • Use --backup to create backups before deletion
95
+ • Double confirmation before actual removal
96
+
97
+ ${chalk.bold('Website:')} https://github.com/YOUR_USERNAME/appclean
98
+ `)
99
+ );
100
+ }
@@ -0,0 +1,177 @@
1
+ import inquirer from 'inquirer';
2
+ import { InstalledApp, ArtifactPath } from '../types';
3
+ import { Logger, formatBytes } from '../utils/logger';
4
+ import chalk from 'chalk';
5
+
6
+ export async function promptSearchQuery(): Promise<string> {
7
+ const { query } = await inquirer.prompt([
8
+ {
9
+ type: 'input',
10
+ name: 'query',
11
+ message: 'Search for an app (partial name):',
12
+ default: '',
13
+ },
14
+ ]);
15
+ return query;
16
+ }
17
+
18
+ export async function promptSelectApp(apps: InstalledApp[]): Promise<InstalledApp | null> {
19
+ if (apps.length === 0) {
20
+ Logger.warn('No apps found');
21
+ return null;
22
+ }
23
+
24
+ const choices = apps.map((app) => ({
25
+ name: `${app.name} (${app.installMethod}) - v${app.version}`,
26
+ value: app,
27
+ }));
28
+
29
+ const { app } = await inquirer.prompt([
30
+ {
31
+ type: 'list',
32
+ name: 'app',
33
+ message: 'Select an app to remove:',
34
+ choices: [
35
+ ...choices,
36
+ new inquirer.Separator(),
37
+ { name: 'Cancel', value: null },
38
+ ],
39
+ pageSize: 10,
40
+ },
41
+ ]);
42
+
43
+ return app;
44
+ }
45
+
46
+ export async function promptConfirmRemoval(appName: string): Promise<boolean> {
47
+ const { confirmed } = await inquirer.prompt([
48
+ {
49
+ type: 'confirm',
50
+ name: 'confirmed',
51
+ message: chalk.yellow(`Are you sure you want to remove ${appName}?`),
52
+ default: false,
53
+ },
54
+ ]);
55
+ return confirmed;
56
+ }
57
+
58
+ export async function promptRemovalOptions(): Promise<{
59
+ dryRun: boolean;
60
+ createBackup: boolean;
61
+ }> {
62
+ const { options } = await inquirer.prompt([
63
+ {
64
+ type: 'checkbox',
65
+ name: 'options',
66
+ message: 'Removal options:',
67
+ choices: [
68
+ {
69
+ name: 'Dry run (preview without removing)',
70
+ value: 'dryRun',
71
+ checked: true,
72
+ },
73
+ {
74
+ name: 'Create backup before removal',
75
+ value: 'backup',
76
+ checked: false,
77
+ },
78
+ ],
79
+ },
80
+ ]);
81
+
82
+ return {
83
+ dryRun: options.includes('dryRun'),
84
+ createBackup: options.includes('backup'),
85
+ };
86
+ }
87
+
88
+ export async function promptInstallMethodFilter(): Promise<string | null> {
89
+ const { method } = await inquirer.prompt([
90
+ {
91
+ type: 'list',
92
+ name: 'method',
93
+ message: 'Filter by installation method:',
94
+ choices: [
95
+ { name: 'All', value: null },
96
+ { name: 'npm', value: 'npm' },
97
+ { name: 'yarn', value: 'yarn' },
98
+ { name: 'pnpm', value: 'pnpm' },
99
+ { name: 'Homebrew', value: 'brew' },
100
+ { name: 'apt', value: 'apt' },
101
+ { name: 'yum', value: 'yum' },
102
+ { name: 'dnf', value: 'dnf' },
103
+ { name: 'Custom', value: 'custom' },
104
+ ],
105
+ },
106
+ ]);
107
+
108
+ return method;
109
+ }
110
+
111
+ export async function promptSortBy(): Promise<'name' | 'date' | 'size'> {
112
+ const { sortBy } = await inquirer.prompt([
113
+ {
114
+ type: 'list',
115
+ name: 'sortBy',
116
+ message: 'Sort results by:',
117
+ choices: [
118
+ { name: 'Name', value: 'name' },
119
+ { name: 'Installation Date', value: 'date' },
120
+ { name: 'Size', value: 'size' },
121
+ ],
122
+ },
123
+ ]);
124
+
125
+ return sortBy;
126
+ }
127
+
128
+ export function displayAppDetails(app: InstalledApp, artifacts: ArtifactPath[]): void {
129
+ Logger.space();
130
+ Logger.info(`App: ${chalk.cyan(app.name)}`);
131
+ Logger.info(`Method: ${chalk.yellow(app.installMethod)}`);
132
+ Logger.info(`Version: ${app.version}`);
133
+ Logger.info(`Location: ${app.mainPath}`);
134
+ Logger.space();
135
+
136
+ Logger.info('Associated files and directories:');
137
+ Logger.space();
138
+
139
+ let totalSize = 0;
140
+
141
+ for (const artifact of artifacts) {
142
+ const size = artifact.size || 0;
143
+ totalSize += size;
144
+
145
+ const icon =
146
+ artifact.type === 'binary'
147
+ ? '⚙️'
148
+ : artifact.type === 'config'
149
+ ? '⚙️'
150
+ : artifact.type === 'cache'
151
+ ? '📦'
152
+ : '📁';
153
+
154
+ console.log(
155
+ ` ${icon} [${artifact.type.padEnd(7)}] ${formatBytes(size).padEnd(10)} ${artifact.path}`
156
+ );
157
+ }
158
+
159
+ Logger.space();
160
+ Logger.info(`Total space to be freed: ${chalk.green(formatBytes(totalSize))}`);
161
+ Logger.space();
162
+ }
163
+
164
+ export async function promptFinalConfirmation(appName: string): Promise<boolean> {
165
+ const { confirmed } = await inquirer.prompt([
166
+ {
167
+ type: 'confirm',
168
+ name: 'confirmed',
169
+ message: chalk.red(
170
+ `This action cannot be undone. Remove ${appName} and all its files?`
171
+ ),
172
+ default: false,
173
+ },
174
+ ]);
175
+
176
+ return confirmed;
177
+ }
@@ -0,0 +1,145 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export function getFileSize(filePath: string): number {
5
+ try {
6
+ const stats = fs.statSync(filePath);
7
+ if (stats.isDirectory()) {
8
+ return getDirectorySize(filePath);
9
+ }
10
+ return stats.size;
11
+ } catch {
12
+ return 0;
13
+ }
14
+ }
15
+
16
+ export function getDirectorySize(dirPath: string): number {
17
+ try {
18
+ let size = 0;
19
+ const files = fs.readdirSync(dirPath, { withFileTypes: true });
20
+
21
+ for (const file of files) {
22
+ const fullPath = path.join(dirPath, file.name);
23
+ if (file.isDirectory()) {
24
+ size += getDirectorySize(fullPath);
25
+ } else {
26
+ const stats = fs.statSync(fullPath);
27
+ size += stats.size;
28
+ }
29
+ }
30
+ return size;
31
+ } catch {
32
+ return 0;
33
+ }
34
+ }
35
+
36
+ export function pathExists(filePath: string): boolean {
37
+ return fs.existsSync(filePath);
38
+ }
39
+
40
+ export function isDirectory(filePath: string): boolean {
41
+ try {
42
+ return fs.statSync(filePath).isDirectory();
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ export function isFile(filePath: string): boolean {
49
+ try {
50
+ return fs.statSync(filePath).isFile();
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ export function listDirectory(dirPath: string): string[] {
57
+ try {
58
+ return fs.readdirSync(dirPath);
59
+ } catch {
60
+ return [];
61
+ }
62
+ }
63
+
64
+ export function listDirectoryDeep(
65
+ dirPath: string,
66
+ maxDepth: number = 5,
67
+ currentDepth: number = 0
68
+ ): string[] {
69
+ const files: string[] = [];
70
+
71
+ if (currentDepth >= maxDepth) return files;
72
+
73
+ try {
74
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
75
+
76
+ for (const entry of entries) {
77
+ const fullPath = path.join(dirPath, entry.name);
78
+
79
+ try {
80
+ // Skip symlinks and permission issues
81
+ if (entry.isSymbolicLink()) continue;
82
+
83
+ files.push(fullPath);
84
+
85
+ if (entry.isDirectory() && currentDepth < maxDepth - 1) {
86
+ files.push(...listDirectoryDeep(fullPath, maxDepth, currentDepth + 1));
87
+ }
88
+ } catch {
89
+ // Skip entries that can't be accessed
90
+ continue;
91
+ }
92
+ }
93
+ } catch {
94
+ return files;
95
+ }
96
+
97
+ return files;
98
+ }
99
+
100
+ export function deleteFile(filePath: string): boolean {
101
+ try {
102
+ fs.unlinkSync(filePath);
103
+ return true;
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ export function deleteDirectory(dirPath: string): boolean {
110
+ try {
111
+ fs.rmSync(dirPath, { recursive: true, force: true });
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ export function readFile(filePath: string): string | null {
119
+ try {
120
+ return fs.readFileSync(filePath, 'utf-8');
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+
126
+ export function writeFile(filePath: string, content: string): boolean {
127
+ try {
128
+ const dir = path.dirname(filePath);
129
+ if (!fs.existsSync(dir)) {
130
+ fs.mkdirSync(dir, { recursive: true });
131
+ }
132
+ fs.writeFileSync(filePath, content, 'utf-8');
133
+ return true;
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+
139
+ export function getModificationTime(filePath: string): Date | null {
140
+ try {
141
+ return fs.statSync(filePath).mtime;
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
@@ -0,0 +1,48 @@
1
+ import chalk from 'chalk';
2
+
3
+ export class Logger {
4
+ static info(message: string): void {
5
+ console.log(chalk.blue('ℹ'), message);
6
+ }
7
+
8
+ static success(message: string): void {
9
+ console.log(chalk.green('✓'), message);
10
+ }
11
+
12
+ static warn(message: string): void {
13
+ console.log(chalk.yellow('⚠'), message);
14
+ }
15
+
16
+ static error(message: string): void {
17
+ console.log(chalk.red('✗'), message);
18
+ }
19
+
20
+ static debug(message: string): void {
21
+ if (process.env.DEBUG) {
22
+ console.log(chalk.gray('🐛'), message);
23
+ }
24
+ }
25
+
26
+ static table(data: any[]): void {
27
+ console.table(data);
28
+ }
29
+
30
+ static space(): void {
31
+ console.log('');
32
+ }
33
+ }
34
+
35
+ export function formatBytes(bytes: number): string {
36
+ if (bytes === 0) return '0 B';
37
+
38
+ const k = 1024;
39
+ const sizes = ['B', 'KB', 'MB', 'GB'];
40
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
41
+
42
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
43
+ }
44
+
45
+ export function formatDate(date: Date | undefined): string {
46
+ if (!date) return 'Unknown';
47
+ return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
48
+ }
@@ -0,0 +1,75 @@
1
+ import os from 'os';
2
+ import { Platform } from '../types';
3
+
4
+ export function getPlatform(): Platform {
5
+ return (os.platform() as Platform);
6
+ }
7
+
8
+ export function isMacOS(): boolean {
9
+ return getPlatform() === 'darwin';
10
+ }
11
+
12
+ export function isLinux(): boolean {
13
+ return getPlatform() === 'linux';
14
+ }
15
+
16
+ export function isWindows(): boolean {
17
+ return getPlatform() === 'win32';
18
+ }
19
+
20
+ export function getHomeDir(): string {
21
+ return os.homedir();
22
+ }
23
+
24
+ export function getConfigDir(): string {
25
+ const platform = getPlatform();
26
+ const home = getHomeDir();
27
+
28
+ switch (platform) {
29
+ case 'darwin':
30
+ return home;
31
+ case 'linux':
32
+ return home;
33
+ case 'win32':
34
+ return process.env.APPDATA || home;
35
+ default:
36
+ return home;
37
+ }
38
+ }
39
+
40
+ export function getCommonPaths(subpath: string): string[] {
41
+ const platform = getPlatform();
42
+ const home = getHomeDir();
43
+ const paths: string[] = [];
44
+
45
+ switch (platform) {
46
+ case 'darwin':
47
+ paths.push(`${home}/.config/${subpath}`);
48
+ paths.push(`${home}/Library/Application Support/${subpath}`);
49
+ paths.push(`${home}/Library/Preferences/${subpath}`);
50
+ paths.push(`${home}/Library/Caches/${subpath}`);
51
+ paths.push(`${home}/Library/Logs/${subpath}`);
52
+ paths.push(`${home}/.${subpath}`);
53
+ break;
54
+
55
+ case 'linux':
56
+ paths.push(`${home}/.config/${subpath}`);
57
+ paths.push(`${home}/.local/share/${subpath}`);
58
+ paths.push(`${home}/.cache/${subpath}`);
59
+ paths.push(`${home}/.${subpath}`);
60
+ paths.push(`/var/log/${subpath}`);
61
+ paths.push(`/usr/local/etc/${subpath}`);
62
+ paths.push(`/etc/${subpath}`);
63
+ break;
64
+
65
+ case 'win32':
66
+ const appData = process.env.APPDATA || `${home}\\AppData\\Roaming`;
67
+ const localAppData = process.env.LOCALAPPDATA || `${home}\\AppData\\Local`;
68
+ paths.push(`${appData}\\${subpath}`);
69
+ paths.push(`${localAppData}\\${subpath}`);
70
+ paths.push(`${home}\\.${subpath}`);
71
+ break;
72
+ }
73
+
74
+ return paths;
75
+ }