appclean 1.8.0 → 2.0.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 (211) hide show
  1. package/GUI_IMPLEMENTATION_STATUS.md +143 -0
  2. package/MD_Files/INDEX.md +51 -0
  3. package/MD_Files/PUBLICATION_SUCCESS_REPORT.md +227 -0
  4. package/PHASE2_COMPLETION.md +281 -0
  5. package/PHASE3_COMPLETION.md +364 -0
  6. package/README.md +446 -376
  7. package/assets/logo.svg +34 -0
  8. package/dist/core/appUpdateChecker.js +12 -16
  9. package/dist/core/appUpdateChecker.js.map +1 -1
  10. package/dist/core/detector.js +14 -18
  11. package/dist/core/detector.js.map +1 -1
  12. package/dist/core/duplicateFileFinder.js +12 -19
  13. package/dist/core/duplicateFileFinder.js.map +1 -1
  14. package/dist/core/orphanedDependencyDetector.js +19 -26
  15. package/dist/core/orphanedDependencyDetector.js.map +1 -1
  16. package/dist/core/performanceOptimizer.js +6 -10
  17. package/dist/core/performanceOptimizer.js.map +1 -1
  18. package/dist/core/permissionHandler.js +21 -25
  19. package/dist/core/permissionHandler.js.map +1 -1
  20. package/dist/core/pluginSystem.js +9 -13
  21. package/dist/core/pluginSystem.js.map +1 -1
  22. package/dist/core/removalRecorder.js +12 -19
  23. package/dist/core/removalRecorder.js.map +1 -1
  24. package/dist/core/remover.js +59 -66
  25. package/dist/core/remover.js.map +1 -1
  26. package/dist/core/reportGenerator.d.ts +1 -1
  27. package/dist/core/reportGenerator.d.ts.map +1 -1
  28. package/dist/core/reportGenerator.js +27 -34
  29. package/dist/core/reportGenerator.js.map +1 -1
  30. package/dist/core/scheduledCleanup.js +23 -30
  31. package/dist/core/scheduledCleanup.js.map +1 -1
  32. package/dist/core/serviceFileDetector.js +24 -31
  33. package/dist/core/serviceFileDetector.js.map +1 -1
  34. package/dist/core/verificationModule.js +10 -14
  35. package/dist/core/verificationModule.js.map +1 -1
  36. package/dist/index.js +190 -90
  37. package/dist/index.js.map +1 -1
  38. package/dist/managers/brewManager.d.ts.map +1 -1
  39. package/dist/managers/brewManager.js +35 -41
  40. package/dist/managers/brewManager.js.map +1 -1
  41. package/dist/managers/customManager.d.ts +2 -1
  42. package/dist/managers/customManager.d.ts.map +1 -1
  43. package/dist/managers/customManager.js +79 -53
  44. package/dist/managers/customManager.js.map +1 -1
  45. package/dist/managers/linuxManager.js +29 -36
  46. package/dist/managers/linuxManager.js.map +1 -1
  47. package/dist/managers/npmManager.js +27 -34
  48. package/dist/managers/npmManager.js.map +1 -1
  49. package/dist/types/index.js +1 -2
  50. package/dist/ui/client/api/client.d.ts +24 -0
  51. package/dist/ui/client/api/client.d.ts.map +1 -0
  52. package/dist/ui/client/api/client.js +96 -0
  53. package/dist/ui/client/api/client.js.map +1 -0
  54. package/dist/ui/client/app.d.ts +7 -0
  55. package/dist/ui/client/app.d.ts.map +1 -0
  56. package/dist/ui/client/app.js +71 -0
  57. package/dist/ui/client/app.js.map +1 -0
  58. package/dist/ui/client/index.html +107 -0
  59. package/dist/ui/client/pages/appDetails.d.ts +8 -0
  60. package/dist/ui/client/pages/appDetails.d.ts.map +1 -0
  61. package/dist/ui/client/pages/appDetails.js +287 -0
  62. package/dist/ui/client/pages/appDetails.js.map +1 -0
  63. package/dist/ui/client/pages/appSearch.d.ts +2 -0
  64. package/dist/ui/client/pages/appSearch.d.ts.map +1 -0
  65. package/dist/ui/client/pages/appSearch.js +210 -0
  66. package/dist/ui/client/pages/appSearch.js.map +1 -0
  67. package/dist/ui/client/pages/dashboard.d.ts +2 -0
  68. package/dist/ui/client/pages/dashboard.d.ts.map +1 -0
  69. package/dist/ui/client/pages/dashboard.js +154 -0
  70. package/dist/ui/client/pages/dashboard.js.map +1 -0
  71. package/dist/ui/client/pages/settings.d.ts +7 -0
  72. package/dist/ui/client/pages/settings.d.ts.map +1 -0
  73. package/dist/ui/client/pages/settings.js +279 -0
  74. package/dist/ui/client/pages/settings.js.map +1 -0
  75. package/dist/ui/client/state/appStore.d.ts +38 -0
  76. package/dist/ui/client/state/appStore.d.ts.map +1 -0
  77. package/dist/ui/client/state/appStore.js +121 -0
  78. package/dist/ui/client/state/appStore.js.map +1 -0
  79. package/dist/ui/client/state/dashboardStore.d.ts +31 -0
  80. package/dist/ui/client/state/dashboardStore.d.ts.map +1 -0
  81. package/dist/ui/client/state/dashboardStore.js +70 -0
  82. package/dist/ui/client/state/dashboardStore.js.map +1 -0
  83. package/dist/ui/client/state/uiStore.d.ts +43 -0
  84. package/dist/ui/client/state/uiStore.d.ts.map +1 -0
  85. package/dist/ui/client/state/uiStore.js +109 -0
  86. package/dist/ui/client/state/uiStore.js.map +1 -0
  87. package/dist/ui/client/styles/animations.css +327 -0
  88. package/dist/ui/client/styles/base.css +214 -0
  89. package/dist/ui/client/styles/components.css +400 -0
  90. package/dist/ui/client/styles/layout.css +224 -0
  91. package/dist/ui/client/styles/variables.css +140 -0
  92. package/dist/ui/client/utils/events.d.ts +19 -0
  93. package/dist/ui/client/utils/events.d.ts.map +1 -0
  94. package/dist/ui/client/utils/events.js +54 -0
  95. package/dist/ui/client/utils/events.js.map +1 -0
  96. package/dist/ui/client/utils/formatting.d.ts +11 -0
  97. package/dist/ui/client/utils/formatting.d.ts.map +1 -0
  98. package/dist/ui/client/utils/formatting.js +104 -0
  99. package/dist/ui/client/utils/formatting.js.map +1 -0
  100. package/dist/ui/client/utils/router.d.ts +25 -0
  101. package/dist/ui/client/utils/router.d.ts.map +1 -0
  102. package/dist/ui/client/utils/router.js +90 -0
  103. package/dist/ui/client/utils/router.js.map +1 -0
  104. package/dist/ui/guiServer.d.ts +8 -1
  105. package/dist/ui/guiServer.d.ts.map +1 -1
  106. package/dist/ui/guiServer.js +148 -110
  107. package/dist/ui/guiServer.js.map +1 -1
  108. package/dist/ui/menu.js +18 -27
  109. package/dist/ui/menu.js.map +1 -1
  110. package/dist/ui/prompts.js +34 -47
  111. package/dist/ui/prompts.js.map +1 -1
  112. package/dist/ui/server/middleware/errorHandler.d.ts +19 -0
  113. package/dist/ui/server/middleware/errorHandler.d.ts.map +1 -0
  114. package/dist/ui/server/middleware/errorHandler.js +100 -0
  115. package/dist/ui/server/middleware/errorHandler.js.map +1 -0
  116. package/dist/ui/server/routes/apps.d.ts +8 -0
  117. package/dist/ui/server/routes/apps.d.ts.map +1 -0
  118. package/dist/ui/server/routes/apps.js +74 -0
  119. package/dist/ui/server/routes/apps.js.map +1 -0
  120. package/dist/ui/server/routes/dashboard.d.ts +4 -0
  121. package/dist/ui/server/routes/dashboard.d.ts.map +1 -0
  122. package/dist/ui/server/routes/dashboard.js +57 -0
  123. package/dist/ui/server/routes/dashboard.js.map +1 -0
  124. package/dist/ui/server/routes/settings.d.ts +6 -0
  125. package/dist/ui/server/routes/settings.d.ts.map +1 -0
  126. package/dist/ui/server/routes/settings.js +31 -0
  127. package/dist/ui/server/routes/settings.js.map +1 -0
  128. package/dist/ui/server/services/appService.d.ts +45 -0
  129. package/dist/ui/server/services/appService.d.ts.map +1 -0
  130. package/dist/ui/server/services/appService.js +114 -0
  131. package/dist/ui/server/services/appService.js.map +1 -0
  132. package/dist/ui/server/services/removalService.d.ts +24 -0
  133. package/dist/ui/server/services/removalService.d.ts.map +1 -0
  134. package/dist/ui/server/services/removalService.js +83 -0
  135. package/dist/ui/server/services/removalService.js.map +1 -0
  136. package/dist/utils/filesystem.js +32 -49
  137. package/dist/utils/filesystem.js.map +1 -1
  138. package/dist/utils/logger.js +9 -18
  139. package/dist/utils/logger.js.map +1 -1
  140. package/dist/utils/platform.js +10 -22
  141. package/dist/utils/platform.js.map +1 -1
  142. package/dist/utils/upgrade.d.ts +22 -0
  143. package/dist/utils/upgrade.d.ts.map +1 -0
  144. package/dist/utils/upgrade.js +94 -0
  145. package/dist/utils/upgrade.js.map +1 -0
  146. package/package.json +4 -2
  147. package/src/core/appUpdateChecker.ts +1 -1
  148. package/src/core/detector.ts +6 -6
  149. package/src/core/duplicateFileFinder.ts +1 -1
  150. package/src/core/orphanedDependencyDetector.ts +2 -2
  151. package/src/core/performanceOptimizer.ts +1 -1
  152. package/src/core/permissionHandler.ts +2 -2
  153. package/src/core/pluginSystem.ts +1 -1
  154. package/src/core/removalRecorder.ts +2 -2
  155. package/src/core/remover.ts +11 -11
  156. package/src/core/reportGenerator.ts +2 -2
  157. package/src/core/scheduledCleanup.ts +2 -2
  158. package/src/core/serviceFileDetector.ts +2 -2
  159. package/src/core/verificationModule.ts +2 -2
  160. package/src/index.ts +133 -6
  161. package/src/managers/brewManager.ts +11 -9
  162. package/src/managers/customManager.ts +71 -30
  163. package/src/managers/linuxManager.ts +3 -3
  164. package/src/managers/npmManager.ts +3 -3
  165. package/src/ui/client/api/client.ts +163 -0
  166. package/src/ui/client/app.ts +121 -0
  167. package/src/ui/client/index.html +107 -0
  168. package/src/ui/client/pages/appDetails.ts +356 -0
  169. package/src/ui/client/pages/appSearch.ts +270 -0
  170. package/src/ui/client/pages/dashboard.ts +189 -0
  171. package/src/ui/client/pages/settings.ts +342 -0
  172. package/src/ui/client/state/appStore.ts +169 -0
  173. package/src/ui/client/state/dashboardStore.ts +113 -0
  174. package/src/ui/client/state/uiStore.ts +166 -0
  175. package/src/ui/client/styles/animations.css +327 -0
  176. package/src/ui/client/styles/base.css +214 -0
  177. package/src/ui/client/styles/components.css +400 -0
  178. package/src/ui/client/styles/layout.css +224 -0
  179. package/src/ui/client/styles/variables.css +140 -0
  180. package/src/ui/client/utils/events.ts +74 -0
  181. package/src/ui/client/utils/formatting.ts +157 -0
  182. package/src/ui/client/utils/router.ts +161 -0
  183. package/src/ui/guiServer.ts +206 -105
  184. package/src/ui/prompts.ts +1 -1
  185. package/src/ui/server/middleware/errorHandler.ts +174 -0
  186. package/src/ui/server/routes/apps.ts +132 -0
  187. package/src/ui/server/routes/dashboard.ts +93 -0
  188. package/src/ui/server/routes/settings.ts +63 -0
  189. package/src/ui/server/services/appService.ts +184 -0
  190. package/src/ui/server/services/removalService.ts +138 -0
  191. package/src/utils/upgrade.ts +143 -0
  192. package/tsconfig.json +3 -2
  193. package/INDEX.md +0 -165
  194. /package/{ACTION_CHECKLIST.md → MD_Files/ACTION_CHECKLIST.md} +0 -0
  195. /package/{APPCLEAN_SUMMARY.md → MD_Files/APPCLEAN_SUMMARY.md} +0 -0
  196. /package/{CHANGELOG.md → MD_Files/CHANGELOG.md} +0 -0
  197. /package/{CODE_OF_CONDUCT.md → MD_Files/CODE_OF_CONDUCT.md} +0 -0
  198. /package/{CODE_REVIEW_REPORT.md → MD_Files/CODE_REVIEW_REPORT.md} +0 -0
  199. /package/{COMMUNITY_POSTS.md → MD_Files/COMMUNITY_POSTS.md} +0 -0
  200. /package/{DEPLOYMENT_GUIDE.md → MD_Files/DEPLOYMENT_GUIDE.md} +0 -0
  201. /package/{DEPLOYMENT_STATUS.md → MD_Files/DEPLOYMENT_STATUS.md} +0 -0
  202. /package/{EXECUTIVE_REPORT.md → MD_Files/EXECUTIVE_REPORT.md} +0 -0
  203. /package/{GITHUB_OPTIMIZATION.md → MD_Files/GITHUB_OPTIMIZATION.md} +0 -0
  204. /package/{MARKETING_SUMMARY.md → MD_Files/MARKETING_SUMMARY.md} +0 -0
  205. /package/{NPM_PACKAGE_OPTIMIZATION.md → MD_Files/NPM_PACKAGE_OPTIMIZATION.md} +0 -0
  206. /package/{NPM_PUBLISH.md → MD_Files/NPM_PUBLISH.md} +0 -0
  207. /package/{PROJECT_SUMMARY.txt → MD_Files/PROJECT_SUMMARY.txt} +0 -0
  208. /package/{QUICKSTART.md → MD_Files/QUICKSTART.md} +0 -0
  209. /package/{SETUP_GITHUB.md → MD_Files/SETUP_GITHUB.md} +0 -0
  210. /package/{TESTING_SUMMARY.md → MD_Files/TESTING_SUMMARY.md} +0 -0
  211. /package/{setup-github.sh → MD_Files/setup-github.sh} +0 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Simple Hash-Based Router for SPA
