@configjs/cli 1.1.16 → 1.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{angular-command-XN26G6L3.js → angular-command-EOREU45Q.js} +8 -8
  2. package/dist/{angular-installer-FY43HE72.js → angular-installer-TKZDPFLD.js} +9 -1
  3. package/dist/angular-setup-QDTWXOB4.js +30 -0
  4. package/dist/check-KAPRT4FM.js +168 -0
  5. package/dist/{chunk-JYWGJJ4M.js → chunk-D7IWYKUX.js} +476 -28
  6. package/dist/chunk-EDCNW4UO.js +92 -0
  7. package/dist/{chunk-TN27AX4L.js → chunk-FJLN62L4.js} +797 -18
  8. package/dist/{chunk-FIB2J36N.js → chunk-HI7RYD6W.js} +161 -36
  9. package/dist/{chunk-NYCK4R4K.js → chunk-RIJNUJDC.js} +361 -96
  10. package/dist/chunk-V2IBYLVH.js +932 -0
  11. package/dist/chunk-VN4XTFDK.js +315 -0
  12. package/dist/{chunk-UKEHW2LH.js → chunk-Y4XYC7QV.js} +17 -3
  13. package/dist/cli.js +31 -21
  14. package/dist/{installed-D6CUYQM5.js → installed-QMJZIZNC.js} +4 -4
  15. package/dist/{list-VZDUWV5O.js → list-5T6VDDAO.js} +4 -4
  16. package/dist/{nextjs-command-WKKOAY7I.js → nextjs-command-C6PM7A5C.js} +8 -9
  17. package/dist/{nextjs-installer-2ZC5IWJ6.js → nextjs-installer-OFY5BQC4.js} +9 -2
  18. package/dist/{nextjs-setup-DYOFF72S.js → nextjs-setup-JIKPIJCX.js} +21 -9
  19. package/dist/{react-command-2T6IOTHB.js → react-command-JMK6VM4Q.js} +8 -9
  20. package/dist/{remove-ZY3MLPGN.js → remove-4ZNQR6ZR.js} +4 -4
  21. package/dist/{svelte-command-B2DNNQ5Z.js → svelte-command-YUSD55NO.js} +8 -8
  22. package/dist/svelte-installer-UP3KDZSY.js +105 -0
  23. package/dist/{svelte-setup-FWXLXJAC.js → svelte-setup-33E46IBT.js} +16 -5
  24. package/dist/{vite-installer-Y6VMFHIM.js → vite-installer-EE2LE76G.js} +9 -2
  25. package/dist/{vite-setup-JRELX6K2.js → vite-setup-VO5BOI46.js} +16 -4
  26. package/dist/{vue-command-IOTC32AI.js → vue-command-3CYWLLFQ.js} +8 -9
  27. package/dist/{vue-installer-DGBBVF6F.js → vue-installer-LEGLVD77.js} +9 -2
  28. package/dist/{vue-setup-G44DLT2U.js → vue-setup-FK5QT7AY.js} +16 -4
  29. package/package.json +12 -4
  30. package/dist/angular-setup-Z6TCKNBG.js +0 -18
  31. package/dist/check-KNGZSCMM.js +0 -131
  32. package/dist/chunk-6GV4NKUX.js +0 -122
  33. package/dist/chunk-QPEUT7QG.js +0 -157
  34. package/dist/svelte-installer-EOSC3EP3.js +0 -65
@@ -1,24 +1,29 @@
1
1
  import {
2
2
  CompatibilityValidator,
3
3
  allCompatibilityRules
4
- } from "./chunk-NYCK4R4K.js";
4
+ } from "./chunk-RIJNUJDC.js";
5
5
  import {
6
6
  BackupManager,
7
7
  ConfigWriter,
8
8
  getPluginsByCategory,
9
9
  getRecommendedPlugins,
10
10
  pluginRegistry
11
- } from "./chunk-JYWGJJ4M.js";
11
+ } from "./chunk-D7IWYKUX.js";
12
12
  import {
13
13
  PluginTracker
14
- } from "./chunk-UKEHW2LH.js";
14
+ } from "./chunk-Y4XYC7QV.js";
15
15
  import {
16
16
  SpinnerManager
17
17
  } from "./chunk-KAMPBTFG.js";
18
+ import {
19
+ checkPathExists,
20
+ readFileContent,
21
+ writeFileContent
22
+ } from "./chunk-HI7RYD6W.js";
18
23
  import {
19
24
  getModuleLogger,
20
25
  logger
21
- } from "./chunk-QPEUT7QG.js";
26
+ } from "./chunk-VN4XTFDK.js";
22
27
  import {
23
28
  getTranslations
24
29
  } from "./chunk-L4GX22RG.js";
@@ -36,11 +41,11 @@ var frameworkRegistry = {
36
41
  ts: "react-ts"
37
42
  },
38
43
  getSetupPrompt: async (language) => {
39
- const { promptViteSetup } = await import("./vite-setup-JRELX6K2.js");
44
+ const { promptViteSetup } = await import("./vite-setup-VO5BOI46.js");
40
45
  return await promptViteSetup(language);
41
46
  },
