appclean 1.9.0 → 2.0.2
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.
- package/.github/workflows/npm-publish.yml +61 -0
- package/DEVELOPMENT.md +84 -0
- package/GUI_IMPLEMENTATION_STATUS.md +143 -0
- package/MD_Files/INDEX.md +51 -0
- package/PHASE2_COMPLETION.md +281 -0
- package/PHASE3_COMPLETION.md +364 -0
- package/README.md +411 -91
- package/RELEASE_GUIDE.md +236 -0
- package/assets/logo.svg +34 -0
- package/dist/core/appUpdateChecker.js +12 -16
- package/dist/core/appUpdateChecker.js.map +1 -1
- package/dist/core/detector.js +14 -18
- package/dist/core/detector.js.map +1 -1
- package/dist/core/duplicateFileFinder.js +12 -19
- package/dist/core/duplicateFileFinder.js.map +1 -1
- package/dist/core/orphanedDependencyDetector.js +19 -26
- package/dist/core/orphanedDependencyDetector.js.map +1 -1
- package/dist/core/performanceOptimizer.js +6 -10
- package/dist/core/performanceOptimizer.js.map +1 -1
- package/dist/core/permissionHandler.js +21 -25
- package/dist/core/permissionHandler.js.map +1 -1
- package/dist/core/pluginSystem.js +9 -13
- package/dist/core/pluginSystem.js.map +1 -1
- package/dist/core/removalRecorder.js +12 -19
- package/dist/core/removalRecorder.js.map +1 -1
- package/dist/core/remover.js +59 -66
- package/dist/core/remover.js.map +1 -1
- package/dist/core/reportGenerator.d.ts +1 -1
- package/dist/core/reportGenerator.d.ts.map +1 -1
- package/dist/core/reportGenerator.js +27 -34
- package/dist/core/reportGenerator.js.map +1 -1
- package/dist/core/scheduledCleanup.js +23 -30
- package/dist/core/scheduledCleanup.js.map +1 -1
- package/dist/core/serviceFileDetector.js +24 -31
- package/dist/core/serviceFileDetector.js.map +1 -1
- package/dist/core/verificationModule.js +10 -14
- package/dist/core/verificationModule.js.map +1 -1
- package/dist/index.js +118 -156
- package/dist/index.js.map +1 -1
- package/dist/managers/brewManager.js +30 -37
- package/dist/managers/brewManager.js.map +1 -1
- package/dist/managers/customManager.js +23 -30
- package/dist/managers/customManager.js.map +1 -1
- package/dist/managers/linuxManager.js +29 -36
- package/dist/managers/linuxManager.js.map +1 -1
- package/dist/managers/npmManager.js +27 -34
- package/dist/managers/npmManager.js.map +1 -1
- package/dist/types/index.js +1 -2
- package/dist/ui/client/api/client.d.ts +24 -0
- package/dist/ui/client/api/client.d.ts.map +1 -0
- package/dist/ui/client/api/client.js +100 -0
- package/dist/ui/client/api/client.js.map +1 -0
- package/dist/ui/client/app.d.ts +7 -0
- package/dist/ui/client/app.d.ts.map +1 -0
- package/dist/ui/client/app.js +75 -0
- package/dist/ui/client/app.js.map +1 -0
- package/dist/ui/client/index.html +107 -0
- package/dist/ui/client/pages/appDetails.d.ts +8 -0
- package/dist/ui/client/pages/appDetails.d.ts.map +1 -0
- package/dist/ui/client/pages/appDetails.js +287 -0
- package/dist/ui/client/pages/appDetails.js.map +1 -0
- package/dist/ui/client/pages/appSearch.d.ts +2 -0
- package/dist/ui/client/pages/appSearch.d.ts.map +1 -0
- package/dist/ui/client/pages/appSearch.js +221 -0
- package/dist/ui/client/pages/appSearch.js.map +1 -0
- package/dist/ui/client/pages/dashboard.d.ts +2 -0
- package/dist/ui/client/pages/dashboard.d.ts.map +1 -0
- package/dist/ui/client/pages/dashboard.js +175 -0
- package/dist/ui/client/pages/dashboard.js.map +1 -0
- package/dist/ui/client/pages/settings.d.ts +7 -0
- package/dist/ui/client/pages/settings.d.ts.map +1 -0
- package/dist/ui/client/pages/settings.js +279 -0
- package/dist/ui/client/pages/settings.js.map +1 -0
- package/dist/ui/client/state/appStore.d.ts +38 -0
- package/dist/ui/client/state/appStore.d.ts.map +1 -0
- package/dist/ui/client/state/appStore.js +130 -0
- package/dist/ui/client/state/appStore.js.map +1 -0
- package/dist/ui/client/state/dashboardStore.d.ts +31 -0
- package/dist/ui/client/state/dashboardStore.d.ts.map +1 -0
- package/dist/ui/client/state/dashboardStore.js +76 -0
- package/dist/ui/client/state/dashboardStore.js.map +1 -0
- package/dist/ui/client/state/uiStore.d.ts +43 -0
- package/dist/ui/client/state/uiStore.d.ts.map +1 -0
- package/dist/ui/client/state/uiStore.js +109 -0
- package/dist/ui/client/state/uiStore.js.map +1 -0
- package/dist/ui/client/styles/animations.css +349 -0
- package/dist/ui/client/styles/base.css +214 -0
- package/dist/ui/client/styles/components.css +400 -0
- package/dist/ui/client/styles/layout.css +224 -0
- package/dist/ui/client/styles/variables.css +140 -0
- package/dist/ui/client/utils/events.d.ts +19 -0
- package/dist/ui/client/utils/events.d.ts.map +1 -0
- package/dist/ui/client/utils/events.js +54 -0
- package/dist/ui/client/utils/events.js.map +1 -0
- package/dist/ui/client/utils/formatting.d.ts +11 -0
- package/dist/ui/client/utils/formatting.d.ts.map +1 -0
- package/dist/ui/client/utils/formatting.js +104 -0
- package/dist/ui/client/utils/formatting.js.map +1 -0
- package/dist/ui/client/utils/router.d.ts +25 -0
- package/dist/ui/client/utils/router.d.ts.map +1 -0
- package/dist/ui/client/utils/router.js +90 -0
- package/dist/ui/client/utils/router.js.map +1 -0
- package/dist/ui/guiServer.d.ts +11 -5
- package/dist/ui/guiServer.d.ts.map +1 -1
- package/dist/ui/guiServer.js +180 -501
- package/dist/ui/guiServer.js.map +1 -1
- package/dist/ui/menu.js +18 -27
- package/dist/ui/menu.js.map +1 -1
- package/dist/ui/prompts.js +34 -47
- package/dist/ui/prompts.js.map +1 -1
- package/dist/ui/server/middleware/errorHandler.d.ts +19 -0
- package/dist/ui/server/middleware/errorHandler.d.ts.map +1 -0
- package/dist/ui/server/middleware/errorHandler.js +100 -0
- package/dist/ui/server/middleware/errorHandler.js.map +1 -0
- package/dist/ui/server/routes/apps.d.ts +8 -0
- package/dist/ui/server/routes/apps.d.ts.map +1 -0
- package/dist/ui/server/routes/apps.js +74 -0
- package/dist/ui/server/routes/apps.js.map +1 -0
- package/dist/ui/server/routes/dashboard.d.ts +4 -0
- package/dist/ui/server/routes/dashboard.d.ts.map +1 -0
- package/dist/ui/server/routes/dashboard.js +57 -0
- package/dist/ui/server/routes/dashboard.js.map +1 -0
- package/dist/ui/server/routes/settings.d.ts +6 -0
- package/dist/ui/server/routes/settings.d.ts.map +1 -0
- package/dist/ui/server/routes/settings.js +31 -0
- package/dist/ui/server/routes/settings.js.map +1 -0
- package/dist/ui/server/services/appService.d.ts +45 -0
- package/dist/ui/server/services/appService.d.ts.map +1 -0
- package/dist/ui/server/services/appService.js +114 -0
- package/dist/ui/server/services/appService.js.map +1 -0
- package/dist/ui/server/services/removalService.d.ts +24 -0
- package/dist/ui/server/services/removalService.d.ts.map +1 -0
- package/dist/ui/server/services/removalService.js +83 -0
- package/dist/ui/server/services/removalService.js.map +1 -0
- package/dist/utils/filesystem.js +32 -49
- package/dist/utils/filesystem.js.map +1 -1
- package/dist/utils/logger.js +9 -18
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/platform.js +10 -22
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/upgrade.d.ts +2 -1
- package/dist/utils/upgrade.d.ts.map +1 -1
- package/dist/utils/upgrade.js +24 -15
- package/dist/utils/upgrade.js.map +1 -1
- package/package.json +4 -2
- package/scripts/publish-npm.sh +64 -0
- package/src/core/appUpdateChecker.ts +1 -1
- package/src/core/detector.ts +6 -6
- package/src/core/duplicateFileFinder.ts +1 -1
- package/src/core/orphanedDependencyDetector.ts +2 -2
- package/src/core/performanceOptimizer.ts +1 -1
- package/src/core/permissionHandler.ts +2 -2
- package/src/core/pluginSystem.ts +1 -1
- package/src/core/removalRecorder.ts +2 -2
- package/src/core/remover.ts +11 -11
- package/src/core/reportGenerator.ts +2 -2
- package/src/core/scheduledCleanup.ts +2 -2
- package/src/core/serviceFileDetector.ts +2 -2
- package/src/core/verificationModule.ts +2 -2
- package/src/index.ts +8 -8
- package/src/managers/brewManager.ts +3 -3
- package/src/managers/customManager.ts +2 -2
- package/src/managers/linuxManager.ts +3 -3
- package/src/managers/npmManager.ts +3 -3
- package/src/ui/client/api/client.ts +168 -0
- package/src/ui/client/app.ts +125 -0
- package/src/ui/client/index.html +107 -0
- package/src/ui/client/pages/appDetails.ts +356 -0
- package/src/ui/client/pages/appSearch.ts +283 -0
- package/src/ui/client/pages/dashboard.ts +211 -0
- package/src/ui/client/pages/settings.ts +342 -0
- package/src/ui/client/state/appStore.ts +181 -0
- package/src/ui/client/state/dashboardStore.ts +123 -0
- package/src/ui/client/state/uiStore.ts +166 -0
- package/src/ui/client/styles/animations.css +349 -0
- package/src/ui/client/styles/base.css +214 -0
- package/src/ui/client/styles/components.css +400 -0
- package/src/ui/client/styles/layout.css +224 -0
- package/src/ui/client/styles/variables.css +140 -0
- package/src/ui/client/utils/events.ts +74 -0
- package/src/ui/client/utils/formatting.ts +157 -0
- package/src/ui/client/utils/router.ts +161 -0
- package/src/ui/guiServer.ts +245 -498
- package/src/ui/prompts.ts +1 -1
- package/src/ui/server/middleware/errorHandler.ts +174 -0
- package/src/ui/server/routes/apps.ts +132 -0
- package/src/ui/server/routes/dashboard.ts +93 -0
- package/src/ui/server/routes/settings.ts +63 -0
- package/src/ui/server/services/appService.ts +184 -0
- package/src/ui/server/services/removalService.ts +138 -0
- package/src/utils/upgrade.ts +19 -2
- package/tsconfig.json +3 -2
- package/INDEX.md +0 -165
- /package/{ACTION_CHECKLIST.md → MD_Files/ACTION_CHECKLIST.md} +0 -0
- /package/{APPCLEAN_SUMMARY.md → MD_Files/APPCLEAN_SUMMARY.md} +0 -0
- /package/{CHANGELOG.md → MD_Files/CHANGELOG.md} +0 -0
- /package/{CODE_OF_CONDUCT.md → MD_Files/CODE_OF_CONDUCT.md} +0 -0
- /package/{CODE_REVIEW_REPORT.md → MD_Files/CODE_REVIEW_REPORT.md} +0 -0
- /package/{COMMUNITY_POSTS.md → MD_Files/COMMUNITY_POSTS.md} +0 -0
- /package/{DEPLOYMENT_GUIDE.md → MD_Files/DEPLOYMENT_GUIDE.md} +0 -0
- /package/{DEPLOYMENT_STATUS.md → MD_Files/DEPLOYMENT_STATUS.md} +0 -0
- /package/{EXECUTIVE_REPORT.md → MD_Files/EXECUTIVE_REPORT.md} +0 -0
- /package/{GITHUB_OPTIMIZATION.md → MD_Files/GITHUB_OPTIMIZATION.md} +0 -0
- /package/{MARKETING_SUMMARY.md → MD_Files/MARKETING_SUMMARY.md} +0 -0
- /package/{NPM_PACKAGE_OPTIMIZATION.md → MD_Files/NPM_PACKAGE_OPTIMIZATION.md} +0 -0
- /package/{NPM_PUBLISH.md → MD_Files/NPM_PUBLISH.md} +0 -0
- /package/{PROJECT_SUMMARY.txt → MD_Files/PROJECT_SUMMARY.txt} +0 -0
- /package/{PUBLICATION_SUCCESS_REPORT.md → MD_Files/PUBLICATION_SUCCESS_REPORT.md} +0 -0
- /package/{QUICKSTART.md → MD_Files/QUICKSTART.md} +0 -0
- /package/{SETUP_GITHUB.md → MD_Files/SETUP_GITHUB.md} +0 -0
- /package/{TESTING_SUMMARY.md → MD_Files/TESTING_SUMMARY.md} +0 -0
- /package/{setup-github.sh → MD_Files/setup-github.sh} +0 -0
package/src/ui/guiServer.ts
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GUI Server for AppClean
|
|
3
|
-
*
|
|
4
|
-
* v1.2.0 Feature
|
|
2
|
+
* GUI Server for AppClean v2.0.0
|
|
3
|
+
* Modern SPA with API endpoints
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
import { createServer, IncomingMessage, ServerResponse } from 'http';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { readFileSync, existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname } from 'path';
|
|
11
|
+
import { execFile, spawn, type ChildProcess } from 'child_process';
|
|
12
|
+
import { Logger } from '../utils/logger.js';
|
|
13
|
+
import { sendJson, sendError, parseQueryParams } from './server/middleware/errorHandler.js';
|
|
14
|
+
import { handleAppRoutes } from './server/routes/apps.js';
|
|
15
|
+
import { handleDashboardRoutes } from './server/routes/dashboard.js';
|
|
16
|
+
import { handleSettingsRoutes } from './server/routes/settings.js';
|
|
17
|
+
|
|
18
|
+
// ES module compatibility: Define __dirname and __filename
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
10
21
|
|
|
11
22
|
export class GUIServer {
|
|
12
23
|
private port: number = 3000;
|
|
13
24
|
private server: any;
|
|
14
|
-
private
|
|
25
|
+
private spaHtml: string | null = null;
|
|
26
|
+
private browserProcess: ChildProcess | null = null;
|
|
15
27
|
|
|
16
28
|
constructor(port: number = 3000) {
|
|
17
29
|
this.port = port;
|
|
18
|
-
this.upgradeManager = new UpgradeManager();
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
/**
|
|
@@ -24,20 +35,23 @@ export class GUIServer {
|
|
|
24
35
|
async start(): Promise<void> {
|
|
25
36
|
Logger.info(`Starting AppClean GUI server on port ${this.port}...`);
|
|
26
37
|
|
|
38
|
+
// Try to load SPA HTML (from compiled dist)
|
|
39
|
+
this.loadSPAHtml();
|
|
40
|
+
|
|
27
41
|
this.server = createServer((req, res) => {
|
|
28
|
-
|
|
29
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
30
|
-
res.end(this.getIndexHTML());
|
|
31
|
-
} else if (req.url?.startsWith('/api/')) {
|
|
32
|
-
this.handleAPIRequest(req, res);
|
|
33
|
-
} else {
|
|
34
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
35
|
-
res.end('Not Found');
|
|
36
|
-
}
|
|
42
|
+
this.handleRequest(req, res);
|
|
37
43
|
});
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
this.server.listen(this.port, async () => {
|
|
47
|
+
Logger.success(`✨ AppClean GUI running at http://localhost:${this.port}`);
|
|
48
|
+
Logger.info('Press Ctrl+C to stop the server');
|
|
49
|
+
|
|
50
|
+
// Open the default browser
|
|
51
|
+
await this.openBrowser();
|
|
52
|
+
|
|
53
|
+
resolve();
|
|
54
|
+
});
|
|
41
55
|
});
|
|
42
56
|
}
|
|
43
57
|
|
|
@@ -45,6 +59,9 @@ export class GUIServer {
|
|
|
45
59
|
* Stop GUI server
|
|
46
60
|
*/
|
|
47
61
|
async stop(): Promise<void> {
|
|
62
|
+
// Close the browser
|
|
63
|
+
await this.closeBrowser();
|
|
64
|
+
|
|
48
65
|
if (this.server) {
|
|
49
66
|
this.server.close();
|
|
50
67
|
Logger.info('GUI server stopped');
|
|
@@ -52,524 +69,254 @@ export class GUIServer {
|
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
/**
|
|
55
|
-
*
|
|
72
|
+
* Open the default browser
|
|
56
73
|
*/
|
|
57
|
-
private async
|
|
58
|
-
const url =
|
|
74
|
+
private async openBrowser(): Promise<void> {
|
|
75
|
+
const url = `http://localhost:${this.port}`;
|
|
59
76
|
|
|
60
77
|
try {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
const platform = process.platform;
|
|
79
|
+
|
|
80
|
+
if (platform === 'darwin') {
|
|
81
|
+
// macOS
|
|
82
|
+
this.browserProcess = execFile('open', [url]);
|
|
83
|
+
} else if (platform === 'win32') {
|
|
84
|
+
// Windows
|
|
85
|
+
this.browserProcess = execFile('cmd', ['/c', 'start', url]);
|
|
67
86
|
} else {
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
// Linux and other Unix-like systems
|
|
88
|
+
this.browserProcess = execFile('xdg-open', [url]);
|
|
70
89
|
}
|
|
90
|
+
|
|
91
|
+
this.browserProcess.on('error', (error) => {
|
|
92
|
+
Logger.warn(`Could not open browser: ${error.message}`);
|
|
93
|
+
});
|
|
71
94
|
} catch (error) {
|
|
72
|
-
|
|
73
|
-
res.end(
|
|
74
|
-
JSON.stringify({ error: (error as Error).message })
|
|
75
|
-
);
|
|
95
|
+
Logger.warn(`Failed to open browser: ${(error as Error).message}`);
|
|
76
96
|
}
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
/**
|
|
80
|
-
*
|
|
100
|
+
* Close the browser
|
|
81
101
|
*/
|
|
82
|
-
private async
|
|
83
|
-
|
|
102
|
+
private async closeBrowser(): Promise<void> {
|
|
103
|
+
if (!this.browserProcess) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const platform = process.platform;
|
|
109
|
+
|
|
110
|
+
if (platform === 'win32') {
|
|
111
|
+
// Windows: kill the process
|
|
112
|
+
if (this.browserProcess.pid) {
|
|
113
|
+
process.kill(this.browserProcess.pid, 'SIGTERM');
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// macOS and Linux: kill the process
|
|
117
|
+
if (this.browserProcess.pid) {
|
|
118
|
+
process.kill(this.browserProcess.pid);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
84
121
|
|
|
85
|
-
|
|
86
|
-
|
|
122
|
+
this.browserProcess = null;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
Logger.debug(`Note: Could not close browser process: ${(error as Error).message}`);
|
|
125
|
+
}
|
|
87
126
|
}
|
|
88
127
|
|
|
89
128
|
/**
|
|
90
|
-
*
|
|
129
|
+
* Main request handler
|
|
91
130
|
*/
|
|
92
|
-
private async
|
|
93
|
-
const
|
|
131
|
+
private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
132
|
+
const url = req.url || '/';
|
|
133
|
+
const method = req.method || 'GET';
|
|
134
|
+
const pathname = url.split('?')[0];
|
|
94
135
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
// Set CORS headers
|
|
137
|
+
this.setCORSHeaders(res);
|
|
138
|
+
|
|
139
|
+
// Handle preflight requests
|
|
140
|
+
if (method === 'OPTIONS') {
|
|
141
|
+
res.writeHead(200);
|
|
142
|
+
res.end();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Route API requests
|
|
148
|
+
if (pathname.startsWith('/api/')) {
|
|
149
|
+
return this.handleAPIRequest(method, pathname, req, res);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Serve static assets
|
|
153
|
+
if (pathname.startsWith('/static/')) {
|
|
154
|
+
return this.serveStaticAsset(pathname, res);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Serve SPA for all other routes
|
|
158
|
+
this.serveSPA(res);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
Logger.error(`Request error: ${(error as Error).message}`);
|
|
161
|
+
sendError(res, 'Internal server error', 500);
|
|
162
|
+
}
|
|
99
163
|
}
|
|
100
164
|
|
|
101
165
|
/**
|
|
102
|
-
* Handle
|
|
166
|
+
* Handle API requests
|
|
103
167
|
*/
|
|
104
|
-
private
|
|
105
|
-
|
|
168
|
+
private handleAPIRequest(
|
|
169
|
+
method: string,
|
|
170
|
+
pathname: string,
|
|
171
|
+
req: IncomingMessage,
|
|
172
|
+
res: ServerResponse
|
|
173
|
+
): void {
|
|
174
|
+
// Try app routes
|
|
175
|
+
if (handleAppRoutes(method, pathname, req, res)) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
106
178
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
179
|
+
// Try dashboard routes
|
|
180
|
+
if (handleDashboardRoutes(method, pathname, req, res)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Try settings routes
|
|
185
|
+
if (handleSettingsRoutes(method, pathname, req, res)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Unknown endpoint
|
|
190
|
+
sendError(res, 'API endpoint not found', 404);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Serve static assets (CSS, JS)
|
|
195
|
+
*/
|
|
196
|
+
private serveStaticAsset(pathname: string, res: ServerResponse): void {
|
|
197
|
+
// Remove /static/ prefix
|
|
198
|
+
const relativePath = pathname.slice(8);
|
|
199
|
+
|
|
200
|
+
// Security: prevent directory traversal
|
|
201
|
+
if (relativePath.includes('..')) {
|
|
202
|
+
sendError(res, 'Access denied', 403);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Construct file path
|
|
207
|
+
const filePath = join(__dirname, 'client', relativePath);
|
|
208
|
+
|
|
209
|
+
// Check if file exists
|
|
210
|
+
if (!existsSync(filePath)) {
|
|
211
|
+
sendError(res, 'Asset not found', 404);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const content = readFileSync(filePath);
|
|
217
|
+
const contentType = this.getContentType(filePath);
|
|
218
|
+
|
|
219
|
+
res.writeHead(200, {
|
|
220
|
+
'Content-Type': contentType,
|
|
221
|
+
'Cache-Control': 'public, max-age=3600', // 1 hour
|
|
222
|
+
});
|
|
223
|
+
res.end(content);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
Logger.warn(`Failed to serve asset ${pathname}: ${(error as Error).message}`);
|
|
226
|
+
sendError(res, 'Failed to load asset', 500);
|
|
227
|
+
}
|
|
111
228
|
}
|
|
112
229
|
|
|
113
230
|
/**
|
|
114
|
-
*
|
|
231
|
+
* Serve SPA HTML
|
|
115
232
|
*/
|
|
116
|
-
private
|
|
117
|
-
|
|
233
|
+
private serveSPA(res: ServerResponse): void {
|
|
234
|
+
if (!this.spaHtml) {
|
|
235
|
+
// Fallback: serve minimal HTML with error message
|
|
236
|
+
const fallbackHtml = `
|
|
118
237
|
<!DOCTYPE html>
|
|
119
|
-
<html
|
|
238
|
+
<html>
|
|
120
239
|
<head>
|
|
121
240
|
<meta charset="UTF-8">
|
|
122
241
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
123
|
-
<title>AppClean
|
|
124
|
-
<style>
|
|
125
|
-
* {
|
|
126
|
-
margin: 0;
|
|
127
|
-
padding: 0;
|
|
128
|
-
box-sizing: border-box;
|
|
129
|
-
}
|
|
130
|
-
body {
|
|
131
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
132
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
133
|
-
min-height: 100vh;
|
|
134
|
-
display: flex;
|
|
135
|
-
align-items: center;
|
|
136
|
-
justify-content: center;
|
|
137
|
-
padding: 20px;
|
|
138
|
-
}
|
|
139
|
-
.container {
|
|
140
|
-
background: white;
|
|
141
|
-
border-radius: 12px;
|
|
142
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
143
|
-
max-width: 800px;
|
|
144
|
-
width: 100%;
|
|
145
|
-
padding: 40px;
|
|
146
|
-
}
|
|
147
|
-
h1 {
|
|
148
|
-
color: #333;
|
|
149
|
-
margin-bottom: 10px;
|
|
150
|
-
}
|
|
151
|
-
h3 {
|
|
152
|
-
color: #333;
|
|
153
|
-
margin-top: 20px;
|
|
154
|
-
margin-bottom: 15px;
|
|
155
|
-
}
|
|
156
|
-
.version {
|
|
157
|
-
color: #666;
|
|
158
|
-
font-size: 14px;
|
|
159
|
-
margin-bottom: 30px;
|
|
160
|
-
}
|
|
161
|
-
.feature-badge {
|
|
162
|
-
display: inline-block;
|
|
163
|
-
background: #667eea;
|
|
164
|
-
color: white;
|
|
165
|
-
padding: 8px 16px;
|
|
166
|
-
border-radius: 20px;
|
|
167
|
-
font-size: 12px;
|
|
168
|
-
font-weight: 600;
|
|
169
|
-
margin-bottom: 20px;
|
|
170
|
-
}
|
|
171
|
-
p {
|
|
172
|
-
color: #666;
|
|
173
|
-
line-height: 1.6;
|
|
174
|
-
margin-bottom: 20px;
|
|
175
|
-
}
|
|
176
|
-
.section {
|
|
177
|
-
background: #f0f4ff;
|
|
178
|
-
border-left: 4px solid #667eea;
|
|
179
|
-
padding: 20px;
|
|
180
|
-
border-radius: 4px;
|
|
181
|
-
margin-top: 30px;
|
|
182
|
-
}
|
|
183
|
-
.version-info {
|
|
184
|
-
background: #f9f9f9;
|
|
185
|
-
padding: 15px;
|
|
186
|
-
border-radius: 6px;
|
|
187
|
-
margin: 15px 0;
|
|
188
|
-
font-family: monospace;
|
|
189
|
-
font-size: 14px;
|
|
190
|
-
}
|
|
191
|
-
.version-row {
|
|
192
|
-
display: flex;
|
|
193
|
-
justify-content: space-between;
|
|
194
|
-
margin: 8px 0;
|
|
195
|
-
}
|
|
196
|
-
.label {
|
|
197
|
-
color: #666;
|
|
198
|
-
font-weight: 500;
|
|
199
|
-
}
|
|
200
|
-
.value {
|
|
201
|
-
color: #333;
|
|
202
|
-
font-weight: 600;
|
|
203
|
-
}
|
|
204
|
-
.update-available {
|
|
205
|
-
color: #f59e0b;
|
|
206
|
-
font-weight: 600;
|
|
207
|
-
}
|
|
208
|
-
.up-to-date {
|
|
209
|
-
color: #10b981;
|
|
210
|
-
font-weight: 600;
|
|
211
|
-
}
|
|
212
|
-
.button-group {
|
|
213
|
-
display: flex;
|
|
214
|
-
gap: 10px;
|
|
215
|
-
margin-top: 20px;
|
|
216
|
-
}
|
|
217
|
-
button {
|
|
218
|
-
flex: 1;
|
|
219
|
-
padding: 12px 20px;
|
|
220
|
-
border: none;
|
|
221
|
-
border-radius: 6px;
|
|
222
|
-
font-size: 14px;
|
|
223
|
-
font-weight: 600;
|
|
224
|
-
cursor: pointer;
|
|
225
|
-
transition: all 0.3s ease;
|
|
226
|
-
}
|
|
227
|
-
.btn-primary {
|
|
228
|
-
background: #667eea;
|
|
229
|
-
color: white;
|
|
230
|
-
}
|
|
231
|
-
.btn-primary:hover {
|
|
232
|
-
background: #5568d3;
|
|
233
|
-
transform: translateY(-2px);
|
|
234
|
-
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
235
|
-
}
|
|
236
|
-
.btn-primary:disabled {
|
|
237
|
-
background: #ccc;
|
|
238
|
-
cursor: not-allowed;
|
|
239
|
-
transform: none;
|
|
240
|
-
}
|
|
241
|
-
.btn-secondary {
|
|
242
|
-
background: #e5e7eb;
|
|
243
|
-
color: #333;
|
|
244
|
-
}
|
|
245
|
-
.btn-secondary:hover {
|
|
246
|
-
background: #d1d5db;
|
|
247
|
-
}
|
|
248
|
-
.status-message {
|
|
249
|
-
margin-top: 15px;
|
|
250
|
-
padding: 12px;
|
|
251
|
-
border-radius: 6px;
|
|
252
|
-
display: none;
|
|
253
|
-
font-weight: 500;
|
|
254
|
-
}
|
|
255
|
-
.status-success {
|
|
256
|
-
background: #d1fae5;
|
|
257
|
-
color: #065f46;
|
|
258
|
-
display: block;
|
|
259
|
-
}
|
|
260
|
-
.status-error {
|
|
261
|
-
background: #fee2e2;
|
|
262
|
-
color: #991b1b;
|
|
263
|
-
display: block;
|
|
264
|
-
}
|
|
265
|
-
.status-loading {
|
|
266
|
-
background: #dbeafe;
|
|
267
|
-
color: #1e40af;
|
|
268
|
-
display: block;
|
|
269
|
-
}
|
|
270
|
-
.spinner {
|
|
271
|
-
display: inline-block;
|
|
272
|
-
width: 16px;
|
|
273
|
-
height: 16px;
|
|
274
|
-
border: 2px solid rgba(30, 64, 175, 0.3);
|
|
275
|
-
border-top-color: #1e40af;
|
|
276
|
-
border-radius: 50%;
|
|
277
|
-
animation: spin 0.8s linear infinite;
|
|
278
|
-
margin-right: 8px;
|
|
279
|
-
vertical-align: middle;
|
|
280
|
-
}
|
|
281
|
-
@keyframes spin {
|
|
282
|
-
to { transform: rotate(360deg); }
|
|
283
|
-
}
|
|
284
|
-
ul {
|
|
285
|
-
margin-left: 20px;
|
|
286
|
-
margin-top: 10px;
|
|
287
|
-
}
|
|
288
|
-
.danger-zone {
|
|
289
|
-
background: #fee2e2;
|
|
290
|
-
border-left: 4px solid #dc2626;
|
|
291
|
-
padding: 20px;
|
|
292
|
-
border-radius: 4px;
|
|
293
|
-
margin-top: 30px;
|
|
294
|
-
}
|
|
295
|
-
.danger-zone h3 {
|
|
296
|
-
color: #991b1b;
|
|
297
|
-
margin-top: 0;
|
|
298
|
-
}
|
|
299
|
-
.danger-zone p {
|
|
300
|
-
color: #7c2d12;
|
|
301
|
-
margin-bottom: 15px;
|
|
302
|
-
}
|
|
303
|
-
.btn-danger {
|
|
304
|
-
background: #dc2626;
|
|
305
|
-
color: white;
|
|
306
|
-
}
|
|
307
|
-
.btn-danger:hover {
|
|
308
|
-
background: #b91c1c;
|
|
309
|
-
transform: translateY(-2px);
|
|
310
|
-
box-shadow: 0 5px 15px rgba(220, 38, 38, 0.4);
|
|
311
|
-
}
|
|
312
|
-
.btn-danger:disabled {
|
|
313
|
-
background: #ccc;
|
|
314
|
-
cursor: not-allowed;
|
|
315
|
-
transform: none;
|
|
316
|
-
}
|
|
317
|
-
/* Modal Styles */
|
|
318
|
-
.modal {
|
|
319
|
-
display: none;
|
|
320
|
-
position: fixed;
|
|
321
|
-
z-index: 1000;
|
|
322
|
-
left: 0;
|
|
323
|
-
top: 0;
|
|
324
|
-
width: 100%;
|
|
325
|
-
height: 100%;
|
|
326
|
-
background-color: rgba(0,0,0,0.5);
|
|
327
|
-
}
|
|
328
|
-
.modal-content {
|
|
329
|
-
background-color: white;
|
|
330
|
-
margin: auto;
|
|
331
|
-
padding: 30px;
|
|
332
|
-
border-radius: 12px;
|
|
333
|
-
width: 90%;
|
|
334
|
-
max-width: 500px;
|
|
335
|
-
position: absolute;
|
|
336
|
-
top: 50%;
|
|
337
|
-
left: 50%;
|
|
338
|
-
transform: translate(-50%, -50%);
|
|
339
|
-
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
340
|
-
}
|
|
341
|
-
.modal-content h2 {
|
|
342
|
-
color: #dc2626;
|
|
343
|
-
margin-bottom: 15px;
|
|
344
|
-
}
|
|
345
|
-
.modal-content p {
|
|
346
|
-
color: #666;
|
|
347
|
-
margin-bottom: 20px;
|
|
348
|
-
}
|
|
349
|
-
.modal-buttons {
|
|
350
|
-
display: flex;
|
|
351
|
-
gap: 10px;
|
|
352
|
-
justify-content: flex-end;
|
|
353
|
-
}
|
|
354
|
-
.modal-buttons button {
|
|
355
|
-
padding: 10px 20px;
|
|
356
|
-
border: none;
|
|
357
|
-
border-radius: 6px;
|
|
358
|
-
font-weight: 600;
|
|
359
|
-
cursor: pointer;
|
|
360
|
-
transition: all 0.3s ease;
|
|
361
|
-
}
|
|
362
|
-
.modal-buttons .btn-cancel {
|
|
363
|
-
background: #e5e7eb;
|
|
364
|
-
color: #333;
|
|
365
|
-
}
|
|
366
|
-
.modal-buttons .btn-cancel:hover {
|
|
367
|
-
background: #d1d5db;
|
|
368
|
-
}
|
|
369
|
-
.modal-buttons .btn-confirm {
|
|
370
|
-
background: #dc2626;
|
|
371
|
-
color: white;
|
|
372
|
-
}
|
|
373
|
-
.modal-buttons .btn-confirm:hover {
|
|
374
|
-
background: #b91c1c;
|
|
375
|
-
}
|
|
376
|
-
</style>
|
|
242
|
+
<title>AppClean</title>
|
|
377
243
|
</head>
|
|
378
|
-
<body>
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
A beautiful, intuitive graphical user interface for AppClean, bringing the power of
|
|
386
|
-
intelligent app removal to users who prefer a visual interface.
|
|
387
|
-
</p>
|
|
388
|
-
|
|
389
|
-
<div class="section">
|
|
390
|
-
<h3>📦 Version & Updates</h3>
|
|
391
|
-
<div class="version-info">
|
|
392
|
-
<div class="version-row">
|
|
393
|
-
<span class="label">Current Version:</span>
|
|
394
|
-
<span class="value" id="currentVersion">Loading...</span>
|
|
395
|
-
</div>
|
|
396
|
-
<div class="version-row">
|
|
397
|
-
<span class="label">Latest Version:</span>
|
|
398
|
-
<span class="value" id="latestVersion">Loading...</span>
|
|
399
|
-
</div>
|
|
400
|
-
<div class="version-row">
|
|
401
|
-
<span class="label">Status:</span>
|
|
402
|
-
<span id="updateStatus">Checking...</span>
|
|
403
|
-
</div>
|
|
404
|
-
</div>
|
|
405
|
-
<div class="button-group">
|
|
406
|
-
<button class="btn-secondary" onclick="checkVersion()">🔄 Check for Updates</button>
|
|
407
|
-
<button class="btn-primary" id="upgradeBtn" onclick="upgradeAppClean()" disabled>⬆️ Upgrade</button>
|
|
408
|
-
</div>
|
|
409
|
-
<div id="statusMessage" class="status-message"></div>
|
|
410
|
-
</div>
|
|
411
|
-
|
|
412
|
-
<div class="section">
|
|
413
|
-
<h3>🚀 Features Coming in v1.2.0</h3>
|
|
414
|
-
<ul>
|
|
415
|
-
<li>✨ Modern, responsive GUI design</li>
|
|
416
|
-
<li>🖥️ Cross-platform support (macOS, Linux, Windows)</li>
|
|
417
|
-
<li>🔍 Visual app search and discovery</li>
|
|
418
|
-
<li>📊 Beautiful artifact visualization</li>
|
|
419
|
-
<li>🗑️ Drag-and-drop app removal</li>
|
|
420
|
-
<li>📈 Real-time removal progress</li>
|
|
421
|
-
<li>📋 Interactive report viewer</li>
|
|
422
|
-
</ul>
|
|
423
|
-
</div>
|
|
424
|
-
|
|
425
|
-
<div class="danger-zone">
|
|
426
|
-
<h3>⚠️ Danger Zone</h3>
|
|
427
|
-
<p>
|
|
428
|
-
Uninstall AppClean from your system. This action will remove the application and
|
|
429
|
-
all its global files. This cannot be undone easily.
|
|
430
|
-
</p>
|
|
431
|
-
<div class="button-group">
|
|
432
|
-
<button class="btn-danger" onclick="showUninstallConfirm()">🗑️ Uninstall AppClean</button>
|
|
433
|
-
</div>
|
|
434
|
-
</div>
|
|
435
|
-
</div>
|
|
436
|
-
|
|
437
|
-
<!-- Uninstall Confirmation Modal -->
|
|
438
|
-
<div id="uninstallModal" class="modal">
|
|
439
|
-
<div class="modal-content">
|
|
440
|
-
<h2>⚠️ Confirm Uninstall</h2>
|
|
441
|
-
<p>
|
|
442
|
-
Are you sure you want to uninstall AppClean? This action will remove the application
|
|
443
|
-
from your system and cannot be easily undone.
|
|
444
|
-
</p>
|
|
445
|
-
<p style="color: #dc2626; font-weight: 600;">
|
|
446
|
-
This action cannot be undone!
|
|
447
|
-
</p>
|
|
448
|
-
<div class="modal-buttons">
|
|
449
|
-
<button class="btn-cancel" onclick="closeUninstallConfirm()">Cancel</button>
|
|
450
|
-
<button class="btn-confirm" onclick="confirmUninstall()">Uninstall</button>
|
|
451
|
-
</div>
|
|
452
|
-
</div>
|
|
453
|
-
</div>
|
|
454
|
-
|
|
455
|
-
<script>
|
|
456
|
-
// Check version on page load
|
|
457
|
-
window.addEventListener('load', checkVersion);
|
|
458
|
-
|
|
459
|
-
async function checkVersion() {
|
|
460
|
-
const statusEl = document.getElementById('statusMessage');
|
|
461
|
-
statusEl.textContent = '🔄 Checking for updates...';
|
|
462
|
-
statusEl.className = 'status-message status-loading';
|
|
463
|
-
statusEl.innerHTML = '<span class="spinner"></span>Checking for updates...';
|
|
464
|
-
|
|
465
|
-
try {
|
|
466
|
-
const response = await fetch('/api/version');
|
|
467
|
-
const data = await response.json();
|
|
468
|
-
|
|
469
|
-
document.getElementById('currentVersion').textContent = 'v' + data.current;
|
|
470
|
-
document.getElementById('latestVersion').textContent = 'v' + data.latest;
|
|
471
|
-
|
|
472
|
-
const upgradeBtn = document.getElementById('upgradeBtn');
|
|
473
|
-
const updateStatus = document.getElementById('updateStatus');
|
|
474
|
-
|
|
475
|
-
if (data.isUpdateAvailable) {
|
|
476
|
-
updateStatus.innerHTML = '<span class="update-available">⚠️ Update available!</span>';
|
|
477
|
-
upgradeBtn.disabled = false;
|
|
478
|
-
statusEl.textContent = '✓ Update available! Click the Upgrade button to install.';
|
|
479
|
-
statusEl.className = 'status-message status-success';
|
|
480
|
-
} else {
|
|
481
|
-
updateStatus.innerHTML = '<span class="up-to-date">✓ Up to date</span>';
|
|
482
|
-
upgradeBtn.disabled = true;
|
|
483
|
-
statusEl.textContent = '✓ AppClean is already up to date!';
|
|
484
|
-
statusEl.className = 'status-message status-success';
|
|
485
|
-
}
|
|
486
|
-
} catch (error) {
|
|
487
|
-
statusEl.textContent = '✗ Failed to check for updates: ' + error.message;
|
|
488
|
-
statusEl.className = 'status-message status-error';
|
|
489
|
-
}
|
|
490
|
-
}
|
|
244
|
+
<body style="font-family: system-ui; padding: 20px; text-align: center;">
|
|
245
|
+
<h1>⚠️ GUI Not Ready</h1>
|
|
246
|
+
<p>The SPA assets haven't been compiled yet.</p>
|
|
247
|
+
<p>Run <code>npm run build</code> to compile the TypeScript/CSS files.</p>
|
|
248
|
+
<p>For now, use the CLI: <code>appclean --help</code></p>
|
|
249
|
+
</body>
|
|
250
|
+
</html>`;
|
|
491
251
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
upgradeBtn.disabled = true;
|
|
497
|
-
statusEl.innerHTML = '<span class="spinner"></span>Upgrading AppClean...';
|
|
498
|
-
statusEl.className = 'status-message status-loading';
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
const response = await fetch('/api/upgrade');
|
|
502
|
-
const data = await response.json();
|
|
503
|
-
|
|
504
|
-
if (data.success) {
|
|
505
|
-
statusEl.textContent = '✓ ' + data.message;
|
|
506
|
-
statusEl.className = 'status-message status-success';
|
|
507
|
-
setTimeout(() => {
|
|
508
|
-
statusEl.textContent = 'Please refresh the page or restart the GUI server.';
|
|
509
|
-
checkVersion();
|
|
510
|
-
}, 2000);
|
|
511
|
-
} else {
|
|
512
|
-
statusEl.textContent = '✗ ' + data.message;
|
|
513
|
-
statusEl.className = 'status-message status-error';
|
|
514
|
-
upgradeBtn.disabled = false;
|
|
515
|
-
}
|
|
516
|
-
} catch (error) {
|
|
517
|
-
statusEl.textContent = '✗ Upgrade failed: ' + error.message;
|
|
518
|
-
statusEl.className = 'status-message status-error';
|
|
519
|
-
upgradeBtn.disabled = false;
|
|
520
|
-
}
|
|
252
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
253
|
+
res.end(fallbackHtml);
|
|
254
|
+
return;
|
|
521
255
|
}
|
|
522
256
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
257
|
+
res.writeHead(200, {
|
|
258
|
+
'Content-Type': 'text/html',
|
|
259
|
+
'Cache-Control': 'no-cache',
|
|
260
|
+
});
|
|
261
|
+
res.end(this.spaHtml);
|
|
262
|
+
}
|
|
527
263
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Load SPA HTML from compiled dist
|
|
266
|
+
*/
|
|
267
|
+
private loadSPAHtml(): void {
|
|
268
|
+
const htmlPath = join(__dirname, 'client', 'index.html');
|
|
531
269
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
const response = await fetch('/api/uninstall');
|
|
541
|
-
const data = await response.json();
|
|
542
|
-
|
|
543
|
-
if (data.success) {
|
|
544
|
-
modal.querySelector('p').textContent = '✓ ' + data.message;
|
|
545
|
-
modal.querySelector('h2').textContent = '✓ Uninstall Complete';
|
|
546
|
-
modal.querySelector('.modal-buttons').innerHTML =
|
|
547
|
-
'<button class="btn-cancel" onclick="window.close()">Close</button>';
|
|
548
|
-
setTimeout(() => {
|
|
549
|
-
alert('AppClean has been uninstalled. You can close this window.');
|
|
550
|
-
}, 500);
|
|
551
|
-
} else {
|
|
552
|
-
alert('❌ Uninstall failed: ' + data.message);
|
|
553
|
-
btn.disabled = false;
|
|
554
|
-
btn.textContent = 'Uninstall';
|
|
555
|
-
}
|
|
556
|
-
} catch (error) {
|
|
557
|
-
alert('❌ Error: ' + error.message);
|
|
558
|
-
btn.disabled = false;
|
|
559
|
-
btn.textContent = 'Uninstall';
|
|
270
|
+
try {
|
|
271
|
+
if (existsSync(htmlPath)) {
|
|
272
|
+
this.spaHtml = readFileSync(htmlPath, 'utf-8');
|
|
273
|
+
Logger.debug('✓ Loaded SPA HTML');
|
|
274
|
+
} else {
|
|
275
|
+
Logger.warn(`⚠️ SPA HTML not found at ${htmlPath}`);
|
|
276
|
+
Logger.info('Make sure to run: npm run build');
|
|
560
277
|
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
Logger.warn(`Failed to load SPA HTML: ${(error as Error).message}`);
|
|
561
280
|
}
|
|
281
|
+
}
|
|
562
282
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Determine content type from file extension
|
|
285
|
+
*/
|
|
286
|
+
private getContentType(filePath: string): string {
|
|
287
|
+
const ext = filePath.toLowerCase().split('.').pop();
|
|
288
|
+
|
|
289
|
+
const typeMap: Record<string, string> = {
|
|
290
|
+
'js': 'application/javascript; charset=utf-8',
|
|
291
|
+
'css': 'text/css; charset=utf-8',
|
|
292
|
+
'html': 'text/html; charset=utf-8',
|
|
293
|
+
'json': 'application/json; charset=utf-8',
|
|
294
|
+
'svg': 'image/svg+xml',
|
|
295
|
+
'png': 'image/png',
|
|
296
|
+
'jpg': 'image/jpeg',
|
|
297
|
+
'jpeg': 'image/jpeg',
|
|
298
|
+
'gif': 'image/gif',
|
|
299
|
+
'ico': 'image/x-icon',
|
|
300
|
+
'webp': 'image/webp',
|
|
301
|
+
'woff': 'font/woff',
|
|
302
|
+
'woff2': 'font/woff2',
|
|
303
|
+
'ttf': 'font/ttf',
|
|
304
|
+
'eot': 'application/vnd.ms-fontobject',
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
return typeMap[ext || ''] || 'application/octet-stream';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Set CORS headers
|
|
312
|
+
*/
|
|
313
|
+
private setCORSHeaders(res: ServerResponse): void {
|
|
314
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
315
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
316
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
317
|
+
res.setHeader('Access-Control-Max-Age', '3600');
|
|
574
318
|
}
|
|
575
319
|
}
|
|
320
|
+
|
|
321
|
+
// Export for use in CLI
|
|
322
|
+
export default GUIServer;
|