appclean 1.8.0 → 1.9.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.
@@ -4,15 +4,18 @@
4
4
  * v1.2.0 Feature
5
5
  */
6
6
 
7
- import { createServer } from 'http';
7
+ import { createServer, IncomingMessage, ServerResponse } from 'http';
8
8
  import { Logger } from '../utils/logger';
9
+ import { UpgradeManager } from '../utils/upgrade';
9
10
 
10
11
  export class GUIServer {
11
12
  private port: number = 3000;
12
13
  private server: any;
14
+ private upgradeManager: UpgradeManager;
13
15
 
14
16
  constructor(port: number = 3000) {
15
17
  this.port = port;
18
+ this.upgradeManager = new UpgradeManager();
16
19
  }
17
20
 
18
21
  /**
@@ -51,10 +54,60 @@ export class GUIServer {
51
54
  /**
52
55
  * Handle API requests
53
56
  */
54
- private handleAPIRequest(req: any, res: any): void {
55
- // API endpoints for GUI
57
+ private async handleAPIRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
58
+ const url = req.url || '';
59
+
60
+ try {
61
+ if (url === '/api/version') {
62
+ await this.handleVersionCheck(res);
63
+ } else if (url === '/api/upgrade') {
64
+ await this.handleUpgrade(res);
65
+ } else if (url === '/api/uninstall') {
66
+ await this.handleUninstall(res);
67
+ } else {
68
+ res.writeHead(404, { 'Content-Type': 'application/json' });
69
+ res.end(JSON.stringify({ error: 'API endpoint not found' }));
70
+ }
71
+ } catch (error) {
72
+ res.writeHead(500, { 'Content-Type': 'application/json' });
73
+ res.end(
74
+ JSON.stringify({ error: (error as Error).message })
75
+ );
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Handle version check request
81
+ */
82
+ private async handleVersionCheck(res: ServerResponse): Promise<void> {
83
+ const versionInfo = await this.upgradeManager.checkForUpdates();
84
+
56
85
  res.writeHead(200, { 'Content-Type': 'application/json' });
57
- res.end(JSON.stringify({ message: 'API endpoint' }));
86
+ res.end(JSON.stringify(versionInfo));
87
+ }
88
+
89
+ /**
90
+ * Handle upgrade request
91
+ */
92
+ private async handleUpgrade(res: ServerResponse): Promise<void> {
93
+ const result = await this.upgradeManager.upgrade();
94
+
95
+ res.writeHead(result.success ? 200 : 500, {
96
+ 'Content-Type': 'application/json',
97
+ });
98
+ res.end(JSON.stringify(result));
99
+ }
100
+
101
+ /**
102
+ * Handle uninstall request
103
+ */
104
+ private async handleUninstall(res: ServerResponse): Promise<void> {
105
+ const result = await this.upgradeManager.uninstall();
106
+
107
+ res.writeHead(result.success ? 200 : 500, {
108
+ 'Content-Type': 'application/json',
109
+ });
110
+ res.end(JSON.stringify(result));
58
111
  }
59
112
 
60
113
  /**
@@ -95,6 +148,11 @@ export class GUIServer {
95
148
  color: #333;
96
149
  margin-bottom: 10px;
97
150
  }
151
+ h3 {
152
+ color: #333;
153
+ margin-top: 20px;
154
+ margin-bottom: 15px;
155
+ }
98
156
  .version {
99
157
  color: #666;
100
158
  font-size: 14px;
@@ -115,13 +173,206 @@ export class GUIServer {
115
173
  line-height: 1.6;
116
174
  margin-bottom: 20px;
117
175
  }
118
- .coming-soon {
176
+ .section {
119
177
  background: #f0f4ff;
120
178
  border-left: 4px solid #667eea;
121
179
  padding: 20px;
122
180
  border-radius: 4px;
123
181
  margin-top: 30px;
124
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
+ }
125
376
  </style>
126
377
  </head>
127
378
  <body>
@@ -135,9 +386,32 @@ export class GUIServer {
135
386
  intelligent app removal to users who prefer a visual interface.
136
387
  </p>
137
388
 
138
- <div class="coming-soon">
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">
139
413
  <h3>🚀 Features Coming in v1.2.0</h3>
140
- <ul style="margin-left: 20px; margin-top: 10px;">
414
+ <ul>
141
415
  <li>✨ Modern, responsive GUI design</li>
142
416
  <li>🖥️ Cross-platform support (macOS, Linux, Windows)</li>
143
417
  <li>🔍 Visual app search and discovery</li>
@@ -147,7 +421,153 @@ export class GUIServer {
147
421
  <li>📋 Interactive report viewer</li>
148
422
  </ul>
149
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>
150
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
+ }
491
+
492
+ async function upgradeAppClean() {
493
+ const statusEl = document.getElementById('statusMessage');
494
+ const upgradeBtn = document.getElementById('upgradeBtn');
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
+ }
521
+ }
522
+
523
+ // Uninstall Functions
524
+ function showUninstallConfirm() {
525
+ document.getElementById('uninstallModal').style.display = 'block';
526
+ }
527
+
528
+ function closeUninstallConfirm() {
529
+ document.getElementById('uninstallModal').style.display = 'none';
530
+ }
531
+
532
+ async function confirmUninstall() {
533
+ const modal = document.getElementById('uninstallModal');
534
+ const btn = modal.querySelector('.btn-confirm');
535
+
536
+ btn.disabled = true;
537
+ btn.textContent = '🗑️ Uninstalling...';
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';
560
+ }
561
+ }
562
+
563
+ // Close modal when clicking outside
564
+ window.onclick = function(event) {
565
+ const modal = document.getElementById('uninstallModal');
566
+ if (event.target === modal) {
567
+ modal.style.display = 'none';
568
+ }
569
+ }
570
+ </script>
151
571
  </body>
152
572
  </html>
153
573
  `;
@@ -0,0 +1,143 @@
1
+ import { execSync } from 'child_process';
2
+ import { Logger } from './logger';
3
+
4
+ export interface VersionInfo {
5
+ current: string;
6
+ latest: string;
7
+ isUpdateAvailable: boolean;
8
+ }
9
+
10
+ export class UpgradeManager {
11
+ private readonly packageName = 'appclean';
12
+ private readonly currentVersion = '1.9.0';
13
+
14
+ /**
15
+ * Get version information from npm registry
16
+ */
17
+ async getVersionInfo(): Promise<VersionInfo> {
18
+ try {
19
+ // Fetch latest version from npm registry
20
+ const output = execSync(
21
+ `npm view ${this.packageName} version --json`,
22
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
23
+ ).trim();
24
+
25
+ const latestVersion = output.replace(/"/g, '');
26
+ const isUpdateAvailable = this.compareVersions(
27
+ this.currentVersion,
28
+ latestVersion
29
+ );
30
+
31
+ return {
32
+ current: this.currentVersion,
33
+ latest: latestVersion,
34
+ isUpdateAvailable,
35
+ };
36
+ } catch (error) {
37
+ Logger.debug(
38
+ 'Failed to check npm registry: ' + (error as Error).message
39
+ );
40
+ return {
41
+ current: this.currentVersion,
42
+ latest: this.currentVersion,
43
+ isUpdateAvailable: false,
44
+ };
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Compare two semantic versions
50
+ * Returns true if currentVersion < latestVersion
51
+ */
52
+ private compareVersions(current: string, latest: string): boolean {
53
+ const currentParts = current.split('.').map(Number);
54
+ const latestParts = latest.split('.').map(Number);
55
+
56
+ for (let i = 0; i < 3; i++) {
57
+ const curr = currentParts[i] || 0;
58
+ const lat = latestParts[i] || 0;
59
+
60
+ if (lat > curr) return true;
61
+ if (lat < curr) return false;
62
+ }
63
+
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * Perform upgrade to latest version
69
+ */
70
+ async upgrade(): Promise<{ success: boolean; message: string }> {
71
+ try {
72
+ Logger.info('Checking for updates...');
73
+
74
+ const versionInfo = await this.getVersionInfo();
75
+
76
+ if (!versionInfo.isUpdateAvailable) {
77
+ return {
78
+ success: true,
79
+ message: `AppClean is already up to date (v${versionInfo.current})`,
80
+ };
81
+ }
82
+
83
+ Logger.info(
84
+ `Upgrading from v${versionInfo.current} to v${versionInfo.latest}...`
85
+ );
86
+
87
+ // Install latest version globally
88
+ execSync(`npm install -g ${this.packageName}@latest`, {
89
+ stdio: 'inherit',
90
+ });
91
+
92
+ return {
93
+ success: true,
94
+ message: `Successfully upgraded to v${versionInfo.latest}`,
95
+ };
96
+ } catch (error) {
97
+ const errorMsg = (error as Error).message;
98
+ return {
99
+ success: false,
100
+ message: `Upgrade failed: ${errorMsg}`,
101
+ };
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Get current version
107
+ */
108
+ getCurrentVersion(): string {
109
+ return this.currentVersion;
110
+ }
111
+
112
+ /**
113
+ * Check if update is available without upgrading
114
+ */
115
+ async checkForUpdates(): Promise<VersionInfo> {
116
+ return this.getVersionInfo();
117
+ }
118
+
119
+ /**
120
+ * Uninstall AppClean from the system
121
+ */
122
+ async uninstall(): Promise<{ success: boolean; message: string }> {
123
+ try {
124
+ Logger.info('Uninstalling AppClean...');
125
+
126
+ // Uninstall global npm package
127
+ execSync(`npm uninstall -g ${this.packageName}`, {
128
+ stdio: 'inherit',
129
+ });
130
+
131
+ return {
132
+ success: true,
133
+ message: `AppClean has been successfully uninstalled from your system.`,
134
+ };
135
+ } catch (error) {
136
+ const errorMsg = (error as Error).message;
137
+ return {
138
+ success: false,
139
+ message: `Uninstall failed: ${errorMsg}`,
140
+ };
141
+ }
142
+ }
143
+ }