42
47
  createProject: async (options, currentDir, language) => {
43
- const { createViteProject } = await import("./vite-installer-Y6VMFHIM.js");
48
+ const { createViteProject } = await import("./vite-installer-EE2LE76G.js");
44
49
  return await createViteProject(
45
50
  options,
46
51
  currentDir,
@@ -60,11 +65,11 @@ var frameworkRegistry = {
60
65
  defaultBundler: "nextjs",
61
66
  createCommand: "npm create next-app@latest",
62
67
  getSetupPrompt: async (language) => {
63
- const { promptNextjsSetup } = await import("./nextjs-setup-DYOFF72S.js");
68
+ const { promptNextjsSetup } = await import("./nextjs-setup-JIKPIJCX.js");
64
69
  return await promptNextjsSetup(language);
65
70
  },
66
71
  createProject: async (options, currentDir, language) => {
67
- const { createNextjsProject } = await import("./nextjs-installer-2ZC5IWJ6.js");
72
+ const { createNextjsProject } = await import("./nextjs-installer-OFY5BQC4.js");
68
73
  return await createNextjsProject(
69
74
  options,
70
75
  currentDir,
@@ -88,11 +93,11 @@ var frameworkRegistry = {
88
93
  ts: "vue-ts"
89
94
  },
90
95
  getSetupPrompt: async (language) => {
91
- const { promptVueSetup } = await import("./vue-setup-G44DLT2U.js");
96
+ const { promptVueSetup } = await import("./vue-setup-FK5QT7AY.js");
92
97
  return await promptVueSetup(language);
93
98
  },
94
99
  createProject: async (options, currentDir, language) => {
95
- const { createVueProject } = await import("./vue-installer-DGBBVF6F.js");
100
+ const { createVueProject } = await import("./vue-installer-LEGLVD77.js");
96
101
  return await createVueProject(
97
102
  options,
98
103
  currentDir,
@@ -112,11 +117,11 @@ var frameworkRegistry = {
112
117
  defaultBundler: "vite",
113
118
  createCommand: "npm create svelte@latest",
114
119
  getSetupPrompt: async (language) => {
115
- const { promptSvelteSetup } = await import("./svelte-setup-FWXLXJAC.js");
120
+ const { promptSvelteSetup } = await import("./svelte-setup-33E46IBT.js");
116
121
  return await promptSvelteSetup(language);
117
122
  },
118
123
  createProject: async (options, currentDir, language) => {
119
- const { createSvelteProject } = await import("./svelte-installer-EOSC3EP3.js");
124
+ const { createSvelteProject } = await import("./svelte-installer-UP3KDZSY.js");
120
125
  return await createSvelteProject(
121
126
  options,
122
127
  currentDir,
@@ -136,11 +141,11 @@ var frameworkRegistry = {
136
141
  defaultBundler: "webpack",
137
142
  createCommand: "npx @angular/cli@latest new",
138
143
  getSetupPrompt: async () => {
139
- const { promptAngularSetup } = await import("./angular-setup-Z6TCKNBG.js");
144
+ const { promptAngularSetup } = await import("./angular-setup-QDTWXOB4.js");
140
145
  return await promptAngularSetup();
141
146
  },
142
147
  createProject: async (options, currentDir, language) => {
143
- const { createAngularProject } = await import("./angular-installer-FY43HE72.js");
148
+ const { createAngularProject } = await import("./angular-installer-TKZDPFLD.js");
144
149
  return await createAngularProject(
145
150
  options,
146
151
  currentDir,
@@ -303,6 +308,669 @@ ${translations.confirmation.summary}
303
308
  return confirm;
304
309
  }
305
310
 
311
+ // src/core/snapshot-manager.ts
312
+ import { resolve } from "path";
313
+ var SnapshotManager = class {
314
+ /**
315
+ * @param projectRoot - Racine du projet
316
+ * @param fsAdapter - Adaptateur filesystem optionnel (pour tests)
317
+ */
318
+ constructor(projectRoot, fsAdapter) {
319
+ this.projectRoot = projectRoot;
320
+ this.fsAdapter = fsAdapter;
321
+ this.startCleanupInterval();
322
+ }
323
+ logger = getModuleLogger();
324
+ /**
325
+ * Map des snapshots actifs: id -> snapshot
326
+ */
327
+ snapshots = /* @__PURE__ */ new Map();
328
+ /**
329
+ * Interval ID pour le cleanup des snapshots expirés
330
+ */
331
+ cleanupInterval;
332
+ /**
333
+ * TTL des snapshots en millisecondes (24 heures par défaut)
334
+ */
335
+ snapshotTTL = 24 * 60 * 60 * 1e3;
336
+ /**
337
+ * Crée un snapshot de l'état actuel du projet
338
+ *
339
+ * Sauvegarde les fichiers critiques:
340
+ * - package.json
341
+ * - package-lock.json
342
+ * - yarn.lock / pnpm-lock.yaml
343
+ * - .npmrc / .yarnrc
344
+ *
345
+ * @param description - Description optionnelle du snapshot
346
+ * @returns ID du snapshot créé
347
+ * @throws {Error} Si le snapshot échoue
348
+ *
349
+ * @example
350
+ * ```typescript
351
+ * const snapshotId = await snapshotManager.createSnapshot('Before React installation')
352
+ * console.log('Created snapshot:', snapshotId)
353
+ * ```
354
+ */
355
+ async createSnapshot(description) {
356
+ const snapshotId = this.generateSnapshotId();
357
+ const now = Date.now();
358
+ const files = /* @__PURE__ */ new Map();
359
+ this.logger.debug(`Creating snapshot ${snapshotId}`);
360
+ const criticalFiles = [
361
+ "package.json",
362
+ "package-lock.json",
363
+ "yarn.lock",
364
+ "pnpm-lock.yaml",
365
+ ".npmrc",
366
+ ".yarnrc",
367
+ ".yarnrc.yml",
368
+ "tsconfig.json",
369
+ ".eslintrc.json",
370
+ "vitest.config.ts"
371
+ ];
372
+ for (const filename of criticalFiles) {
373
+ const filePath = resolve(this.projectRoot, filename);
374
+ try {
375
+ if (await checkPathExists(filePath, this.fsAdapter)) {
376
+ const content = await readFileContent(
377
+ filePath,
378
+ "utf-8",
379
+ this.fsAdapter
380
+ );
381
+ files.set(filePath, content);
382
+ this.logger.debug(`Snapshotted ${filename}`);
383
+ }
384
+ } catch (error) {
385
+ const errorMessage = error instanceof Error ? error.message : String(error);
386
+ this.logger.debug(`Could not snapshot ${filename}: ${errorMessage}`);
387
+ }
388
+ }
389
+ const snapshot = {
390
+ id: snapshotId,
391
+ timestamp: now,
392
+ projectRoot: this.projectRoot,
393
+ files,
394
+ metadata: {
395
+ description,
396
+ createdAt: new Date(now).toISOString(),
397
+ expiresAt: new Date(now + this.snapshotTTL).toISOString()
398
+ }
399
+ };
400
+ this.snapshots.set(snapshotId, snapshot);
401
+ this.logger.info(
402
+ `Created snapshot ${snapshotId} with ${files.size} file(s)`
403
+ );
404
+ return snapshotId;
405
+ }
406
+ /**
407
+ * Restaure un projet depuis un snapshot
408
+ *
409
+ * Restore tous les fichiers sauvegardés dans le snapshot.
410
+ * Effectue une restauration atomique ou lance une erreur.
411
+ *
412
+ * @param snapshotId - ID du snapshot à restaurer
413
+ * @throws {Error} Si le snapshot n'existe pas ou si la restauration échoue
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * try {
418
+ * await snapshotManager.restoreSnapshot(snapshotId)
419
+ * console.log('Project restored successfully')
420
+ * } catch (error) {
421
+ * console.error('Restore failed:', error)
422
+ * }
423
+ * ```
424
+ */
425
+ async restoreSnapshot(snapshotId) {
426
+ const snapshot = this.snapshots.get(snapshotId);
427
+ if (!snapshot) {
428
+ const availableIds = Array.from(this.snapshots.keys()).join(", ");
429
+ throw new Error(
430
+ `Snapshot not found: ${snapshotId}. Available: ${availableIds}`
431
+ );
432
+ }
433
+ this.logger.info(`Restoring snapshot ${snapshotId}`);
434
+ const errors = [];
435
+ for (const [filePath, content] of snapshot.files) {
436
+ try {
437
+ await writeFileContent(filePath, content, "utf-8", this.fsAdapter);
438
+ this.logger.debug(`Restored ${filePath}`);
439
+ } catch (error) {
440
+ const errorMessage = error instanceof Error ? error.message : String(error);
441
+ const restoreError = new Error(
442
+ `Failed to restore ${filePath}: ${errorMessage}`
443
+ );
444
+ errors.push({ file: filePath, error: restoreError });
445
+ this.logger.error(errorMessage);
446
+ }
447
+ }
448
+ if (errors.length > 0) {
449
+ const errorMessages = errors.map((e) => `${e.file}: ${e.error.message}`).join("; ");
450
+ throw new Error(
451
+ `Restore failed for ${errors.length}/${snapshot.files.size} file(s): ${errorMessages}`
452
+ );
453
+ }
454
+ this.logger.info(
455
+ `Successfully restored snapshot ${snapshotId} (${snapshot.files.size} file(s))`
456
+ );
457
+ }
458
+ /**
459
+ * Libère (supprime) un snapshot
460
+ *
461
+ * À appeler après une installation réussie pour nettoyer les snapshots.
462
+ * Les snapshots sont aussi nettoyés automatiquement après 24h.
463
+ *
464
+ * @param snapshotId - ID du snapshot à libérer
465
+ * @returns true si le snapshot a été libéré, false s'il n'existait pas
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * await snapshotManager.releaseSnapshot(snapshotId)
470
+ * console.log('Snapshot cleaned up')
471
+ * ```
472
+ */
473
+ releaseSnapshot(snapshotId) {
474
+ const removed = this.snapshots.delete(snapshotId);
475
+ if (removed) {
476
+ this.logger.info(`Released snapshot ${snapshotId}`);
477
+ } else {
478
+ this.logger.warn(`Snapshot ${snapshotId} not found for release`);
479
+ }
480
+ return removed;
481
+ }
482
+ /**
483
+ * Récupère les détails d'un snapshot
484
+ *
485
+ * @param snapshotId - ID du snapshot
486
+ * @returns Métadonnées du snapshot ou undefined
487
+ *
488
+ * @example
489
+ * ```typescript
490
+ * const info = snapshotManager.getSnapshotInfo(snapshotId)
491
+ * console.log('Created at:', info?.metadata.createdAt)
492
+ * ```
493
+ */
494
+ getSnapshotInfo(snapshotId) {
495
+ return this.snapshots.get(snapshotId);
496
+ }
497
+ /**
498
+ * Liste tous les snapshots actifs
499
+ *
500
+ * @returns Array de métadonnées des snapshots
501
+ *
502
+ * @example
503
+ * ```typescript
504
+ * const snapshots = snapshotManager.listSnapshots()
505
+ * snapshots.forEach(s => {
506
+ * console.log(`${s.id}: ${s.metadata.description}`)
507
+ * })
508
+ * ```
509
+ */
510
+ listSnapshots() {
511
+ return Array.from(this.snapshots.values());
512
+ }
513
+ /**
514
+ * Vérifie si un snapshot existe
515
+ *
516
+ * @param snapshotId - ID du snapshot
517
+ * @returns true si le snapshot existe, false sinon
518
+ */
519
+ hasSnapshot(snapshotId) {
520
+ return this.snapshots.has(snapshotId);
521
+ }
522
+ /**
523
+ * Nettoie tous les snapshots expirés
524
+ *
525
+ * Appelé automatiquement toutes les heures.
526
+ * Peut aussi être appelé manuellement si nécessaire.
527
+ *
528
+ * @returns Nombre de snapshots nettoyés
529
+ *
530
+ * @example
531
+ * ```typescript
532
+ * const cleaned = snapshotManager.cleanupExpired()
533
+ * console.log(`Cleaned up ${cleaned} expired snapshot(s)`)
534
+ * ```
535
+ */
536
+ cleanupExpired() {
537
+ const now = Date.now();
538
+ const toDelete = [];
539
+ for (const [id, snapshot] of this.snapshots) {
540
+ if (now > snapshot.timestamp + this.snapshotTTL) {
541
+ toDelete.push(id);
542
+ }
543
+ }
544
+ for (const id of toDelete) {
545
+ this.releaseSnapshot(id);
546
+ }
547
+ if (toDelete.length > 0) {
548
+ this.logger.info(`Cleaned up ${toDelete.length} expired snapshot(s)`);
549
+ }
550
+ return toDelete.length;
551
+ }
552
+ /**
553
+ * Nettoie TOUS les snapshots (utile pour reset complet)
554
+ *
555
+ * @returns Nombre de snapshots nettoyés
556
+ *
557
+ * @example
558
+ * ```typescript
559
+ * const count = snapshotManager.clearAll()
560
+ * console.log(`Cleared ${count} snapshot(s)`)
561
+ * ```
562
+ */
563
+ clearAll() {
564
+ const count = this.snapshots.size;
565
+ this.snapshots.clear();
566
+ if (count > 0) {
567
+ this.logger.info(`Cleared all ${count} snapshot(s)`);
568
+ }
569
+ return count;
570
+ }
571
+ /**
572
+ * Arrête le cleanup interval (à appeler avant destruction)
573
+ *
574
+ * @example
575
+ * ```typescript
576
+ * snapshotManager.destroy()
577
+ * ```
578
+ */
579
+ destroy() {
580
+ if (this.cleanupInterval) {
581
+ clearInterval(this.cleanupInterval);
582
+ this.cleanupInterval = void 0;
583
+ }
584
+ this.logger.debug("SnapshotManager destroyed");
585
+ }
586
+ /**
587
+ * Génère un ID unique pour snapshot
588
+ *
589
+ * @internal
590
+ */
591
+ generateSnapshotId() {
592
+ const timestamp = Date.now();
593
+ const random = Math.random().toString(36).substring(2, 11);
594
+ return `snapshot-${timestamp}-${random}`;
595
+ }
596
+ /**
597
+ * Démarre le nettoyage automatique des snapshots expirés
598
+ *
599
+ * @internal
600
+ */
601
+ startCleanupInterval() {
602
+ const interval = 60 * 60 * 1e3;
603
+ this.cleanupInterval = setInterval(() => {
604
+ this.cleanupExpired();
605
+ }, interval);
606
+ if (this.cleanupInterval.unref) {
607
+ this.cleanupInterval.unref();
608
+ }
609
+ }
610
+ };
611
+
612
+ // src/core/transaction-log.ts
613
+ var TransactionLog = class {
614
+ /**
615
+ * @param projectRoot - Racine du projet
616
+ */
617
+ constructor(projectRoot) {
618
+ this.projectRoot = projectRoot;
619
+ }
620
+ logger = getModuleLogger();
621
+ /**
622
+ * Map des transactions actives: txId -> metadata + entries
623
+ */
624
+ transactions = /* @__PURE__ */ new Map();
625
+ /**
626
+ * Transaction courante (pour logging convenience)
627
+ */
628
+ currentTxId;
629
+ /**
630
+ * Counter pour IDs d'entries
631
+ */
632
+ entryIdCounter = 0;
633
+ /**
634
+ * Démarre une nouvelle transaction
635
+ *
636
+ * @param plugins - Noms des plugins à installer
637
+ * @returns ID de la transaction
638
+ *
639
+ * @example
640
+ * ```typescript
641
+ * const txId = txLog.startTransaction(['react', 'redux'])
642
+ * ```
643
+ */
644
+ startTransaction(plugins) {
645
+ const txId = this.generateTransactionId();
646
+ const now = Date.now();
647
+ const metadata = {
648
+ id: txId,
649
+ startTime: now,
650
+ projectRoot: this.projectRoot,
651
+ plugins
652
+ };
653
+ this.transactions.set(txId, {
654
+ metadata,
655
+ entries: []
656
+ });
657
+ this.currentTxId = txId;
658
+ this.logger.debug(`Started transaction ${txId}`);
659
+ return txId;
660
+ }
661
+ /**
662
+ * Enregistre une action dans la transaction courante
663
+ *
664
+ * @param action - Type d'action
665
+ * @param message - Message descriptif
666
+ * @param data - Données optionnelles
667
+ * @throws {Error} Si aucune transaction active
668
+ *
669
+ * @example
670
+ * ```typescript
671
+ * txLog.log(
672
+ * TransactionActionType.PACKAGE_INSTALL_START,
673
+ * 'Installing react',
674
+ * { package: 'react', version: '18.0.0' }
675
+ * )
676
+ * ```
677
+ */
678
+ log(action, message, data) {
679
+ if (!this.currentTxId) {
680
+ throw new Error("No active transaction");
681
+ }
682
+ const tx = this.transactions.get(this.currentTxId);
683
+ if (!tx) {
684
+ throw new Error(`Transaction ${this.currentTxId} not found`);
685
+ }
686
+ const entry = {
687
+ id: this.generateEntryId(),
688
+ timestamp: Date.now(),
689
+ action,
690
+ message,
691
+ data
692
+ };
693
+ tx.entries.push(entry);
694
+ }
695
+ /**
696
+ * Enregistre une erreur dans la transaction courante
697
+ *
698
+ * @param message - Message d'erreur
699
+ * @param error - L'erreur originale
700
+ * @param data - Données optionnelles
701
+ * @throws {Error} Si aucune transaction active
702
+ *
703
+ * @example
704
+ * ```typescript
705
+ * try {
706
+ * // ... perform action ...
707
+ * } catch (error) {
708
+ * txLog.logError('Package install failed', error)
709
+ * }
710
+ * ```
711
+ */
712
+ logError(message, error, data) {
713
+ if (!this.currentTxId) {
714
+ throw new Error("No active transaction");
715
+ }
716
+ const tx = this.transactions.get(this.currentTxId);
717
+ if (!tx) {
718
+ throw new Error(`Transaction ${this.currentTxId} not found`);
719
+ }
720
+ const entry = {
721
+ id: this.generateEntryId(),
722
+ timestamp: Date.now(),
723
+ action: "ERROR" /* ERROR */,
724
+ message,
725
+ data,
726
+ error: {
727
+ message: error.message,
728
+ stack: error.stack,
729
+ code: error.code
730
+ }
731
+ };
732
+ tx.entries.push(entry);
733
+ }
734
+ /**
735
+ * Enregistre un warning dans la transaction courante
736
+ *
737
+ * @param message - Message de warning
738
+ * @param data - Données optionnelles
739
+ * @throws {Error} Si aucune transaction active
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * txLog.logWarning('Plugin deprecation notice', {
744
+ * plugin: 'old-plugin',
745
+ * replacement: 'new-plugin'
746
+ * })
747
+ * ```
748
+ */
749
+ logWarning(message, data) {
750
+ if (!this.currentTxId) {
751
+ throw new Error("No active transaction");
752
+ }
753
+ const tx = this.transactions.get(this.currentTxId);
754
+ if (!tx) {
755
+ throw new Error(`Transaction ${this.currentTxId} not found`);
756
+ }
757
+ const entry = {
758
+ id: this.generateEntryId(),
759
+ timestamp: Date.now(),
760
+ action: "WARNING" /* WARNING */,
761
+ message,
762
+ data
763
+ };
764
+ tx.entries.push(entry);
765
+ }
766
+ /**
767
+ * Enregistre une action avec timing (pour debugging performance)
768
+ *
769
+ * Utile pour mesurer la durée d'opérations comme install, validate, config.
770
+ *
771
+ * @param action - Type d'action
772
+ * @param message - Message descriptif
773
+ * @param durationMs - Durée en millisecondes
774
+ * @param data - Données optionnelles
775
+ * @throws {Error} Si aucune transaction active
776
+ *
777
+ * @example
778
+ * ```typescript
779
+ * const start = Date.now()
780
+ * // ... perform action ...
781
+ * txLog.logTimed(
782
+ * TransactionActionType.PACKAGE_INSTALL_COMPLETE,
783
+ * 'React installed',
784
+ * Date.now() - start,
785
+ * { packages: 1 }
786
+ * )
787
+ * ```
788
+ */
789
+ logTimed(action, message, durationMs, data) {
790
+ if (!this.currentTxId) {
791
+ throw new Error("No active transaction");
792
+ }
793
+ const tx = this.transactions.get(this.currentTxId);
794
+ if (!tx) {
795
+ throw new Error(`Transaction ${this.currentTxId} not found`);
796
+ }
797
+ const entry = {
798
+ id: this.generateEntryId(),
799
+ timestamp: Date.now(),
800
+ action,
801
+ message,
802
+ data,
803
+ duration: durationMs
804
+ };
805
+ tx.entries.push(entry);
806
+ }
807
+ /**
808
+ * Termine la transaction courante
809
+ *
810
+ * @param success - Succès de la transaction
811
+ * @param snapshotId - ID du snapshot associé (optionnel)
812
+ * @throws {Error} Si aucune transaction active
813
+ *
814
+ * @example
815
+ * ```typescript
816
+ * txLog.endTransaction(true, snapshotId)
817
+ * ```
818
+ */
819
+ endTransaction(success, snapshotId) {
820
+ if (!this.currentTxId) {
821
+ throw new Error("No active transaction");
822
+ }
823
+ const tx = this.transactions.get(this.currentTxId);
824
+ if (!tx) {
825
+ throw new Error(`Transaction ${this.currentTxId} not found`);
826
+ }
827
+ tx.metadata.endTime = Date.now();
828
+ tx.metadata.success = success;
829
+ tx.metadata.snapshotId = snapshotId;
830
+ const duration = (tx.metadata.endTime - tx.metadata.startTime) / 1e3;
831
+ this.logger.info(
832
+ `Transaction ${this.currentTxId} ${success ? "succeeded" : "failed"} in ${duration.toFixed(2)}s`
833
+ );
834
+ this.currentTxId = void 0;
835
+ }
836
+ /**
837
+ * Récupère le rapport complet d'une transaction
838
+ *
839
+ * @param txId - ID de la transaction
840
+ * @returns Rapport avec métadonnées et entries, ou undefined
841
+ *
842
+ * @example
843
+ * ```typescript
844
+ * const report = txLog.getReport(txId)
845
+ * console.log(`Transaction took ${report?.duration}ms`)
846
+ * console.log(`Total errors: ${report?.errorCount}`)
847
+ * ```
848
+ */
849
+ getReport(txId) {
850
+ const tx = this.transactions.get(txId);
851
+ if (!tx) {
852
+ return void 0;
853
+ }
854
+ const duration = (tx.metadata.endTime ?? Date.now()) - tx.metadata.startTime;
855
+ const errorCount = tx.entries.filter(
856
+ (e) => e.action === "ERROR" /* ERROR */
857
+ ).length;
858
+ const warningCount = tx.entries.filter(
859
+ (e) => e.action === "WARNING" /* WARNING */
860
+ ).length;
861
+ return {
862
+ metadata: tx.metadata,
863
+ entries: tx.entries,
864
+ duration,
865
+ errorCount,
866
+ warningCount
867
+ };
868
+ }
869
+ /**
870
+ * Récupère les entries d'une transaction
871
+ *
872
+ * @param txId - ID de la transaction
873
+ * @returns Array d'entries, ou undefined si transaction pas trouvée
874
+ *
875
+ * @example
876
+ * ```typescript
877
+ * const entries = txLog.getEntries(txId)
878
+ * entries?.forEach(e => console.log(`[${e.action}] ${e.message}`))
879
+ * ```
880
+ */
881
+ getEntries(txId) {
882
+ const tx = this.transactions.get(txId);
883
+ return tx?.entries;
884
+ }
885
+ /**
886
+ * Formate le rapport pour affichage/debugging
887
+ *
888
+ * @param txId - ID de la transaction
889
+ * @returns String formaté du rapport
890
+ *
891
+ * @example
892
+ * ```typescript
893
+ * console.log(txLog.formatReport(txId))
894
+ * ```
895
+ */
896
+ formatReport(txId) {
897
+ const report = this.getReport(txId);
898
+ if (!report) {
899
+ return `Transaction ${txId} not found`;
900
+ }
901
+ const lines = [];
902
+ lines.push(`=== Transaction Report: ${txId} ===`);
903
+ lines.push(`Status: ${report.metadata.success ? "SUCCESS" : "FAILED"}`);
904
+ lines.push(`Duration: ${(report.duration / 1e3).toFixed(2)}s`);
905
+ lines.push(`Plugins: ${report.metadata.plugins.join(", ")}`);
906
+ lines.push(`Errors: ${report.errorCount}, Warnings: ${report.warningCount}`);
907
+ lines.push("");
908
+ lines.push("Timeline:");
909
+ for (const entry of report.entries) {
910
+ const time = new Date(entry.timestamp).toLocaleTimeString();
911
+ const duration = entry.duration ? ` (${entry.duration}ms)` : "";
912
+ lines.push(` [${time}] ${entry.action}${duration}: ${entry.message}`);
913
+ if (entry.error) {
914
+ lines.push(` Error: ${entry.error.message}`);
915
+ if (entry.error.code) {
916
+ lines.push(` Code: ${entry.error.code}`);
917
+ }
918
+ }
919
+ if (entry.data) {
920
+ lines.push(` Data: ${JSON.stringify(entry.data)}`);
921
+ }
922
+ }
923
+ return lines.join("\n");
924
+ }
925
+ /**
926
+ * Supprime une transaction du log
927
+ *
928
+ * @param txId - ID de la transaction
929
+ * @returns true si la transaction a été supprimée, false sinon
930
+ *
931
+ * @example
932
+ * ```typescript
933
+ * txLog.removeTransaction(txId)
934
+ * ```
935
+ */
936
+ removeTransaction(txId) {
937
+ if (this.currentTxId === txId) {
938
+ this.currentTxId = void 0;
939
+ }
940
+ return this.transactions.delete(txId);
941
+ }
942
+ /**
943
+ * Nettoie tous les logs des transactions
944
+ *
945
+ * @example
946
+ * ```typescript
947
+ * txLog.clear()
948
+ * ```
949
+ */
950
+ clear() {
951
+ this.transactions.clear();
952
+ this.currentTxId = void 0;
953
+ }
954
+ /**
955
+ * Génère un ID unique pour transaction
956
+ *
957
+ * @internal
958
+ */
959
+ generateTransactionId() {
960
+ const timestamp = Date.now();
961
+ const random = Math.random().toString(36).substring(2, 11);
962
+ return `tx-${timestamp}-${random}`;
963
+ }
964
+ /**
965
+ * Génère un ID unique pour entry
966
+ *
967
+ * @internal
968
+ */
969
+ generateEntryId() {
970
+ return `entry-${++this.entryIdCounter}`;
971
+ }
972
+ };
973
+
306
974
  // src/core/installer.ts
307
975
  var Installer = class {
308
976
  /**
@@ -316,8 +984,12 @@ var Installer = class {
316
984
  this.validator = validator;
317
985
  this.backupManager = backupManager;
318
986
  this.tracker = new PluginTracker(ctx.projectRoot, ctx.fsAdapter);
987
+ this.snapshotManager = new SnapshotManager(ctx.projectRoot, ctx.fsAdapter);
988
+ this.transactionLog = new TransactionLog(ctx.projectRoot);
319
989
  }
320
990
  tracker;
991
+ snapshotManager;
992
+ transactionLog;
321
993
  logger = getModuleLogger();
322
994
  /**
323
995
  * Installe un ensemble de plugins
@@ -336,7 +1008,16 @@ var Installer = class {
336
1008
  async install(plugins, options) {
337
1009
  const startTime = Date.now();
338
1010
  this.logger.info(`Starting installation of ${plugins.length} plugin(s)`);
1011
+ const txId = this.transactionLog.startTransaction(
1012
+ plugins.map((p) => p.name)
1013
+ );
1014
+ let snapshotId;
339
1015
  try {
1016
+ this.logger.debug("PHASE 1: Validation (no modifications)");
1017
+ this.transactionLog.log(
1018
+ "VALIDATION_START" /* VALIDATION_START */,
1019
+ "Starting validation phase"
1020
+ );
340
1021
  await this.tracker.load();
341
1022
  const notInstalledPromises = plugins.map(async (p) => {
342
1023
  const isTracked = this.tracker.isInstalled(p.name);
@@ -375,6 +1056,7 @@ var Installer = class {
375
1056
  );
376
1057
  if (notInstalled.length === 0) {
377
1058
  this.logger.info("All plugins are already installed");
1059
+ this.transactionLog.endTransaction(true);
378
1060
  return {
379
1061
  success: true,
380
1062
  duration: Date.now() - startTime,
@@ -405,8 +1087,19 @@ var Installer = class {
405
1087
  `Found ${validationResult.warnings.length} warning(s):`,
406
1088
  validationResult.warnings.map((w) => w.message)
407
1089
  );
1090
+ for (const warning of validationResult.warnings) {
1091
+ this.transactionLog.logWarning(warning.message);
1092
+ }
408
1093
  }
1094
+ this.transactionLog.log(
1095
+ "VALIDATION_COMPLETE" /* VALIDATION_COMPLETE */,
1096
+ `Validation passed for ${notInstalled.length} plugin(s)`
1097
+ );
409
1098
  this.logger.debug("Resolving dependencies...");
1099
+ this.transactionLog.log(
1100
+ "DEPENDENCY_RESOLUTION" /* DEPENDENCY_RESOLUTION */,
1101
+ "Resolving plugin dependencies"
1102
+ );
410
1103
  const resolved = this.resolveDependencies(notInstalled);
411
1104
  const allPlugins = resolved.plugins;
412
1105
  if (resolved.autoInstalled.length > 0) {
@@ -414,13 +1107,31 @@ var Installer = class {
414
1107
  `Auto-installing ${resolved.autoInstalled.length} required dependency(ies): ${resolved.autoInstalled.join(", ")}`
415
1108
  );
416
1109
  }
1110
+ this.logger.debug("PHASE 2: Creating backup and snapshot");
1111
+ snapshotId = await this.snapshotManager.createSnapshot(
1112
+ `Before installing ${allPlugins.map((p) => p.displayName).join(", ")}`
1113
+ );
1114
+ this.transactionLog.log(
1115
+ "SNAPSHOT_CREATED" /* SNAPSHOT_CREATED */,
1116
+ `Snapshot created for rollback protection`,
1117
+ { snapshotId }
1118
+ );
417
1119
  this.logger.debug("Running pre-install hooks...");
1120
+ this.transactionLog.log(
1121
+ "PRE_INSTALL_HOOK" /* PRE_INSTALL_HOOK */,
1122
+ "Running pre-install hooks"
1123
+ );
418
1124
  await this.runPreInstallHooks(allPlugins);
1125
+ this.logger.debug("PHASE 3: Installation");
419
1126
  let installResults = [];
420
1127
  if (options?.skipPackageInstall) {
421
1128
  this.logger.info("Skipping package installation (--no-install mode)");
422
1129
  } else {
423
1130
  this.logger.debug("Installing packages...");
1131
+ this.transactionLog.log(
1132
+ "PACKAGE_INSTALL_START" /* PACKAGE_INSTALL_START */,
1133
+ "Starting package installation"
1134
+ );
424
1135
  installResults = await this.installPackages(allPlugins);
425
1136
  const failedInstalls = installResults.filter((r) => !r.success);
426
1137
  if (failedInstalls.length > 0) {
@@ -428,6 +1139,10 @@ var Installer = class {
428
1139
  `Failed to install packages for ${failedInstalls.length} plugin(s)`
429
1140
  );
430
1141
  }
1142
+ this.transactionLog.log(
1143
+ "PACKAGE_INSTALL_COMPLETE" /* PACKAGE_INSTALL_COMPLETE */,
1144
+ `Successfully installed packages for ${allPlugins.length} plugin(s)`
1145
+ );
431
1146
  }
432
1147
  this.logger.debug("Configuring plugins...");
433
1148
  const configResults = [];
@@ -435,6 +1150,11 @@ var Installer = class {
435
1150
  for (const plugin of allPlugins) {
436
1151
  try {
437
1152
  this.logger.debug(`Configuring ${plugin.displayName}...`);
1153
+ this.transactionLog.log(
1154
+ "PLUGIN_CONFIGURE_START" /* PLUGIN_CONFIGURE_START */,
1155
+ `Starting configuration for ${plugin.displayName}`,
1156
+ { plugin: plugin.name }
1157
+ );
438
1158
  const configResult = await plugin.configure(this.ctx);
439
1159
  configResults.push(configResult);
440
1160
  filesCreated.push(...configResult.files || []);
@@ -443,16 +1163,38 @@ var Installer = class {
443
1163
  `Configuration failed for ${plugin.displayName}: ${configResult.message || "Unknown error"}`
444
1164
  );
445
1165
  }
1166
+ this.transactionLog.log(
1167
+ "PLUGIN_CONFIGURE_COMPLETE" /* PLUGIN_CONFIGURE_COMPLETE */,
1168
+ `Configuration complete for ${plugin.displayName}`,
1169
+ {
1170
+ plugin: plugin.name,
1171
+ filesCreated: configResult.files?.length ?? 0
1172
+ }
1173
+ );
446
1174
  } catch (error) {
447
1175
  const errorMessage = error instanceof Error ? error.message : String(error);
448
1176
  this.logger.error(
449
1177
  `Configuration failed for ${plugin.displayName}: ${errorMessage}`
450
1178
  );
1179
+ this.transactionLog.logError(
1180
+ `Configuration failed for ${plugin.displayName}`,
1181
+ error instanceof Error ? error : new Error(errorMessage),
1182
+ { plugin: plugin.name }
1183
+ );
451
1184
  throw error;
452
1185
  }
453
1186
  }
454
1187
  this.logger.debug("Running post-install hooks...");
1188
+ this.transactionLog.log(
1189
+ "POST_INSTALL_HOOK" /* POST_INSTALL_HOOK */,
1190
+ "Running post-install hooks"
1191
+ );
455
1192
  await this.runPostInstallHooks(allPlugins);
1193
+ this.logger.debug("PHASE 4: Cleanup on success");
1194
+ this.transactionLog.log(
1195
+ "CLEANUP_START" /* CLEANUP_START */,
1196
+ "Starting cleanup"
1197
+ );
456
1198
  this.logger.debug("Tracking installed plugins...");
457
1199
  const installResultsByName = /* @__PURE__ */ new Map();
458
1200
  for (let i = 0; i < allPlugins.length; i++) {
@@ -475,11 +1217,19 @@ var Installer = class {
475
1217
  }
476
1218
  });
477
1219
  }
1220
+ if (snapshotId) {
1221
+ this.snapshotManager.releaseSnapshot(snapshotId);
1222
+ }
1223
+ this.transactionLog.log(
1224
+ "CLEANUP_COMPLETE" /* CLEANUP_COMPLETE */,
1225
+ "Cleanup complete"
1226
+ );
478
1227
  const duration = Date.now() - startTime;
479
1228
  const installed = allPlugins.map((p) => p.name);
480
1229
  this.logger.info(
481
1230
  `Successfully installed ${installed.length} plugin(s) in ${duration}ms`
482
1231
  );
1232
+ this.transactionLog.endTransaction(true, snapshotId);
483
1233
  return {
484
1234
  success: true,
485
1235
  duration,
@@ -490,14 +1240,41 @@ var Installer = class {
490
1240
  } catch (error) {
491
1241
  const errorMessage = error instanceof Error ? error.message : String(error);
492
1242
  this.logger.error(`Installation failed: ${errorMessage}`);
493
- this.logger.debug("Rolling back changes...");
1243
+ this.transactionLog.logError(
1244
+ "Installation failed",
1245
+ error instanceof Error ? error : new Error(errorMessage)
1246
+ );
1247
+ this.logger.debug("ROLLBACK: Starting rollback procedure");
1248
+ this.transactionLog.log(
1249
+ "ROLLBACK_START" /* ROLLBACK_START */,
1250
+ "Starting rollback"
1251
+ );
494
1252
  try {
1253
+ if (snapshotId && this.snapshotManager.hasSnapshot(snapshotId)) {
1254
+ this.logger.debug(
1255
+ `Restoring snapshot ${snapshotId} to previous state`
1256
+ );
1257
+ await this.snapshotManager.restoreSnapshot(snapshotId);
1258
+ this.logger.info("Snapshot restored successfully");
1259
+ }
495
1260
  await this.rollback(plugins);
1261
+ this.transactionLog.log(
1262
+ "ROLLBACK_COMPLETE" /* ROLLBACK_COMPLETE */,
1263
+ "Rollback complete - snapshot retained for debugging"
1264
+ );
496
1265
  } catch (rollbackError) {
497
1266
  const rollbackMessage = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
498
1267
  this.logger.error(`Rollback failed: ${rollbackMessage}`);
1268
+ this.transactionLog.logError(
1269
+ "Rollback procedure failed",
1270
+ rollbackError instanceof Error ? rollbackError : new Error(rollbackMessage)
1271
+ );
499
1272
  }
500
1273
  const duration = Date.now() - startTime;
1274
+ this.transactionLog.endTransaction(false, snapshotId);
1275
+ const txReport = this.transactionLog.formatReport(txId);
1276
+ this.logger.debug(`Transaction report:
1277
+ ${txReport}`);
501
1278
  return {
502
1279
  success: false,
503
1280
  duration,
@@ -505,6 +1282,8 @@ var Installer = class {
505
1282
  warnings: [],
506
1283
  filesCreated: []
507
1284
  };
1285
+ } finally {
1286
+ this.snapshotManager.destroy();
508
1287
  }
509
1288
  }
510
1289
  /**
@@ -654,7 +1433,7 @@ var Installer = class {
654
1433
  };
655
1434
 
656
1435
  // src/cli/ui/report.ts
657
- import pc from "picocolors";
1436
+ import pc from "chalk";
658
1437
  function displayInstallationReport(report, plugins, lang) {
659
1438
  const t = getTranslations(lang);
660
1439
  console.log();
@@ -719,7 +1498,7 @@ function displayNextSteps(lang) {
719
1498
  }
720
1499
 
721
1500
  // src/cli/ui/logo.ts
722
- import pc2 from "picocolors";
1501
+ import pc2 from "chalk";
723
1502
  function LOGO() {
724
1503
  return [
725
1504
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
@@ -735,7 +1514,7 @@ function displayLogo() {
735
1514
  }
736
1515
 
737
1516
  // src/cli/commands/base-framework-command.ts
738
- import pc3 from "picocolors";
1517
+ import pc3 from "chalk";
739
1518
  var BaseFrameworkCommand = class {
740
1519
  /**
741
1520
  * Affiche les informations spécifiques du contexte détecté