vako 1.3.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.
@@ -0,0 +1,1118 @@
1
+ // Fichier de l'auto-updater qui va vérifier si c'est la bonne version de veko
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const https = require('https');
5
+ const crypto = require('crypto');
6
+ const os = require('os');
7
+ const { execSync, spawn } = require('child_process');
8
+ const chalk = require('chalk');
9
+
10
+ class AutoUpdater {
11
+ static packageJsonPath = path.join(process.cwd(), 'package.json');
12
+ static backupDir = path.join(process.cwd(), '.veko-backups');
13
+ static configPath = path.join(process.cwd(), '.veko-updater.json');
14
+ static logPath = path.join(process.cwd(), '.veko-updater.log');
15
+ static currentVersion = null;
16
+ static latestVersion = null;
17
+ static config = {};
18
+ static stats = {
19
+ totalUpdates: 0,
20
+ lastUpdate: null,
21
+ lastCheck: null,
22
+ rollbacks: 0
23
+ };
24
+
25
+ // 🎨 Styles visuels simplifiés
26
+ static styles = {
27
+ title: chalk.bold.cyan,
28
+ success: chalk.bold.green,
29
+ error: chalk.bold.red,
30
+ warning: chalk.bold.yellow,
31
+ info: chalk.bold.blue,
32
+ dim: chalk.dim.gray,
33
+ highlight: chalk.bold.white,
34
+ accent: chalk.magenta,
35
+ progress: chalk.green.bold,
36
+ version: chalk.cyan.bold,
37
+ menu: chalk.yellow.bold,
38
+ separator: chalk.dim('─'.repeat(60))
39
+ };
40
+
41
+ // 🔧 Configuration par défaut
42
+ static defaultConfig = {
43
+ autoCheck: true,
44
+ autoUpdate: false,
45
+ checkInterval: 3600000, // 1 heure
46
+ backupCount: 5,
47
+ allowPrerelease: false,
48
+ allowBeta: false,
49
+ securityCheck: true,
50
+ progressBar: true,
51
+ notifications: true,
52
+ rollbackOnFailure: true,
53
+ updateChannel: 'stable', // stable, beta, alpha
54
+ customRegistry: null,
55
+ excludeFiles: ['.git', 'node_modules', '.veko-backups'],
56
+ skipDependencies: false
57
+ };
58
+
59
+ // 🚀 Initialisation robuste
60
+ static async init() {
61
+ try {
62
+ await this.loadConfig();
63
+ await this.loadStats();
64
+ this.createDirectories();
65
+
66
+ if (this.config.autoCheck) {
67
+ this.scheduleAutoCheck();
68
+ }
69
+
70
+ return true;
71
+ } catch (error) {
72
+ console.error(`[Auto-updater] Erreur d'initialisation: ${error.message}`);
73
+ return false; // Ne pas bloquer l'application en cas d'erreur
74
+ }
75
+ }
76
+
77
+ // 📁 Création des répertoires nécessaires avec gestion d'erreurs
78
+ static createDirectories() {
79
+ try {
80
+ [this.backupDir].forEach(dir => {
81
+ if (!fs.existsSync(dir)) {
82
+ fs.mkdirSync(dir, { recursive: true });
83
+ }
84
+ });
85
+ } catch (error) {
86
+ console.warn(`[Auto-updater] Impossible de créer les répertoires: ${error.message}`);
87
+ }
88
+ }
89
+
90
+ // ⚙️ Chargement de la configuration avec fallback
91
+ static async loadConfig() {
92
+ try {
93
+ if (fs.existsSync(this.configPath)) {
94
+ const configData = fs.readFileSync(this.configPath, 'utf8');
95
+ this.config = { ...this.defaultConfig, ...JSON.parse(configData) };
96
+ } else {
97
+ this.config = { ...this.defaultConfig };
98
+ await this.saveConfig();
99
+ }
100
+ } catch (error) {
101
+ console.warn(`[Auto-updater] Erreur de configuration: ${error.message}`);
102
+ this.config = { ...this.defaultConfig };
103
+ }
104
+ }
105
+
106
+ // 💾 Sauvegarde de la configuration avec sécurité
107
+ static async saveConfig() {
108
+ try {
109
+ fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
110
+ } catch (error) {
111
+ console.warn(`[Auto-updater] Impossible de sauvegarder la configuration: ${error.message}`);
112
+ }
113
+ }
114
+
115
+ // 📊 Chargement des statistiques de manière sécurisée
116
+ static async loadStats() {
117
+ try {
118
+ if (fs.existsSync(this.packageJsonPath)) {
119
+ const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf8'));
120
+ if (packageJson.vekoUpdaterStats) {
121
+ this.stats = { ...this.stats, ...packageJson.vekoUpdaterStats };
122
+ }
123
+ }
124
+ } catch (error) {
125
+ console.warn(`[Auto-updater] Impossible de charger les statistiques: ${error.message}`);
126
+ }
127
+ }
128
+
129
+ // 🔄 Programmation de la vérification automatique sécurisée
130
+ static scheduleAutoCheck() {
131
+ try {
132
+ setInterval(async () => {
133
+ try {
134
+ await this.checkForUpdates(true);
135
+ } catch (error) {
136
+ // Capture l'erreur pour ne pas arrêter le processus
137
+ console.error(`[Auto-updater] Erreur de vérification: ${error.message}`);
138
+ }
139
+ }, this.config.checkInterval);
140
+ } catch (error) {
141
+ console.error(`[Auto-updater] Erreur de programmation: ${error.message}`);
142
+ }
143
+ }
144
+
145
+ // 📊 Barre de progression
146
+ static showProgress(current, total, message = '') {
147
+ if (!this.config.progressBar) return;
148
+
149
+ const percentage = Math.round((current / total) * 100);
150
+ const barLength = 40;
151
+ const filledLength = Math.round(barLength * percentage / 100);
152
+ const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
153
+
154
+ process.stdout.write(`\r${this.styles.progress(bar)} ${percentage}% ${message}`);
155
+
156
+ if (current === total) {
157
+ console.log(''); // Nouvelle ligne à la fin
158
+ }
159
+ }
160
+
161
+ // 🎯 Animation de chargement
162
+ static loadingAnimation(message) {
163
+ if (!process.stdout.isTTY) return { stop: () => {} };
164
+
165
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
166
+ let i = 0;
167
+ const loader = setInterval(() => {
168
+ process.stdout.write(`\r${this.styles.info(frames[i++ % frames.length])} ${message}`);
169
+ }, 80);
170
+
171
+ // Retourne une fonction pour arrêter l'animation
172
+ return {
173
+ stop: (finalMessage = '') => {
174
+ clearInterval(loader);
175
+ process.stdout.write(`\r${' '.repeat(message.length + 10)}\r`);
176
+ if (finalMessage) {
177
+ console.log(finalMessage);
178
+ }
179
+ }
180
+ };
181
+ }
182
+
183
+ // 🔍 Vérification de mise à jour avec timeout et animation
184
+ static async checkForUpdates(silent = false) {
185
+ try {
186
+ // Animation si pas en mode silencieux
187
+ const animation = !silent ?
188
+ this.loadingAnimation('Vérification des mises à jour...') :
189
+ { stop: () => {} };
190
+
191
+ this.stats.lastCheck = new Date().toISOString();
192
+
193
+ const currentVersion = this.getCurrentVersion();
194
+ if (!currentVersion) {
195
+ animation.stop(!silent ?
196
+ this.styles.warning('⚠️ Veko n\'est pas installé.') : '');
197
+ return { hasUpdate: false, needsInstall: true };
198
+ }
199
+
200
+ // Timeout pour éviter les boucles infinies
201
+ const versionInfoPromise = Promise.race([
202
+ this.getVersionInfo(),
203
+ new Promise((_, reject) =>
204
+ setTimeout(() => reject(new Error('Timeout lors de la vérification')), 5000)
205
+ )
206
+ ]);
207
+
208
+ const versionInfo = await versionInfoPromise;
209
+
210
+ if (!versionInfo) {
211
+ animation.stop(!silent ?
212
+ this.styles.error('❌ Impossible de récupérer les informations de version') : '');
213
+ throw new Error('Impossible de récupérer les informations de version');
214
+ }
215
+
216
+ const comparison = this.compareVersions(currentVersion, versionInfo.latest);
217
+
218
+ if (comparison < 0) {
219
+ animation.stop(!silent ?
220
+ this.styles.warning(`⚠️ Nouvelle version disponible! ${currentVersion} → ${versionInfo.latest}`) : '');
221
+
222
+ if (!silent) {
223
+ console.log(this.styles.info(` Actuelle: ${this.styles.version(currentVersion)}`));
224
+ console.log(this.styles.info(` Dernière: ${this.styles.version(versionInfo.latest)}`));
225
+
226
+ if (versionInfo.changelog) {
227
+ console.log(this.styles.info('\n📝 Notes de mise à jour:'));
228
+ console.log(this.styles.dim(`${versionInfo.changelog.substring(0, 500)}...`));
229
+ }
230
+ }
231
+
232
+ return {
233
+ hasUpdate: true,
234
+ currentVersion,
235
+ latestVersion: versionInfo.latest,
236
+ changelog: versionInfo.changelog,
237
+ security: versionInfo.security
238
+ };
239
+ } else {
240
+ animation.stop(!silent ?
241
+ this.styles.success(`✅ Version à jour (${currentVersion})`) : '');
242
+ return { hasUpdate: false, currentVersion };
243
+ }
244
+
245
+ } catch (error) {
246
+ if (!silent) {
247
+ console.log(this.styles.error(`❌ ${error.message}`));
248
+ }
249
+ this.logError(`Erreur lors de la vérification: ${error.message}`);
250
+ return { hasUpdate: false, error: error.message };
251
+ }
252
+ }
253
+
254
+ // 🔐 Vérification de sécurité et intégrité
255
+ static async verifyPackageIntegrity(packagePath, expectedIntegrity) {
256
+ if (!this.config.securityCheck || !expectedIntegrity) {
257
+ return true;
258
+ }
259
+
260
+ try {
261
+ const fileBuffer = fs.readFileSync(packagePath);
262
+ const hash = crypto.createHash('sha512').update(fileBuffer).digest('base64');
263
+ const calculatedIntegrity = `sha512-${hash}`;
264
+
265
+ return calculatedIntegrity === expectedIntegrity;
266
+ } catch (error) {
267
+ this.log('error', `Erreur lors de la vérification d'intégrité: ${error.message}`);
268
+ return false;
269
+ }
270
+ }
271
+
272
+ // 💾 Système de backup amélioré
273
+ static async createBackup() {
274
+ try {
275
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
276
+ const backupPath = path.join(this.backupDir, `backup-${timestamp}`);
277
+
278
+ console.log(this.styles.info('💾 Création du backup...'));
279
+
280
+ // Copie les fichiers essentiels
281
+ const filesToBackup = [
282
+ 'package.json',
283
+ 'package-lock.json',
284
+ 'yarn.lock',
285
+ 'node_modules/veko'
286
+ ];
287
+
288
+ fs.mkdirSync(backupPath, { recursive: true });
289
+
290
+ for (let i = 0; i < filesToBackup.length; i++) {
291
+ const file = filesToBackup[i];
292
+ const sourcePath = path.join(process.cwd(), file);
293
+ const destPath = path.join(backupPath, file);
294
+
295
+ if (fs.existsSync(sourcePath)) {
296
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
297
+
298
+ if (fs.statSync(sourcePath).isDirectory()) {
299
+ await this.copyDirectory(sourcePath, destPath);
300
+ } else {
301
+ fs.copyFileSync(sourcePath, destPath);
302
+ }
303
+ }
304
+
305
+ this.showProgress(i + 1, filesToBackup.length, 'Backup en cours...');
306
+ }
307
+
308
+ // Nettoyage des anciens backups
309
+ this.cleanupOldBackups();
310
+
311
+ console.log(this.styles.success(`✅ Backup créé: ${backupPath}`));
312
+ return backupPath;
313
+
314
+ } catch (error) {
315
+ this.log('error', `Erreur lors de la création du backup: ${error.message}`);
316
+ throw error;
317
+ }
318
+ }
319
+
320
+ // 📁 Copie récursive de répertoires
321
+ static async copyDirectory(source, destination) {
322
+ if (!fs.existsSync(destination)) {
323
+ fs.mkdirSync(destination, { recursive: true });
324
+ }
325
+
326
+ const items = fs.readdirSync(source);
327
+
328
+ for (const item of items) {
329
+ const sourcePath = path.join(source, item);
330
+ const destPath = path.join(destination, item);
331
+
332
+ if (fs.statSync(sourcePath).isDirectory()) {
333
+ await this.copyDirectory(sourcePath, destPath);
334
+ } else {
335
+ fs.copyFileSync(sourcePath, destPath);
336
+ }
337
+ }
338
+ }
339
+
340
+ // 🧹 Nettoyage des anciens backups
341
+ static cleanupOldBackups() {
342
+ try {
343
+ const backups = fs.readdirSync(this.backupDir)
344
+ .filter(dir => dir.startsWith('backup-'))
345
+ .map(dir => ({
346
+ name: dir,
347
+ path: path.join(this.backupDir, dir),
348
+ mtime: fs.statSync(path.join(this.backupDir, dir)).mtime
349
+ }))
350
+ .sort((a, b) => b.mtime - a.mtime);
351
+
352
+ if (backups.length > this.config.backupCount) {
353
+ const toDelete = backups.slice(this.config.backupCount);
354
+ toDelete.forEach(backup => {
355
+ fs.rmSync(backup.path, { recursive: true, force: true });
356
+ this.log('info', `Backup supprimé: ${backup.name}`);
357
+ });
358
+ }
359
+ } catch (error) {
360
+ this.log('error', `Erreur lors du nettoyage des backups: ${error.message}`);
361
+ }
362
+ }
363
+
364
+ // 🔄 Rollback vers un backup spécifié
365
+ static async rollback(backupPath) {
366
+ try {
367
+ // Si le chemin n'est pas spécifié, utiliser le plus récent
368
+ if (!backupPath) {
369
+ const backups = fs.readdirSync(this.backupDir)
370
+ .filter(dir => dir.startsWith('backup-'))
371
+ .map(dir => path.join(this.backupDir, dir))
372
+ .sort((a, b) =>
373
+ fs.statSync(b).mtime.getTime() - fs.statSync(a).mtime.getTime()
374
+ );
375
+
376
+ if (backups.length === 0) {
377
+ throw new Error('Aucun backup disponible');
378
+ }
379
+
380
+ backupPath = backups[0];
381
+ console.log(this.styles.info(`Utilisation du backup le plus récent: ${path.basename(backupPath)}`));
382
+ }
383
+
384
+ if (!fs.existsSync(backupPath)) {
385
+ throw new Error(`Backup non trouvé: ${backupPath}`);
386
+ }
387
+
388
+ console.log(this.styles.info('🔄 Restauration en cours...'));
389
+
390
+ const backupFiles = fs.readdirSync(backupPath);
391
+
392
+ for (let i = 0; i < backupFiles.length; i++) {
393
+ const file = backupFiles[i];
394
+ const sourcePath = path.join(backupPath, file);
395
+ const destPath = path.join(process.cwd(), file);
396
+
397
+ if (fs.statSync(sourcePath).isDirectory()) {
398
+ if (fs.existsSync(destPath)) {
399
+ fs.rmSync(destPath, { recursive: true, force: true });
400
+ }
401
+ await this.copyDirectory(sourcePath, destPath);
402
+ } else {
403
+ fs.copyFileSync(sourcePath, destPath);
404
+ }
405
+
406
+ this.showProgress(i + 1, backupFiles.length, 'Restauration...');
407
+ }
408
+
409
+ this.stats.rollbacks++;
410
+ await this.saveStats();
411
+
412
+ console.log(this.styles.success('✅ Rollback effectué avec succès!'));
413
+ return true;
414
+
415
+ } catch (error) {
416
+ this.log('error', `Erreur lors du rollback: ${error.message}`);
417
+ console.log(this.styles.error(`❌ ${error.message}`));
418
+ return false;
419
+ }
420
+ }
421
+
422
+ // 🚀 Mise à jour améliorée avec détection de npm
423
+ static async performUpdate(versionInfo) {
424
+ let backupPath = null;
425
+
426
+ try {
427
+ // Création du backup
428
+ backupPath = await this.createBackup();
429
+
430
+ console.log(this.styles.info('🚀 Mise à jour en cours...'));
431
+
432
+ // Trouver le chemin npm correct selon la plateforme
433
+ const isWindows = process.platform === 'win32';
434
+ const npmCommand = isWindows ? 'npm.cmd' : 'npm';
435
+
436
+ // Désinstallation de l'ancienne version
437
+ console.log(this.styles.info('📦 Désinstallation de l\'ancienne version...'));
438
+ try {
439
+ execSync(`${npmCommand} uninstall veko`, { stdio: 'pipe' });
440
+ } catch (error) {
441
+ // Si echec, essayer avec npx
442
+ console.log(this.styles.warning('⚠️ Tentative alternative avec npx...'));
443
+ execSync(`${isWindows ? 'npx.cmd' : 'npx'} -y npm uninstall veko`, { stdio: 'pipe' });
444
+ }
445
+
446
+ // Installation de la nouvelle version
447
+ console.log(this.styles.info(`📦 Installation de veko@${versionInfo.latestVersion}...`));
448
+
449
+ // Utiliser le chemin complet vers npm si disponible
450
+ const installProcess = spawn(npmCommand, ['install', `veko@${versionInfo.latestVersion}`], {
451
+ stdio: ['pipe', 'pipe', 'pipe'],
452
+ shell: true // Utiliser un shell pour une meilleure compatibilité
453
+ });
454
+
455
+ let installOutput = '';
456
+ installProcess.stdout.on('data', (data) => {
457
+ installOutput += data.toString();
458
+ });
459
+
460
+ installProcess.stderr.on('data', (data) => {
461
+ installOutput += data.toString();
462
+ });
463
+
464
+ await new Promise((resolve, reject) => {
465
+ installProcess.on('close', (code) => {
466
+ if (code === 0) {
467
+ resolve();
468
+ } else {
469
+ reject(new Error(`Installation échouée avec le code ${code}: ${installOutput}`));
470
+ }
471
+ });
472
+
473
+ installProcess.on('error', (err) => {
474
+ // Capturer les erreurs de spawn
475
+ reject(new Error(`Erreur lors du lancement du processus npm: ${err.message}`));
476
+ });
477
+ });
478
+
479
+ // Vérification post-installation
480
+ const newVersion = this.getCurrentVersion();
481
+ if (newVersion !== versionInfo.latestVersion) {
482
+ throw new Error('La version installée ne correspond pas à la version attendue');
483
+ }
484
+
485
+ // Mise à jour des statistiques
486
+ this.stats.totalUpdates++;
487
+ this.stats.lastUpdate = new Date().toISOString();
488
+ await this.saveStats();
489
+
490
+ console.log(this.styles.success(`✅ Mise à jour réussie vers la version ${versionInfo.latestVersion}!`));
491
+
492
+ if (this.config.notifications) {
493
+ this.showNotification('Veko mis à jour avec succès!', `Version ${versionInfo.latestVersion}`);
494
+ }
495
+
496
+ return true;
497
+
498
+ } catch (error) {
499
+ this.log('error', `Erreur lors de la mise à jour: ${error.message}`);
500
+ console.log(this.styles.error(`❌ Erreur: ${error.message}`));
501
+
502
+ if (this.config.rollbackOnFailure && backupPath) {
503
+ console.log(this.styles.warning('🔄 Rollback automatique...'));
504
+ await this.rollback(backupPath);
505
+ }
506
+
507
+ return false;
508
+ }
509
+ }
510
+
511
+ // 🔔 Notification système
512
+ static showNotification(title, message) {
513
+ try {
514
+ const platform = os.platform();
515
+
516
+ if (platform === 'darwin') {
517
+ execSync(`osascript -e 'display notification "${message}" with title "${title}"'`);
518
+ } else if (platform === 'win32') {
519
+ // Windows notification (nécessite des outils supplémentaires)
520
+ console.log(this.styles.info(`🔔 ${title}: ${message}`));
521
+ } else if (platform === 'linux') {
522
+ execSync(`notify-send "${title}" "${message}"`);
523
+ }
524
+ } catch (error) {
525
+ // Ignore les erreurs de notification
526
+ }
527
+ }
528
+
529
+ // 📊 Affichage des statistiques
530
+ static displayStats() {
531
+ console.log(this.styles.title('\n📊 Statistiques de l\'auto-updater'));
532
+ console.log(this.styles.separator);
533
+ console.log(this.styles.info(`Mises à jour totales: ${this.stats.totalUpdates}`));
534
+ console.log(this.styles.info(`Rollbacks effectués: ${this.stats.rollbacks}`));
535
+ console.log(this.styles.info(`Dernière vérification: ${this.stats.lastCheck || 'Jamais'}`));
536
+ console.log(this.styles.info(`Dernière mise à jour: ${this.stats.lastUpdate || 'Jamais'}`));
537
+ console.log(this.styles.info(`Version actuelle: ${this.getCurrentVersion() || 'Non installé'}`));
538
+ console.log(this.styles.info(`Canal de mise à jour: ${this.config.updateChannel}`));
539
+ console.log(this.styles.separator);
540
+ }
541
+
542
+ // ⚙️ Configuration de base
543
+ static async configureSettings(options = {}) {
544
+ try {
545
+ // Mise à jour des options de configuration avec les paramètres passés
546
+ if (options && typeof options === 'object') {
547
+ this.config = { ...this.config, ...options };
548
+ await this.saveConfig();
549
+ return true;
550
+ }
551
+
552
+ console.log(this.styles.title('\n⚙️ Configuration actuelle:'));
553
+ console.log(this.styles.separator);
554
+ console.log(this.styles.info(`Vérification auto: ${this.config.autoCheck ? '✅' : '❌'}`));
555
+ console.log(this.styles.info(`Mise à jour auto: ${this.config.autoUpdate ? '✅' : '❌'}`));
556
+ console.log(this.styles.info(`Canal: ${this.config.updateChannel}`));
557
+ console.log(this.styles.info(`Backups: ${this.config.backupCount}`));
558
+ console.log(this.styles.info(`Vérification sécurité: ${this.config.securityCheck ? '✅' : '❌'}`));
559
+ console.log(this.styles.info(`Notifications: ${this.config.notifications ? '✅' : '❌'}`));
560
+ console.log(this.styles.info(`Rollback auto: ${this.config.rollbackOnFailure ? '✅' : '❌'}`));
561
+ console.log(this.styles.separator);
562
+
563
+ return true;
564
+ } catch (error) {
565
+ console.log(this.styles.error(`❌ Erreur: ${error.message}`));
566
+ return false;
567
+ }
568
+ }
569
+
570
+ // 🔌 Gestion des WebSocket avec sécurité améliorée
571
+ static async getVersionInfo() {
572
+ return new Promise((resolve, reject) => {
573
+ try {
574
+ const registry = this.config.customRegistry || 'registry.npmjs.org';
575
+ const options = {
576
+ hostname: registry,
577
+ path: '/veko',
578
+ method: 'GET',
579
+ headers: {
580
+ 'User-Agent': `veko-auto-updater/2.0.0 (${os.platform()} ${os.arch()})`,
581
+ 'Accept': 'application/json'
582
+ },
583
+ timeout: 5000 // Timeout explicite
584
+ };
585
+
586
+ const req = https.request(options, (res) => {
587
+ let data = '';
588
+
589
+ res.on('data', (chunk) => {
590
+ // Limite la taille des données pour éviter les attaques DoS
591
+ if (data.length > 1000000) { // Limite à ~1MB
592
+ req.destroy();
593
+ reject(new Error('Réponse trop volumineuse'));
594
+ return;
595
+ }
596
+ data += chunk;
597
+ });
598
+
599
+ res.on('end', () => {
600
+ if (res.statusCode !== 200) {
601
+ reject(new Error(`Erreur HTTP ${res.statusCode}`));
602
+ return;
603
+ }
604
+
605
+ try {
606
+ const packageInfo = JSON.parse(data);
607
+ const channel = this.config.updateChannel;
608
+
609
+ if (!packageInfo['dist-tags']) {
610
+ reject(new Error('Format de réponse invalide'));
611
+ return;
612
+ }
613
+
614
+ let version;
615
+ switch (channel) {
616
+ case 'beta':
617
+ version = packageInfo['dist-tags'].beta || packageInfo['dist-tags'].latest;
618
+ break;
619
+ case 'alpha':
620
+ version = packageInfo['dist-tags'].alpha || packageInfo['dist-tags'].beta || packageInfo['dist-tags'].latest;
621
+ break;
622
+ case 'stable':
623
+ default:
624
+ version = packageInfo['dist-tags'].latest;
625
+ }
626
+
627
+ if (!version || !packageInfo.versions || !packageInfo.versions[version]) {
628
+ reject(new Error(`Version invalide: ${version}`));
629
+ return;
630
+ }
631
+
632
+ const versionInfo = packageInfo.versions[version];
633
+
634
+ resolve({
635
+ latest: version,
636
+ changelog: versionInfo?.changelog || (packageInfo.readme?.slice(0, 500) || 'Pas de notes de mise à jour disponibles'),
637
+ security: versionInfo?.security || false,
638
+ size: versionInfo?.dist?.unpackedSize,
639
+ integrity: versionInfo?.dist?.integrity,
640
+ publishDate: versionInfo?.time
641
+ });
642
+ } catch (error) {
643
+ reject(new Error(`Erreur lors du parsing: ${error.message}`));
644
+ }
645
+ });
646
+ });
647
+
648
+ // Gestion explicite des erreurs
649
+ req.on('error', (error) => {
650
+ reject(new Error(`Erreur de connexion: ${error.message}`));
651
+ });
652
+
653
+ // Timeout manuels pour plus de contrôle
654
+ req.setTimeout(10000, () => {
655
+ req.destroy();
656
+ reject(new Error('Timeout de connexion'));
657
+ });
658
+
659
+ req.end();
660
+ } catch (error) {
661
+ reject(new Error(`Erreur lors de la requête: ${error.message}`));
662
+ }
663
+ });
664
+ }
665
+
666
+ // ❓ Aide simplifiée
667
+ static showHelp() {
668
+ console.log(this.styles.title('\n❓ Aide - Veko Auto-Updater'));
669
+ console.log(this.styles.separator);
670
+ console.log('Commandes disponibles:');
671
+ console.log(' veko update check - Vérifier les mises à jour');
672
+ console.log(' veko update update - Mettre à jour maintenant');
673
+ console.log(' veko update config - Afficher la configuration');
674
+ console.log(' veko update rollback - Effectuer un rollback');
675
+ console.log(' veko update stats - Afficher les statistiques');
676
+ console.log(' veko update fix - Réparer l\'auto-updater');
677
+ console.log(' veko update help - Afficher l\'aide');
678
+ console.log(' veko update version - Afficher la version');
679
+ console.log(this.styles.separator);
680
+ }
681
+
682
+ // 🎯 Fonction principale améliorée
683
+ static async checkAndUpdate() {
684
+ try {
685
+ await this.init();
686
+
687
+ // Vérifier npm en avance
688
+ try {
689
+ await this.ensureNpm();
690
+ } catch (error) {
691
+ console.log(this.styles.error(`❌ ${error.message} - L'auto-updater a besoin de npm pour fonctionner.`));
692
+ return false;
693
+ }
694
+
695
+ // Animation de chargement
696
+ const animation = this.loadingAnimation('Vérification des mises à jour...');
697
+
698
+ // Vérification si package.json existe
699
+ if (!fs.existsSync(this.packageJsonPath)) {
700
+ animation.stop(this.styles.error('❌ Le fichier package.json est manquant.'));
701
+ console.log(this.styles.error('Un fichier package.json est nécessaire.'));
702
+ return false;
703
+ }
704
+
705
+ // Vérification des mises à jour avec timeout
706
+ const updateInfo = await Promise.race([
707
+ this.checkForUpdates(true),
708
+ new Promise((_, reject) =>
709
+ setTimeout(() => reject(new Error('Timeout lors de la vérification')), 5000)
710
+ )
711
+ ]);
712
+
713
+ animation.stop();
714
+
715
+ if (updateInfo.needsInstall) {
716
+ console.log(this.styles.warning('⚠️ Veko n\'est pas installé. Installation en cours...'));
717
+ try {
718
+ execSync('npm install veko@latest', { stdio: 'inherit' });
719
+ console.log(this.styles.success('✅ Veko installé avec succès!'));
720
+ return true;
721
+ } catch (error) {
722
+ console.log(this.styles.error(`❌ Erreur lors de l'installation: ${error.message}`));
723
+ return false;
724
+ }
725
+ }
726
+
727
+ if (updateInfo.hasUpdate) {
728
+ console.log(this.styles.warning(`⚠️ Nouvelle version disponible! ${updateInfo.currentVersion} → ${updateInfo.latestVersion}`));
729
+ if (this.config.autoUpdate) {
730
+ return await this.performUpdate(updateInfo);
731
+ } else {
732
+ console.log(this.styles.info('Pour mettre à jour: veko update update'));
733
+ }
734
+ } else if (updateInfo.error) {
735
+ console.log(this.styles.error(`❌ Erreur: ${updateInfo.error}`));
736
+ return false;
737
+ } else {
738
+ console.log(this.styles.success('✅ Veko est à jour!'));
739
+ }
740
+
741
+ return true;
742
+
743
+ } catch (error) {
744
+ this.log('error', `Erreur inattendue: ${error.message}`);
745
+ console.log(this.styles.error(`❌ Erreur inattendue: ${error.message}`));
746
+ return false;
747
+ }
748
+ }
749
+
750
+ // 🧪 Installation sécurisée de npm avec plusieurs méthodes
751
+ static async ensureNpm() {
752
+ const isWindows = process.platform === 'win32';
753
+ const npmCommands = [
754
+ isWindows ? 'npm.cmd' : 'npm',
755
+ isWindows ? 'npx.cmd' : 'npx',
756
+ 'npm', // Essayer sans extension sur Windows aussi
757
+ path.join(process.execPath, '..', isWindows ? 'npm.cmd' : 'npm')
758
+ ];
759
+
760
+ for (const cmd of npmCommands) {
761
+ try {
762
+ execSync(`${cmd} --version`, { stdio: 'pipe' });
763
+ return cmd; // Retourner la première commande qui fonctionne
764
+ } catch (e) {
765
+ // Continuer avec la commande suivante
766
+ }
767
+ }
768
+
769
+ throw new Error('npm introuvable sur le système');
770
+ }
771
+
772
+ // 🚀 Commande de mise à jour spécifique améliorée
773
+ static async performUpdateCommand() {
774
+ try {
775
+ // Vérifier npm en avance
776
+ try {
777
+ await this.ensureNpm();
778
+ } catch (error) {
779
+ console.log(this.styles.error(`❌ ${error.message} - L'auto-updater a besoin de npm pour fonctionner.`));
780
+ return false;
781
+ }
782
+
783
+ // Vérifier les mises à jour
784
+ const updateInfo = await this.checkForUpdates(true);
785
+
786
+ if (updateInfo.hasUpdate) {
787
+ console.log(this.styles.warning(`⚠️ Mise à jour disponible: ${updateInfo.currentVersion} → ${updateInfo.latestVersion}`));
788
+ console.log(this.styles.info('🚀 Démarrage de la mise à jour...'));
789
+
790
+ return await this.performUpdate(updateInfo);
791
+ } else if (updateInfo.needsInstall) {
792
+ console.log(this.styles.warning('⚠️ Veko n\'est pas installé. Installation en cours...'));
793
+
794
+ try {
795
+ execSync('npm install veko@latest', { stdio: 'inherit' });
796
+ console.log(this.styles.success('✅ Veko installé avec succès!'));
797
+ return true;
798
+ } catch (error) {
799
+ console.log(this.styles.error(`❌ Erreur lors de l'installation: ${error.message}`));
800
+ return false;
801
+ }
802
+ } else {
803
+ console.log(this.styles.success('✅ Veko est déjà à jour!'));
804
+ return true;
805
+ }
806
+ } catch (error) {
807
+ console.log(this.styles.error(`❌ Erreur lors de la mise à jour: ${error.message}`));
808
+ return false;
809
+ }
810
+ }
811
+
812
+ // 📋 Afficher version
813
+ static showVersion() {
814
+ const version = this.getCurrentVersion() || 'non installé';
815
+ console.log(`Veko v${version}`);
816
+ console.log(`Auto-updater v1.1.5`);
817
+ return true;
818
+ }
819
+
820
+ // 🔧 Réparer l'installation
821
+ static async fixInstallation() {
822
+ console.log(this.styles.title('\n🔧 Réparation de l\'installation'));
823
+ console.log(this.styles.separator);
824
+
825
+ try {
826
+ // 1. Créer les répertoires nécessaires
827
+ console.log('1. Vérification des répertoires');
828
+ this.createDirectories();
829
+ console.log(this.styles.success('✅ Répertoires vérifiés'));
830
+
831
+ // 2. Réinitialiser la configuration
832
+ console.log('2. Réinitialisation de la configuration');
833
+ this.config = { ...this.defaultConfig };
834
+ await this.saveConfig();
835
+ console.log(this.styles.success('✅ Configuration réinitialisée'));
836
+
837
+ // 3. Vérifier package.json
838
+ console.log('3. Vérification de package.json');
839
+ if (!fs.existsSync(this.packageJsonPath)) {
840
+ console.log(this.styles.warning('⚠️ package.json manquant'));
841
+ console.log(this.styles.error('❌ Impossible de continuer sans package.json'));
842
+ return false;
843
+ } else {
844
+ console.log(this.styles.success('✅ package.json trouvé'));
845
+
846
+ // Vérifier l'installation de veko
847
+ const vekoInstalled = this.getCurrentVersion();
848
+ if (!vekoInstalled) {
849
+ console.log(this.styles.warning('⚠️ Veko non installé, tentative d\'installation'));
850
+ try {
851
+ execSync('npm install veko@latest', { stdio: 'inherit' });
852
+ console.log(this.styles.success('✅ Veko installé'));
853
+ } catch (error) {
854
+ console.log(this.styles.error(`❌ Erreur d'installation: ${error.message}`));
855
+ }
856
+ } else {
857
+ console.log(this.styles.success(`✅ Veko v${vekoInstalled} installé`));
858
+ }
859
+ }
860
+
861
+ // 4. Reset du log
862
+ console.log('4. Nettoyage des logs');
863
+ if (fs.existsSync(this.logPath)) {
864
+ fs.writeFileSync(this.logPath, '');
865
+ console.log(this.styles.success('✅ Logs nettoyés'));
866
+ }
867
+
868
+ console.log(this.styles.separator);
869
+ console.log(this.styles.success('🎉 Réparation terminée!'));
870
+ console.log(this.styles.info('💡 Utilisez "veko update check" pour vérifier les mises à jour'));
871
+
872
+ return true;
873
+ } catch (error) {
874
+ console.log(this.styles.error(`❌ Erreur lors de la réparation: ${error.message}`));
875
+ return false;
876
+ }
877
+ }
878
+
879
+ // 📄 Récupération de la version actuelle plus robuste
880
+ static getCurrentVersion() {
881
+ try {
882
+ if (!fs.existsSync(this.packageJsonPath)) {
883
+ return null;
884
+ }
885
+
886
+ const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf8'));
887
+
888
+ const vekoVersion = packageJson.dependencies?.veko ||
889
+ packageJson.devDependencies?.veko ||
890
+ packageJson.peerDependencies?.veko;
891
+
892
+ if (!vekoVersion) {
893
+ return null;
894
+ }
895
+
896
+ this.currentVersion = vekoVersion.replace(/[\^~>=<]/g, '');
897
+ return this.currentVersion;
898
+ } catch (error) {
899
+ console.warn(`[Auto-updater] Erreur lors de la lecture de package.json: ${error.message}`);
900
+ return null;
901
+ }
902
+ }
903
+
904
+ // 📝 Système de logs amélioré avec gestion d'erreurs renforcée
905
+ static log(level, message) {
906
+ try {
907
+ const timestamp = new Date().toISOString();
908
+ const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
909
+
910
+ // Affichage console avec couleurs
911
+ const colorMap = {
912
+ error: this.styles.error,
913
+ warn: this.styles.warning,
914
+ info: this.styles.info,
915
+ success: this.styles.success,
916
+ debug: this.styles.dim
917
+ };
918
+
919
+ const colorFunc = colorMap[level] || chalk.white;
920
+ console.log(colorFunc(`[${level.toUpperCase()}] ${message}`));
921
+
922
+ // Écriture dans le fichier de log, mais seulement si accessible
923
+ try {
924
+ if (!fs.existsSync(path.dirname(this.logPath))) {
925
+ fs.mkdirSync(path.dirname(this.logPath), { recursive: true });
926
+ }
927
+ fs.appendFileSync(this.logPath, logEntry);
928
+ this.rotateLogFile();
929
+ } catch (error) {
930
+ // Ignore les erreurs d'écriture dans le fichier
931
+ }
932
+ } catch (error) {
933
+ // Éviter les boucles infinies avec console.error
934
+ console.error(`Erreur dans le système de log: ${error.message}`);
935
+ }
936
+ }
937
+
938
+ // 📝 Méthode de fallback pour le logging d'erreurs
939
+ static logError(message) {
940
+ try {
941
+ console.error(`[ERROR] ${message}`);
942
+ // Tentative d'écriture dans le fichier de log
943
+ if (fs.existsSync(path.dirname(this.logPath))) {
944
+ const timestamp = new Date().toISOString();
945
+ const logEntry = `[${timestamp}] [ERROR] ${message}\n`;
946
+ fs.appendFileSync(this.logPath, logEntry);
947
+ }
948
+ } catch (e) {
949
+ // Dernier recours
950
+ console.error(`[Auto-updater] Erreur critique: ${message}`);
951
+ }
952
+ }
953
+
954
+ // 🔧 CLI Handler pour les commandes avec meilleure gestion d'erreurs
955
+ static async handleCLI(args = []) {
956
+ const command = args[0];
957
+
958
+ try {
959
+ // Initialiser d'abord si pas déjà fait
960
+ if (!this.config || Object.keys(this.config).length === 0) {
961
+ await this.init();
962
+ }
963
+
964
+ // Vérifier les fonctions essentielles et les créer si manquantes
965
+ if (typeof this.getCurrentVersion !== 'function') {
966
+ throw new Error("getCurrentVersion is not a function - Auto-updater corrompu");
967
+ }
968
+
969
+ if (typeof this.log !== 'function') {
970
+ // Recréer log à la volée si manquante
971
+ this.log = this.logError;
972
+ }
973
+
974
+ switch (command) {
975
+ case 'check':
976
+ return await this.checkForUpdates();
977
+
978
+ case 'update':
979
+ return await this.performUpdateCommand();
980
+
981
+ case 'config':
982
+ if (args[1] && args[2]) {
983
+ // Mise à jour d'une option spécifique
984
+ return await this.updateSetting(args[1], args[2]);
985
+ }
986
+ return await this.configureSettings();
987
+
988
+ case 'rollback':
989
+ return await this.rollback(args[1]);
990
+
991
+ case 'stats':
992
+ case 'status':
993
+ return this.displayStats();
994
+
995
+ case 'fix':
996
+ return await this.fixInstallation();
997
+
998
+ case 'help':
999
+ case '--help':
1000
+ case '-h':
1001
+ return this.showHelp();
1002
+
1003
+ case 'version':
1004
+ case '--version':
1005
+ case '-v':
1006
+ return this.showVersion();
1007
+
1008
+ case undefined:
1009
+ default:
1010
+ // Par défaut, check seulement
1011
+ return await this.checkForUpdates();
1012
+ }
1013
+ } catch (error) {
1014
+ console.error(`[Auto-updater] Erreur de commande: ${error.message}`);
1015
+ if (process.env.DEBUG) {
1016
+ console.error(error.stack);
1017
+ }
1018
+ return false;
1019
+ }
1020
+ }
1021
+
1022
+ // 🔧 Mise à jour d'un paramètre
1023
+ static async updateSetting(key, value) {
1024
+ try {
1025
+ // Convertir la valeur en fonction du type attendu
1026
+ let parsedValue = value;
1027
+ if (value === 'true') parsedValue = true;
1028
+ if (value === 'false') parsedValue = false;
1029
+ if (!isNaN(parseInt(value))) parsedValue = parseInt(value);
1030
+
1031
+ // Vérifier que la clé existe dans la configuration
1032
+ if (!(key in this.defaultConfig)) {
1033
+ console.log(this.styles.error(`❌ Paramètre inconnu: ${key}`));
1034
+ return false;
1035
+ }
1036
+
1037
+ // Mettre à jour la configuration
1038
+ this.config[key] = parsedValue;
1039
+ await this.saveConfig();
1040
+
1041
+ console.log(this.styles.success(`✅ Paramètre mis à jour: ${key} = ${parsedValue}`));
1042
+ return true;
1043
+ } catch (error) {
1044
+ console.log(this.styles.error(`❌ Erreur: ${error.message}`));
1045
+ return false;
1046
+ }
1047
+ }
1048
+
1049
+ // 🔍 Comparaison de versions améliorée avec support des pre-release
1050
+ static compareVersions(version1, version2) {
1051
+ const parseVersion = (version) => {
1052
+ const [main, prerelease] = version.split('-');
1053
+ const [major, minor, patch] = main.split('.').map(n => parseInt(n));
1054
+ return { major, minor, patch, prerelease: prerelease || null };
1055
+ };
1056
+
1057
+ const v1 = parseVersion(version1);
1058
+ const v2 = parseVersion(version2);
1059
+
1060
+ // Compare major.minor.patch
1061
+ if (v1.major !== v2.major) return v1.major - v2.major;
1062
+ if (v1.minor !== v2.minor) return v1.minor - v2.minor;
1063
+ if (v1.patch !== v2.patch) return v1.patch - v2.patch;
1064
+
1065
+ // Compare prerelease
1066
+ if (v1.prerelease && !v2.prerelease) return -1;
1067
+ if (!v1.prerelease && v2.prerelease) return 1;
1068
+ if (v1.prerelease && v2.prerelease) {
1069
+ return v1.prerelease.localeCompare(v2.prerelease);
1070
+ }
1071
+
1072
+ return 0;
1073
+ }
1074
+
1075
+ // 🔄 Rotation des logs
1076
+ static rotateLogFile() {
1077
+ try {
1078
+ if (!fs.existsSync(this.logPath)) return;
1079
+
1080
+ const stats = fs.statSync(this.logPath);
1081
+ if (stats.size > 1024 * 1024) { // 1MB
1082
+ const rotatedPath = this.logPath + '.' + Date.now();
1083
+ fs.renameSync(this.logPath, rotatedPath);
1084
+ fs.writeFileSync(this.logPath, '');
1085
+
1086
+ // Nettoyer les anciens logs
1087
+ const logDir = path.dirname(this.logPath);
1088
+ const files = fs.readdirSync(logDir)
1089
+ .filter(file => file.startsWith(path.basename(this.logPath) + '.'))
1090
+ .sort();
1091
+
1092
+ // Garder seulement les 5 derniers logs
1093
+ if (files.length > 5) {
1094
+ files.slice(0, files.length - 5).forEach(file => {
1095
+ fs.unlinkSync(path.join(logDir, file));
1096
+ });
1097
+ }
1098
+ }
1099
+ } catch (error) {
1100
+ // Ignorer les erreurs
1101
+ }
1102
+ }
1103
+
1104
+ // 💾 Sauvegarde des statistiques
1105
+ static async saveStats() {
1106
+ try {
1107
+ if (fs.existsSync(this.packageJsonPath)) {
1108
+ const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf8'));
1109
+ packageJson.vekoUpdaterStats = this.stats;
1110
+ fs.writeFileSync(this.packageJsonPath, JSON.stringify(packageJson, null, 2));
1111
+ }
1112
+ } catch (error) {
1113
+ this.logError(`Impossible de sauvegarder les statistiques: ${error.message}`);
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ module.exports = AutoUpdater;