3
+ */
4
+
5
+ export type RouteHandler = (params: Record<string, string>) => void;
6
+
7
+ export interface Route {
8
+ path: string;
9
+ handler: RouteHandler;
10
+ title?: string;
11
+ }
12
+
13
+ export class Router {
14
+ private routes: Route[] = [];
15
+ private currentRoute: Route | null = null;
16
+ private onRouteChange: ((route: Route) => void) | null = null;
17
+
18
+ constructor() {
19
+ this.listenToHashChanges();
20
+ }
21
+
22
+ /**
23
+ * Register a route
24
+ */
25
+ register(path: string, handler: RouteHandler, title?: string): void {
26
+ this.routes.push({ path, handler, title });
27
+ }
28
+
29
+ /**
30
+ * Navigate to a route
31
+ */
32
+ navigate(path: string): void {
33
+ window.location.hash = `#/${path}`.replace('##', '#');
34
+ }
35
+
36
+ /**
37
+ * Get current route path
38
+ */
39
+ getCurrentPath(): string {
40
+ return window.location.hash.slice(2) || '';
41
+ }
42
+
43
+ /**
44
+ * Listen to hash changes and trigger handlers
45
+ */
46
+ private listenToHashChanges(): void {
47
+ window.addEventListener('hashchange', () => {
48
+ this.route();
49
+ });
50
+
51
+ // Initial route on load
52
+ this.route();
53
+ }
54
+
55
+ /**
56
+ * Route the current hash to a handler
57
+ */
58
+ private route(): void {
59
+ const path = this.getCurrentPath();
60
+ const route = this.matchRoute(path);
61
+
62
+ if (route) {
63
+ const params = this.extractParams(route.path, path);
64
+ this.currentRoute = route;
65
+
66
+ if (route.title) {
67
+ document.title = route.title;
68
+ }
69
+
70
+ try {
71
+ route.handler(params);
72
+ } catch (error) {
73
+ console.error('Route handler error:', error);
74
+ }
75
+
76
+ if (this.onRouteChange) {
77
+ this.onRouteChange(route);
78
+ }
79
+ } else {
80
+ console.warn(`No route found for: ${path}`);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Match a path to a registered route
86
+ */
87
+ private matchRoute(path: string): Route | null {
88
+ // Exact match first
89
+ const exactMatch = this.routes.find((r) => r.path === path || r.path === `/${path}`);
90
+ if (exactMatch) return exactMatch;
91
+
92
+ // Dynamic route match
93
+ for (const route of this.routes) {
94
+ if (this.pathMatches(route.path, path)) {
95
+ return route;
96
+ }
97
+ }
98
+
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * Check if a route path matches a given path
104
+ * Supports dynamic segments like /apps/:appName
105
+ */
106
+ private pathMatches(routePath: string, actualPath: string): boolean {
107
+ const routeParts = routePath.split('/').filter(Boolean);
108
+ const actualParts = actualPath.split('/').filter(Boolean);
109
+
110
+ if (routeParts.length !== actualParts.length) {
111
+ return false;
112
+ }
113
+
114
+ return routeParts.every((part, i) => {
115
+ return part.startsWith(':') || part === actualParts[i];
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Extract params from a path
121
+ * Example: /apps/:appName with path /apps/myapp returns { appName: 'myapp' }
122
+ */
123
+ private extractParams(routePath: string, actualPath: string): Record<string, string> {
124
+ const params: Record<string, string> = {};
125
+ const routeParts = routePath.split('/').filter(Boolean);
126
+ const actualParts = actualPath.split('/').filter(Boolean);
127
+
128
+ routeParts.forEach((part, i) => {
129
+ if (part.startsWith(':')) {
130
+ const paramName = part.slice(1);
131
+ params[paramName] = decodeURIComponent(actualParts[i]);
132
+ }
133
+ });
134
+
135
+ return params;
136
+ }
137
+
138
+ /**
139
+ * Register a callback for route changes
140
+ */
141
+ onchange(callback: (route: Route) => void): void {
142
+ this.onRouteChange = callback;
143
+ }
144
+
145
+ /**
146
+ * Get all registered routes
147
+ */
148
+ getRoutes(): Route[] {
149
+ return this.routes;
150
+ }
151
+
152
+ /**
153
+ * Get current route
154
+ */
155
+ getCurrentRoute(): Route | null {
156
+ return this.currentRoute;
157
+ }
158
+ }
159
+
160
+ // Export singleton instance
161
+ export const router = new Router();
@@ -1,15 +1,27 @@
1
1
  /**
2
- * GUI Server for AppClean
3
- * Provides web-based GUI interface for macOS, Linux, and Windows
4
- * v1.2.0 Feature
2
+ * GUI Server for AppClean v2.0.0
3
+ * Modern SPA with API endpoints
5
4
  */
6
5
 
7
- import { createServer } from 'http';
8
- import { Logger } from '../utils/logger';
6
+ import { createServer, IncomingMessage, ServerResponse } from 'http';
7
+ import { readFileSync, existsSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname } from 'path';
11
+ import { Logger } from '../utils/logger.js';
12
+ import { sendJson, sendError, parseQueryParams } from './server/middleware/errorHandler.js';
13
+ import { handleAppRoutes } from './server/routes/apps.js';
14
+ import { handleDashboardRoutes } from './server/routes/dashboard.js';
15
+ import { handleSettingsRoutes } from './server/routes/settings.js';
16
+
17
+ // ES module compatibility: Define __dirname and __filename
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
9
20
 
10
21
  export class GUIServer {
11
22
  private port: number = 3000;
12
23
  private server: any;
24
+ private spaHtml: string | null = null;
13
25
 
14
26
  constructor(port: number = 3000) {
15
27
  this.port = port;
@@ -21,20 +33,19 @@ export class GUIServer {
21
33
  async start(): Promise<void> {
22
34
  Logger.info(`Starting AppClean GUI server on port ${this.port}...`);
23
35
 
36
+ // Try to load SPA HTML (from compiled dist)
37
+ this.loadSPAHtml();
38
+
24
39
  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
- }
40
+ this.handleRequest(req, res);
34
41
  });
35
42
 
36
- this.server.listen(this.port, () => {
37
- Logger.success(`GUI server running at http://localhost:${this.port}`);
43
+ return new Promise((resolve) => {
44
+ this.server.listen(this.port, () => {
45
+ Logger.success(`✨ AppClean GUI running at http://localhost:${this.port}`);
46
+ Logger.info('Press Ctrl+C to stop the server');
47
+ resolve();
48
+ });
38
49
  });
39
50
  }
40
51
 
@@ -49,107 +60,197 @@ export class GUIServer {
49
60
  }
50
61
 
51
62
  /**
52
- * Handle API requests
63
+ * Main request handler
53
64
  */
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' }));
65
+ private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
66
+ const url = req.url || '/';
67
+ const method = req.method || 'GET';
68
+ const pathname = url.split('?')[0];
69
+
70
+ // Set CORS headers
71
+ this.setCORSHeaders(res);
72
+
73
+ // Handle preflight requests
74
+ if (method === 'OPTIONS') {
75
+ res.writeHead(200);
76
+ res.end();
77
+ return;
78
+ }
79
+
80
+ try {
81
+ // Route API requests
82
+ if (pathname.startsWith('/api/')) {
83
+ return this.handleAPIRequest(method, pathname, req, res);
84
+ }
85
+
86
+ // Serve static assets
87
+ if (pathname.startsWith('/static/')) {
88
+ return this.serveStaticAsset(pathname, res);
89
+ }
90
+
91
+ // Serve SPA for all other routes
92
+ this.serveSPA(res);
93
+ } catch (error) {
94
+ Logger.error(`Request error: ${(error as Error).message}`);
95
+ sendError(res, 'Internal server error', 500);
96
+ }
58
97
  }
59
98
 
60
99
  /**
61
- * Get index HTML for GUI
100
+ * Handle API requests
62
101
  */
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;
102
+ private handleAPIRequest(
103
+ method: string,
104
+ pathname: string,
105
+ req: IncomingMessage,
106
+ res: ServerResponse
107
+ ): void {
108
+ // Try app routes
109
+ if (handleAppRoutes(method, pathname, req, res)) {
110
+ return;
85
111
  }
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;
112
+
113
+ // Try dashboard routes
114
+ if (handleDashboardRoutes(method, pathname, req, res)) {
115
+ return;
97
116
  }
98
- .version {
99
- color: #666;
100
- font-size: 14px;
101
- margin-bottom: 30px;
117
+
118
+ // Try settings routes
119
+ if (handleSettingsRoutes(method, pathname, req, res)) {
120
+ return;
102
121
  }
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;
122
+
123
+ // Unknown endpoint
124
+ sendError(res, 'API endpoint not found', 404);
125
+ }
126
+
127
+ /**
128
+ * Serve static assets (CSS, JS)
129
+ */
130
+ private serveStaticAsset(pathname: string, res: ServerResponse): void {
131
+ // Remove /static/ prefix
132
+ const relativePath = pathname.slice(8);
133
+
134
+ // Security: prevent directory traversal
135
+ if (relativePath.includes('..')) {
136
+ sendError(res, 'Access denied', 403);
137
+ return;
112
138
  }
113
- p {
114
- color: #666;
115
- line-height: 1.6;
116
- margin-bottom: 20px;
139
+
140
+ // Construct file path
141
+ const filePath = join(__dirname, 'client', relativePath);
142
+
143
+ // Check if file exists
144
+ if (!existsSync(filePath)) {
145
+ sendError(res, 'Asset not found', 404);
146
+ return;
117
147
  }
118
- .coming-soon {
119
- background: #f0f4ff;
120
- border-left: 4px solid #667eea;
121
- padding: 20px;
122
- border-radius: 4px;
123
- margin-top: 30px;
148
+
149
+ try {
150
+ const content = readFileSync(filePath);
151
+ const contentType = this.getContentType(filePath);
152
+
153
+ res.writeHead(200, {
154
+ 'Content-Type': contentType,
155
+ 'Cache-Control': 'public, max-age=3600', // 1 hour
156
+ });
157
+ res.end(content);
158
+ } catch (error) {
159
+ Logger.warn(`Failed to serve asset ${pathname}: ${(error as Error).message}`);
160
+ sendError(res, 'Failed to load asset', 500);
124
161
  }
125
- </style>
162
+ }
163
+
164
+ /**
165
+ * Serve SPA HTML
166
+ */
167
+ private serveSPA(res: ServerResponse): void {
168
+ if (!this.spaHtml) {
169
+ // Fallback: serve minimal HTML with error message
170
+ const fallbackHtml = `
171
+ <!DOCTYPE html>
172
+ <html>
173
+ <head>
174
+ <meta charset="UTF-8">
175
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
176
+ <title>AppClean</title>
126
177
  </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>
178
+ <body style="font-family: system-ui; padding: 20px; text-align: center;">
179
+ <h1>⚠️ GUI Not Ready</h1>
180
+ <p>The SPA assets haven't been compiled yet.</p>
181
+ <p>Run <code>npm run build</code> to compile the TypeScript/CSS files.</p>
182
+ <p>For now, use the CLI: <code>appclean --help</code></p>
151
183
  </body>
152
- </html>
153
- `;
184
+ </html>`;
185
+
186
+ res.writeHead(200, { 'Content-Type': 'text/html' });
187
+ res.end(fallbackHtml);
188
+ return;
189
+ }
190
+
191
+ res.writeHead(200, {
192
+ 'Content-Type': 'text/html',
193
+ 'Cache-Control': 'no-cache',
194
+ });
195
+ res.end(this.spaHtml);
196
+ }
197
+
198
+ /**
199
+ * Load SPA HTML from compiled dist
200
+ */
201
+ private loadSPAHtml(): void {
202
+ const htmlPath = join(__dirname, 'client', 'index.html');
203
+
204
+ try {
205
+ if (existsSync(htmlPath)) {
206
+ this.spaHtml = readFileSync(htmlPath, 'utf-8');
207
+ Logger.debug('✓ Loaded SPA HTML');
208
+ } else {
209
+ Logger.warn(`⚠️ SPA HTML not found at ${htmlPath}`);
210
+ Logger.info('Make sure to run: npm run build');
211
+ }
212
+ } catch (error) {
213
+ Logger.warn(`Failed to load SPA HTML: ${(error as Error).message}`);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Determine content type from file extension
219
+ */
220
+ private getContentType(filePath: string): string {
221
+ const ext = filePath.toLowerCase().split('.').pop();
222
+
223
+ const typeMap: Record<string, string> = {
224
+ 'js': 'application/javascript; charset=utf-8',
225
+ 'css': 'text/css; charset=utf-8',
226
+ 'html': 'text/html; charset=utf-8',
227
+ 'json': 'application/json; charset=utf-8',
228
+ 'svg': 'image/svg+xml',
229
+ 'png': 'image/png',
230
+ 'jpg': 'image/jpeg',
231
+ 'jpeg': 'image/jpeg',
232
+ 'gif': 'image/gif',
233
+ 'ico': 'image/x-icon',
234
+ 'webp': 'image/webp',
235
+ 'woff': 'font/woff',
236
+ 'woff2': 'font/woff2',
237
+ 'ttf': 'font/ttf',
238
+ 'eot': 'application/vnd.ms-fontobject',
239
+ };
240
+
241
+ return typeMap[ext || ''] || 'application/octet-stream';
242
+ }
243
+
244
+ /**
245
+ * Set CORS headers
246
+ */
247
+ private setCORSHeaders(res: ServerResponse): void {
248
+ res.setHeader('Access-Control-Allow-Origin', '*');
249
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
250
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
251
+ res.setHeader('Access-Control-Max-Age', '3600');
154
252
  }
155
253
  }
254
+
255
+ // Export for use in CLI
256
+ export default GUIServer;
package/src/ui/prompts.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import inquirer from 'inquirer';
2
2
  import { InstalledApp, ArtifactPath } from '../types';
3
- import { Logger, formatBytes } from '../utils/logger';
3
+ import { Logger, formatBytes } from '../utils/logger.js';
4
4
  import chalk from 'chalk';
5
5
 
6
6
  export async function promptSearchQuery(): Promise<string> {
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Error Handler Middleware
3
+ */
4
+
5
+ import { IncomingMessage, ServerResponse } from 'http';
6
+
7
+ export interface ApiError {
8
+ success: false;
9
+ error: string;
10
+ statusCode?: number;
11
+ }
12
+
13
+ export interface ApiSuccess<T> {
14
+ success: true;
15
+ data: T;
16
+ }
17
+
18
+ export type ApiResponse<T> = ApiSuccess<T> | ApiError;
19
+
20
+ /**
21
+ * Send JSON response
22
+ */
23
+ export function sendJson<T>(
24
+ res: ServerResponse,
25
+ data: T,
26
+ statusCode = 200
27
+ ): void {
28
+ res.statusCode = statusCode;
29
+ res.setHeader('Content-Type', 'application/json');
30
+ res.setHeader('Access-Control-Allow-Origin', '*');
31
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
32
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
33
+
34
+ const response: ApiSuccess<T> = {
35
+ success: true,
36
+ data,
37
+ };
38
+
39
+ res.end(JSON.stringify(response));
40
+ }
41
+
42
+ /**
43
+ * Send error response
44
+ */
45
+ export function sendError(
46
+ res: ServerResponse,
47
+ message: string,
48
+ statusCode = 400
49
+ ): void {
50
+ res.statusCode = statusCode;
51
+ res.setHeader('Content-Type', 'application/json');
52
+ res.setHeader('Access-Control-Allow-Origin', '*');
53
+
54
+ const response: ApiError = {
55
+ success: false,
56
+ error: message,
57
+ statusCode,
58
+ };
59
+
60
+ res.end(JSON.stringify(response));
61
+ }
62
+
63
+ /**
64
+ * Parse URL query parameters
65
+ */
66
+ export function parseQueryParams(url: string): Record<string, string> {
67
+ const params: Record<string, string> = {};
68
+ const queryStart = url.indexOf('?');
69
+
70
+ if (queryStart === -1) return params;
71
+
72
+ const queryString = url.substring(queryStart + 1);
73
+ const pairs = queryString.split('&');
74
+
75
+ pairs.forEach((pair) => {
76
+ const [key, value] = pair.split('=');
77
+ if (key) {
78
+ params[decodeURIComponent(key)] = decodeURIComponent(value || '');
79
+ }
80
+ });
81
+
82
+ return params;
83
+ }
84
+
85
+ /**
86
+ * Parse URL path and extract parameters
87
+ * Example: /api/apps/:appName with path /api/apps/myapp returns { appName: 'myapp' }
88
+ */
89
+ export function extractPathParams(
90
+ pattern: string,
91
+ path: string
92
+ ): Record<string, string> | null {
93
+ const patternParts = pattern.split('/').filter(Boolean);
94
+ const pathParts = path.split('/').filter(Boolean);
95
+
96
+ if (patternParts.length !== pathParts.length) {
97
+ return null;
98
+ }
99
+
100
+ const params: Record<string, string> = {};
101
+
102
+ for (let i = 0; i < patternParts.length; i++) {
103
+ const patternPart = patternParts[i];
104
+
105
+ if (patternPart.startsWith(':')) {
106
+ const paramName = patternPart.slice(1);
107
+ params[paramName] = decodeURIComponent(pathParts[i]);
108
+ } else if (patternPart !== pathParts[i]) {
109
+ return null;
110
+ }
111
+ }
112
+
113
+ return params;
114
+ }
115
+
116
+ /**
117
+ * Parse request body as JSON
118
+ */
119
+ export function parseBody(req: IncomingMessage): Promise<any> {
120
+ return new Promise((resolve, reject) => {
121
+ let data = '';
122
+
123
+ req.on('data', (chunk) => {
124
+ data += chunk;
125
+ // Prevent abuse: reject if body > 1MB
126
+ if (data.length > 1024 * 1024) {
127
+ reject(new Error('Request body too large'));
128
+ }
129
+ });
130
+
131
+ req.on('end', () => {
132
+ try {
133
+ resolve(data ? JSON.parse(data) : {});
134
+ } catch (error) {
135
+ reject(new Error('Invalid JSON'));
136
+ }
137
+ });
138
+
139
+ req.on('error', reject);
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Match URL pattern
145
+ */
146
+ export function matchPattern(pattern: string, url: string): boolean {
147
+ const patternParts = pattern.split('/').filter(Boolean);
148
+ const urlParts = url.split('/').filter(Boolean);
149
+
150
+ if (patternParts.length !== urlParts.length) {
151
+ return false;
152
+ }
153
+
154
+ return patternParts.every((part, i) => {
155
+ return part.startsWith(':') || part === urlParts[i];
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Async route handler wrapper
161
+ */
162
+ export function asyncHandler(
163
+ handler: (req: IncomingMessage, res: ServerResponse, params?: any) => Promise<void>
164
+ ) {
165
+ return async (req: IncomingMessage, res: ServerResponse, params?: any) => {
166
+ try {
167
+ await handler(req, res, params);
168
+ } catch (error) {
169
+ console.error('Route handler error:', error);
170
+ const message = (error as Error).message || 'Internal server error';
171
+ sendError(res, message, 500);
172
+ }
173
+ };
174
+ }