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.
- package/PUBLICATION_SUCCESS_REPORT.md +227 -0
- package/README.md +176 -426
- package/dist/index.js +139 -1
- package/dist/index.js.map +1 -1
- package/dist/managers/brewManager.d.ts.map +1 -1
- package/dist/managers/brewManager.js +7 -6
- package/dist/managers/brewManager.js.map +1 -1
- package/dist/managers/customManager.d.ts +2 -1
- package/dist/managers/customManager.d.ts.map +1 -1
- package/dist/managers/customManager.js +56 -23
- package/dist/managers/customManager.js.map +1 -1
- package/dist/ui/guiServer.d.ts +4 -0
- package/dist/ui/guiServer.d.ts.map +1 -1
- package/dist/ui/guiServer.js +411 -5
- package/dist/ui/guiServer.js.map +1 -1
- package/dist/utils/upgrade.d.ts +22 -0
- package/dist/utils/upgrade.d.ts.map +1 -0
- package/dist/utils/upgrade.js +98 -0
- package/dist/utils/upgrade.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +128 -1
- package/src/managers/brewManager.ts +8 -6
- package/src/managers/customManager.ts +69 -28
- package/src/ui/guiServer.ts +427 -7
- package/src/utils/upgrade.ts +143 -0
package/src/ui/guiServer.ts
CHANGED
|
@@ -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:
|
|
55
|
-
|
|
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(
|
|
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
|
-
.
|
|
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="
|
|
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
|
|
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
|
+
}
|