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,181 @@
1
+ import path from 'path';
2
+ import { isMacOS, isLinux, getHomeDir } from '../utils/platform';
3
+ import { pathExists, readFile, listDirectory } from '../utils/filesystem';
4
+ import { ArtifactPath } from '../types';
5
+
6
+ export interface ServiceFile {
7
+ path: string;
8
+ type: 'launchagent' | 'launchdaemon' | 'systemd' | 'service';
9
+ appName: string;
10
+ requiresManualCleanup: boolean;
11
+ dependencies?: string[];
12
+ manualCleanupInstructions: string;
13
+ }
14
+
15
+ export class ServiceFileDetector {
16
+ /**
17
+ * Find all service files related to an app
18
+ */
19
+ async findServiceFiles(appName: string): Promise<ServiceFile[]> {
20
+ const serviceFiles: ServiceFile[] = [];
21
+
22
+ if (isMacOS()) {
23
+ serviceFiles.push(...(await this.findMacOSServiceFiles(appName)));
24
+ }
25
+
26
+ if (isLinux()) {
27
+ serviceFiles.push(...(await this.findLinuxServiceFiles(appName)));
28
+ }
29
+
30
+ return serviceFiles;
31
+ }
32
+
33
+ /**
34
+ * Find macOS LaunchAgent and LaunchDaemon files
35
+ */
36
+ private async findMacOSServiceFiles(appName: string): Promise<ServiceFile[]> {
37
+ const home = getHomeDir();
38
+ const serviceFiles: ServiceFile[] = [];
39
+
40
+ // LaunchAgents
41
+ const launchAgentsPath = path.join(home, 'Library', 'LaunchAgents');
42
+ if (pathExists(launchAgentsPath)) {
43
+ const agents = listDirectory(launchAgentsPath);
44
+ for (const agent of agents) {
45
+ if (agent.toLowerCase().includes(appName.toLowerCase())) {
46
+ const fullPath = path.join(launchAgentsPath, agent);
47
+ serviceFiles.push({
48
+ path: fullPath,
49
+ type: 'launchagent',
50
+ appName,
51
+ requiresManualCleanup: this.checkDependencies(fullPath, appName),
52
+ manualCleanupInstructions: `To manually remove: rm "${fullPath}"`,
53
+ });
54
+ }
55
+ }
56
+ }
57
+
58
+ // LaunchDaemons
59
+ const launchDaemonsPath = path.join(home, 'Library', 'LaunchDaemons');
60
+ if (pathExists(launchDaemonsPath)) {
61
+ const daemons = listDirectory(launchDaemonsPath);
62
+ for (const daemon of daemons) {
63
+ if (daemon.toLowerCase().includes(appName.toLowerCase())) {
64
+ const fullPath = path.join(launchDaemonsPath, daemon);
65
+ serviceFiles.push({
66
+ path: fullPath,
67
+ type: 'launchdaemon',
68
+ appName,
69
+ requiresManualCleanup: true, // Daemons usually need manual removal
70
+ manualCleanupInstructions: `To manually remove: sudo rm "${fullPath}" && sudo launchctl unload "${fullPath}"`,
71
+ });
72
+ }
73
+ }
74
+ }
75
+
76
+ return serviceFiles;
77
+ }
78
+
79
+ /**
80
+ * Find Linux systemd service files
81
+ */
82
+ private async findLinuxServiceFiles(appName: string): Promise<ServiceFile[]> {
83
+ const home = getHomeDir();
84
+ const serviceFiles: ServiceFile[] = [];
85
+
86
+ // User systemd services
87
+ const userSystemdPath = path.join(home, '.config', 'systemd', 'user');
88
+ if (pathExists(userSystemdPath)) {
89
+ const services = listDirectory(userSystemdPath);
90
+ for (const service of services) {
91
+ if (service.includes(appName)) {
92
+ const fullPath = path.join(userSystemdPath, service);
93
+ serviceFiles.push({
94
+ path: fullPath,
95
+ type: 'systemd',
96
+ appName,
97
+ requiresManualCleanup: this.checkDependencies(fullPath, appName),
98
+ manualCleanupInstructions: `To manually remove: systemctl --user stop ${service} && rm "${fullPath}" && systemctl --user daemon-reload`,
99
+ });
100
+ }
101
+ }
102
+ }
103
+
104
+ // System-wide services (requires elevation)
105
+ const systemServicePath = '/etc/systemd/system';
106
+ if (pathExists(systemServicePath)) {
107
+ try {
108
+ const services = listDirectory(systemServicePath);
109
+ for (const service of services) {
110
+ if (service.includes(appName)) {
111
+ const fullPath = path.join(systemServicePath, service);
112
+ serviceFiles.push({
113
+ path: fullPath,
114
+ type: 'service',
115
+ appName,
116
+ requiresManualCleanup: true,
117
+ manualCleanupInstructions: `To manually remove: sudo systemctl stop ${service} && sudo rm "${fullPath}" && sudo systemctl daemon-reload`,
118
+ });
119
+ }
120
+ }
121
+ } catch {
122
+ // Permission denied, skip system services
123
+ }
124
+ }
125
+
126
+ return serviceFiles;
127
+ }
128
+
129
+ /**
130
+ * Check if service has dependencies
131
+ */
132
+ private checkDependencies(filePath: string, appName: string): boolean {
133
+ try {
134
+ const content = readFile(filePath);
135
+ if (!content) return false;
136
+
137
+ // Check for KeepAlive, RunAtLoad, or other critical properties
138
+ return (
139
+ content.includes('KeepAlive') ||
140
+ content.includes('RunAtLoad') ||
141
+ content.includes('StartCalendarInterval')
142
+ );
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Convert service files to artifact paths for UI display
150
+ */
151
+ convertToArtifacts(serviceFiles: ServiceFile[]): ArtifactPath[] {
152
+ return serviceFiles.map((file) => ({
153
+ path: file.path,
154
+ type: 'service',
155
+ size: 0,
156
+ description: `${file.type} service file (requires manual cleanup)`,
157
+ }));
158
+ }
159
+
160
+ /**
161
+ * Get manual cleanup instructions
162
+ */
163
+ getManualCleanupInstructions(serviceFiles: ServiceFile[]): string {
164
+ if (serviceFiles.length === 0) {
165
+ return '';
166
+ }
167
+
168
+ let instructions = '\nāš ļø Manual Cleanup Required:\n\n';
169
+ instructions += 'The following service files require manual cleanup:\n\n';
170
+
171
+ serviceFiles.forEach((file, index) => {
172
+ instructions += `${index + 1}. ${file.type.toUpperCase()}\n`;
173
+ instructions += ` Path: ${file.path}\n`;
174
+ instructions += ` Command: ${file.manualCleanupInstructions}\n\n`;
175
+ });
176
+
177
+ instructions += 'Run these commands in your terminal to complete the cleanup.\n';
178
+
179
+ return instructions;
180
+ }
181
+ }
@@ -0,0 +1,140 @@
1
+ import { execSync } from 'child_process';
2
+ import { isMacOS, isLinux, isWindows } from '../utils/platform';
3
+ import { pathExists } from '../utils/filesystem';
4
+
5
+ export type VerificationStatus = 'verified_removed' | 'still_exists' | 'partial_removal' | 'unknown';
6
+
7
+ export interface VerificationResult {
8
+ status: VerificationStatus;
9
+ remainingPaths: string[];
10
+ commandOutput: string;
11
+ timestamp: Date;
12
+ }
13
+
14
+ export class VerificationModule {
15
+ /**
16
+ * Verify if app is completely removed from the system
17
+ */
18
+ async verifyRemoval(
19
+ appName: string,
20
+ artifactPaths: string[]
21
+ ): Promise<VerificationResult> {
22
+ const result: VerificationResult = {
23
+ status: 'unknown',
24
+ remainingPaths: [],
25
+ commandOutput: '',
26
+ timestamp: new Date(),
27
+ };
28
+
29
+ try {
30
+ // Check if artifacts still exist on filesystem
31
+ const remainingPaths = artifactPaths.filter((path) => pathExists(path));
32
+ result.remainingPaths = remainingPaths;
33
+
34
+ // Verify via command line
35
+ const commandStatus = await this.verifyViaCommand(appName);
36
+ result.commandOutput = commandStatus;
37
+
38
+ // Determine overall status
39
+ if (remainingPaths.length === 0 && !commandStatus) {
40
+ result.status = 'verified_removed';
41
+ } else if (remainingPaths.length === 0 && commandStatus) {
42
+ result.status = 'unknown'; // Found via command but not filesystem
43
+ } else if (remainingPaths.length > 0 && remainingPaths.length < artifactPaths.length) {
44
+ result.status = 'partial_removal';
45
+ } else if (remainingPaths.length === artifactPaths.length) {
46
+ result.status = 'still_exists';
47
+ }
48
+ } catch (error) {
49
+ result.status = 'unknown';
50
+ result.commandOutput = (error as Error).message;
51
+ }
52
+
53
+ return result;
54
+ }
55
+
56
+ /**
57
+ * Verify via command line (which, where, etc.)
58
+ */
59
+ private async verifyViaCommand(appName: string): Promise<string> {
60
+ try {
61
+ if (isMacOS() || isLinux()) {
62
+ // Try 'which' command to locate binary
63
+ try {
64
+ const output = execSync(`which ${appName} 2>/dev/null || true`).toString().trim();
65
+ if (output) {
66
+ return `Found at: ${output}`;
67
+ }
68
+ } catch {
69
+ // which command failed or app not found
70
+ }
71
+
72
+ // Try to run the app to see if it's accessible
73
+ try {
74
+ execSync(`${appName} --version 2>/dev/null || true`).toString().trim();
75
+ return `App still responds to --version`;
76
+ } catch {
77
+ // App not found or doesn't respond
78
+ }
79
+
80
+ return '';
81
+ }
82
+
83
+ if (isWindows()) {
84
+ // Try 'where' command on Windows
85
+ try {
86
+ const output = execSync(`where ${appName} 2>nul || echo ""`).toString().trim();
87
+ if (output && output !== '""') {
88
+ return `Found at: ${output}`;
89
+ }
90
+ } catch {
91
+ // where command failed or app not found
92
+ }
93
+
94
+ return '';
95
+ }
96
+
97
+ return '';
98
+ } catch {
99
+ return '';
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Get human-readable status message
105
+ */
106
+ getStatusMessage(status: VerificationStatus): string {
107
+ const messages: Record<VerificationStatus, string> = {
108
+ verified_removed: 'āœ“ Application successfully removed',
109
+ still_exists: 'āœ— Application still exists on system',
110
+ partial_removal: '⚠ Application partially removed (some files remain)',
111
+ unknown: '? Verification status unknown',
112
+ };
113
+
114
+ return messages[status] || messages['unknown'];
115
+ }
116
+
117
+ /**
118
+ * Get detailed verification report
119
+ */
120
+ getDetailedReport(result: VerificationResult): string {
121
+ let report = `\nšŸ“‹ Verification Report\n`;
122
+ report += `${'─'.repeat(40)}\n`;
123
+ report += `Status: ${this.getStatusMessage(result.status)}\n`;
124
+
125
+ if (result.remainingPaths.length > 0) {
126
+ report += `\nRemaining Files/Folders:\n`;
127
+ result.remainingPaths.forEach((path) => {
128
+ report += ` - ${path}\n`;
129
+ });
130
+ }
131
+
132
+ if (result.commandOutput) {
133
+ report += `\nCommand Output: ${result.commandOutput}\n`;
134
+ }
135
+
136
+ report += `Verified at: ${result.timestamp.toISOString()}\n`;
137
+
138
+ return report;
139
+ }
140
+ }