@configjs/cli 1.0.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,1127 @@
1
+ import {
2
+ CompatibilityValidator,
3
+ compatibilityRules
4
+ } from "./chunk-PQLKGF6I.js";
5
+ import {
6
+ BackupManager,
7
+ ConfigWriter,
8
+ checkPathExists,
9
+ detectPackageManager,
10
+ getPluginsByCategory,
11
+ installPackages,
12
+ logger,
13
+ pluginRegistry,
14
+ readPackageJson,
15
+ readTsConfig
16
+ } from "./chunk-5T664O5A.js";
17
+
18
+ // src/core/detector.ts
19
+ import { resolve, join } from "path";
20
+ import { platform, version } from "process";
21
+ var detectionCache = /* @__PURE__ */ new Map();
22
+ var DetectionError = class extends Error {
23
+ constructor(message, context) {
24
+ super(message);
25
+ this.context = context;
26
+ this.name = "DetectionError";
27
+ }
28
+ };
29
+ function detectFramework(pkg) {
30
+ const deps = {
31
+ ...pkg["dependencies"] || {},
32
+ ...pkg["devDependencies"] || {}
33
+ };
34
+ if (deps["react"]) {
35
+ return {
36
+ framework: "react",
37
+ version: deps["react"].replace(/[\^~]/, "")
38
+ };
39
+ }
40
+ if (deps["vue"]) {
41
+ return {
42
+ framework: "vue",
43
+ version: deps["vue"].replace(/[\^~]/, "")
44
+ };
45
+ }
46
+ if (deps["svelte"]) {
47
+ return {
48
+ framework: "svelte",
49
+ version: deps["svelte"].replace(/[\^~]/, "")
50
+ };
51
+ }
52
+ throw new DetectionError(
53
+ "No supported framework detected. Supported frameworks: React, Vue, Svelte",
54
+ { dependencies: Object.keys(deps) }
55
+ );
56
+ }
57
+ async function detectBundler(projectRoot, pkg) {
58
+ const deps = {
59
+ ...pkg["dependencies"] || {},
60
+ ...pkg["devDependencies"] || {}
61
+ };
62
+ if (deps["vite"]) {
63
+ const viteConfigExists = await checkPathExists(join(projectRoot, "vite.config.js")) || await checkPathExists(join(projectRoot, "vite.config.ts")) || await checkPathExists(join(projectRoot, "vite.config.mjs")) || await checkPathExists(join(projectRoot, "vite.config.cjs"));
64
+ if (viteConfigExists) {
65
+ return {
66
+ bundler: "vite",
67
+ version: deps["vite"].replace(/[\^~]/, "")
68
+ };
69
+ }
70
+ }
71
+ if (deps["react-scripts"]) {
72
+ return {
73
+ bundler: "cra",
74
+ version: deps["react-scripts"].replace(/[\^~]/, "")
75
+ };
76
+ }
77
+ if (deps["webpack"]) {
78
+ const webpackConfigExists = await checkPathExists(join(projectRoot, "webpack.config.js")) || await checkPathExists(join(projectRoot, "webpack.config.ts"));
79
+ if (webpackConfigExists) {
80
+ return {
81
+ bundler: "webpack",
82
+ version: deps["webpack"].replace(/[\^~]/, "")
83
+ };
84
+ }
85
+ }
86
+ if (deps["@rspack/core"]) {
87
+ return {
88
+ bundler: "rspack",
89
+ version: deps["@rspack/core"].replace(/[\^~]/, "")
90
+ };
91
+ }
92
+ return {
93
+ bundler: null,
94
+ version: null
95
+ };
96
+ }
97
+ async function detectTypeScript(projectRoot) {
98
+ const tsConfig = await readTsConfig(projectRoot);
99
+ if (tsConfig) {
100
+ const possiblePaths = [
101
+ join(projectRoot, "tsconfig.json"),
102
+ join(projectRoot, "tsconfig.app.json"),
103
+ join(projectRoot, "tsconfig.node.json")
104
+ ];
105
+ for (const path of possiblePaths) {
106
+ if (await checkPathExists(path)) {
107
+ return {
108
+ typescript: true,
109
+ tsconfigPath: path
110
+ };
111
+ }
112
+ }
113
+ return {
114
+ typescript: true,
115
+ tsconfigPath: join(projectRoot, "tsconfig.json")
116
+ };
117
+ }
118
+ return {
119
+ typescript: false
120
+ };
121
+ }
122
+ async function detectSrcDir(projectRoot) {
123
+ const possibleDirs = ["src", "app", "source", "lib"];
124
+ for (const dir of possibleDirs) {
125
+ const dirPath = join(projectRoot, dir);
126
+ if (await checkPathExists(dirPath)) {
127
+ return dir;
128
+ }
129
+ }
130
+ return "src";
131
+ }
132
+ async function detectPublicDir(projectRoot) {
133
+ const possibleDirs = ["public", "static", "assets"];
134
+ for (const dir of possibleDirs) {
135
+ const dirPath = join(projectRoot, dir);
136
+ if (await checkPathExists(dirPath)) {
137
+ return dir;
138
+ }
139
+ }
140
+ return "public";
141
+ }
142
+ async function detectGit(projectRoot) {
143
+ const gitDir = join(projectRoot, ".git");
144
+ const hasGit = await checkPathExists(gitDir);
145
+ if (!hasGit) {
146
+ return { hasGit: false };
147
+ }
148
+ const hooksPath = join(gitDir, "hooks");
149
+ const hasHooks = await checkPathExists(hooksPath);
150
+ return {
151
+ hasGit: true,
152
+ gitHooksPath: hasHooks ? hooksPath : void 0
153
+ };
154
+ }
155
+ async function detectLockfile(projectRoot, packageManager) {
156
+ const lockfiles = {
157
+ npm: "package-lock.json",
158
+ yarn: "yarn.lock",
159
+ pnpm: "pnpm-lock.yaml",
160
+ bun: "bun.lockb"
161
+ };
162
+ const lockfile = lockfiles[packageManager];
163
+ const lockfilePath = join(projectRoot, lockfile);
164
+ if (await checkPathExists(lockfilePath)) {
165
+ return lockfile;
166
+ }
167
+ return lockfile;
168
+ }
169
+ async function detectContext(projectRoot) {
170
+ const fullPath = resolve(projectRoot);
171
+ if (detectionCache.has(fullPath)) {
172
+ logger.debug(`Using cached context for ${fullPath}`);
173
+ const cached = detectionCache.get(fullPath);
174
+ if (cached) {
175
+ return cached;
176
+ }
177
+ }
178
+ logger.debug(`Detecting context for project: ${fullPath}`);
179
+ let pkg;
180
+ try {
181
+ pkg = await readPackageJson(fullPath);
182
+ } catch (error) {
183
+ const errorMessage = error instanceof Error ? error.message : String(error);
184
+ throw new DetectionError(
185
+ `Invalid project: package.json not found or invalid. ${errorMessage}`,
186
+ { projectRoot: fullPath }
187
+ );
188
+ }
189
+ const [
190
+ frameworkInfo,
191
+ typescriptInfo,
192
+ bundlerInfo,
193
+ packageManager,
194
+ srcDir,
195
+ publicDir,
196
+ gitInfo
197
+ ] = await Promise.all([
198
+ Promise.resolve(detectFramework(pkg)),
199
+ detectTypeScript(fullPath),
200
+ detectBundler(fullPath, pkg),
201
+ detectPackageManager(fullPath),
202
+ detectSrcDir(fullPath),
203
+ detectPublicDir(fullPath),
204
+ detectGit(fullPath)
205
+ ]);
206
+ const lockfile = await detectLockfile(fullPath, packageManager);
207
+ const dependencies = pkg["dependencies"] || {};
208
+ const devDependencies = pkg["devDependencies"] || {};
209
+ const context = {
210
+ // Framework
211
+ framework: frameworkInfo.framework,
212
+ frameworkVersion: frameworkInfo.version,
213
+ // Bundler
214
+ bundler: bundlerInfo.bundler,
215
+ bundlerVersion: bundlerInfo.version,
216
+ // Language
217
+ typescript: typescriptInfo.typescript,
218
+ tsconfigPath: typescriptInfo.tsconfigPath,
219
+ // Package Manager
220
+ packageManager,
221
+ lockfile,
222
+ // Structure
223
+ projectRoot: fullPath,
224
+ srcDir,
225
+ publicDir,
226
+ // Environment
227
+ os: platform,
228
+ nodeVersion: version,
229
+ // Dependencies
230
+ dependencies,
231
+ devDependencies,
232
+ // Git
233
+ hasGit: gitInfo.hasGit,
234
+ gitHooksPath: gitInfo.gitHooksPath
235
+ };
236
+ detectionCache.set(fullPath, context);
237
+ logger.debug(`Context detected successfully for ${fullPath}`, {
238
+ framework: context.framework,
239
+ bundler: context.bundler,
240
+ typescript: context.typescript
241
+ });
242
+ return context;
243
+ }
244
+
245
+ // src/cli/prompts/language.ts
246
+ import inquirer from "inquirer";
247
+
248
+ // src/cli/i18n/fr.ts
249
+ var fr = {
250
+ language: {
251
+ select: "Choisissez votre langue",
252
+ options: [
253
+ { value: "fr", name: "Fran\xE7ais" },
254
+ { value: "en", name: "English" },
255
+ { value: "es", name: "Espa\xF1ol" }
256
+ ]
257
+ },
258
+ common: {
259
+ continue: "Continuer",
260
+ cancel: "Annuler",
261
+ back: "Retour",
262
+ none: "Aucun",
263
+ selected: (count) => count === 0 ? "Aucune biblioth\xE8que s\xE9lectionn\xE9e" : count === 1 ? "1 biblioth\xE8que s\xE9lectionn\xE9e" : `${count} biblioth\xE8ques s\xE9lectionn\xE9es`
264
+ },
265
+ plugins: {
266
+ selectCategory: (category) => `S\xE9lectionnez vos biblioth\xE8ques : ${category}`,
267
+ selectMultiple: "S\xE9lection multiple",
268
+ pressSpace: "Appuyez sur <espace> pour s\xE9lectionner",
269
+ pressEnter: "Appuyez sur <entr\xE9e> pour valider",
270
+ description: "Description"
271
+ },
272
+ detection: {
273
+ detecting: "\u{1F50D} D\xE9tection du contexte...",
274
+ framework: "Framework",
275
+ typescript: "TypeScript",
276
+ bundler: "Bundler",
277
+ packageManager: "Gestionnaire de paquets"
278
+ },
279
+ confirmation: {
280
+ summary: "\u{1F4CB} R\xE9sum\xE9 de l'installation",
281
+ packagesToInstall: "\u{1F4E6} Packages \xE0 installer",
282
+ filesToCreate: "\u{1F4DD} Fichiers qui seront cr\xE9\xE9s",
283
+ filesToModify: "\u{1F4DD} Fichiers qui seront modifi\xE9s",
284
+ continueQuestion: "Continuer avec l'installation ?"
285
+ },
286
+ installation: {
287
+ installing: "Installation en cours...",
288
+ configuring: "Configuration en cours...",
289
+ success: "\u2728 Installation termin\xE9e !",
290
+ error: "\u274C Erreur lors de l'installation",
291
+ rollback: "\u21BA Rollback en cours..."
292
+ },
293
+ report: {
294
+ title: "\u2728 Installation termin\xE9e !",
295
+ packagesInstalled: "\u{1F4E6} Packages install\xE9s",
296
+ filesCreated: "\u{1F4DD} Fichiers cr\xE9\xE9s",
297
+ filesModified: "\u{1F4DD} Fichiers modifi\xE9s",
298
+ nextSteps: "\u{1F680} Prochaines \xE9tapes"
299
+ },
300
+ errors: {
301
+ detectionFailed: "\xC9chec de la d\xE9tection du contexte",
302
+ installationFailed: "\xC9chec de l'installation",
303
+ validationFailed: "\xC9chec de la validation",
304
+ incompatiblePlugins: (plugins) => `Plugins incompatibles d\xE9tect\xE9s : ${plugins.join(", ")}`
305
+ }
306
+ };
307
+
308
+ // src/cli/i18n/en.ts
309
+ var en = {
310
+ language: {
311
+ select: "Choose your language",
312
+ options: [
313
+ { value: "fr", name: "Fran\xE7ais" },
314
+ { value: "en", name: "English" },
315
+ { value: "es", name: "Espa\xF1ol" }
316
+ ]
317
+ },
318
+ common: {
319
+ continue: "Continue",
320
+ cancel: "Cancel",
321
+ back: "Back",
322
+ none: "None",
323
+ selected: (count) => count === 0 ? "No library selected" : count === 1 ? "1 library selected" : `${count} libraries selected`
324
+ },
325
+ plugins: {
326
+ selectCategory: (category) => `Select your libraries: ${category}`,
327
+ selectMultiple: "Multiple selection",
328
+ pressSpace: "Press <space> to select",
329
+ pressEnter: "Press <enter> to confirm",
330
+ description: "Description"
331
+ },
332
+ detection: {
333
+ detecting: "\u{1F50D} Detecting context...",
334
+ framework: "Framework",
335
+ typescript: "TypeScript",
336
+ bundler: "Bundler",
337
+ packageManager: "Package manager"
338
+ },
339
+ confirmation: {
340
+ summary: "\u{1F4CB} Installation Summary",
341
+ packagesToInstall: "\u{1F4E6} Packages to install",
342
+ filesToCreate: "\u{1F4DD} Files that will be created",
343
+ filesToModify: "\u{1F4DD} Files that will be modified",
344
+ continueQuestion: "Continue with installation?"
345
+ },
346
+ installation: {
347
+ installing: "Installing...",
348
+ configuring: "Configuring...",
349
+ success: "\u2728 Installation completed!",
350
+ error: "\u274C Installation error",
351
+ rollback: "\u21BA Rolling back..."
352
+ },
353
+ report: {
354
+ title: "\u2728 Installation completed!",
355
+ packagesInstalled: "\u{1F4E6} Installed packages",
356
+ filesCreated: "\u{1F4DD} Created files",
357
+ filesModified: "\u{1F4DD} Modified files",
358
+ nextSteps: "\u{1F680} Next steps"
359
+ },
360
+ errors: {
361
+ detectionFailed: "Context detection failed",
362
+ installationFailed: "Installation failed",
363
+ validationFailed: "Validation failed",
364
+ incompatiblePlugins: (plugins) => `Incompatible plugins detected: ${plugins.join(", ")}`
365
+ }
366
+ };
367
+
368
+ // src/cli/i18n/es.ts
369
+ var es = {
370
+ language: {
371
+ select: "Elige tu idioma",
372
+ options: [
373
+ { value: "fr", name: "Fran\xE7ais" },
374
+ { value: "en", name: "English" },
375
+ { value: "es", name: "Espa\xF1ol" }
376
+ ]
377
+ },
378
+ common: {
379
+ continue: "Continuar",
380
+ cancel: "Cancelar",
381
+ back: "Volver",
382
+ none: "Ninguno",
383
+ selected: (count) => count === 0 ? "Ninguna biblioteca seleccionada" : count === 1 ? "1 biblioteca seleccionada" : `${count} bibliotecas seleccionadas`
384
+ },
385
+ plugins: {
386
+ selectCategory: (category) => `Selecciona tus bibliotecas: ${category}`,
387
+ selectMultiple: "Selecci\xF3n m\xFAltiple",
388
+ pressSpace: "Presiona <espacio> para seleccionar",
389
+ pressEnter: "Presiona <entrar> para confirmar",
390
+ description: "Descripci\xF3n"
391
+ },
392
+ detection: {
393
+ detecting: "\u{1F50D} Detectando contexto...",
394
+ framework: "Framework",
395
+ typescript: "TypeScript",
396
+ bundler: "Bundler",
397
+ packageManager: "Gestor de paquetes"
398
+ },
399
+ confirmation: {
400
+ summary: "\u{1F4CB} Resumen de la instalaci\xF3n",
401
+ packagesToInstall: "\u{1F4E6} Paquetes a instalar",
402
+ filesToCreate: "\u{1F4DD} Archivos que se crear\xE1n",
403
+ filesToModify: "\u{1F4DD} Archivos que se modificar\xE1n",
404
+ continueQuestion: "\xBFContinuar con la instalaci\xF3n?"
405
+ },
406
+ installation: {
407
+ installing: "Instalando...",
408
+ configuring: "Configurando...",
409
+ success: "\u2728 \xA1Instalaci\xF3n completada!",
410
+ error: "\u274C Error en la instalaci\xF3n",
411
+ rollback: "\u21BA Revirtiendo..."
412
+ },
413
+ report: {
414
+ title: "\u2728 \xA1Instalaci\xF3n completada!",
415
+ packagesInstalled: "\u{1F4E6} Paquetes instalados",
416
+ filesCreated: "\u{1F4DD} Archivos creados",
417
+ filesModified: "\u{1F4DD} Archivos modificados",
418
+ nextSteps: "\u{1F680} Pr\xF3ximos pasos"
419
+ },
420
+ errors: {
421
+ detectionFailed: "Fallo en la detecci\xF3n del contexto",
422
+ installationFailed: "Fallo en la instalaci\xF3n",
423
+ validationFailed: "Fallo en la validaci\xF3n",
424
+ incompatiblePlugins: (plugins) => `Plugins incompatibles detectados: ${plugins.join(", ")}`
425
+ }
426
+ };
427
+
428
+ // src/cli/i18n/index.ts
429
+ function getTranslations(lang) {
430
+ switch (lang) {
431
+ case "fr":
432
+ return fr;
433
+ case "en":
434
+ return en;
435
+ case "es":
436
+ return es;
437
+ default:
438
+ return en;
439
+ }
440
+ }
441
+
442
+ // src/cli/prompts/language.ts
443
+ async function promptLanguage() {
444
+ const defaultTranslations = getTranslations("en");
445
+ const { language } = await inquirer.prompt([
446
+ {
447
+ type: "list",
448
+ name: "language",
449
+ message: defaultTranslations.language.select,
450
+ choices: defaultTranslations.language.options.map((opt) => ({
451
+ name: opt.name,
452
+ value: opt.value
453
+ })),
454
+ default: "en"
455
+ }
456
+ ]);
457
+ return language;
458
+ }
459
+
460
+ // src/cli/prompts/select-plugins.ts
461
+ import inquirer2 from "inquirer";
462
+ function getCategoryName(category, _lang) {
463
+ const categoryMap = {
464
+ ["routing" /* ROUTING */]: "Routing",
465
+ ["state" /* STATE */]: "State Management",
466
+ ["http" /* HTTP */]: "HTTP Client",
467
+ ["css" /* CSS */]: "CSS / Styling",
468
+ ["ui" /* UI */]: "UI Components",
469
+ ["forms" /* FORMS */]: "Forms",
470
+ ["tooling" /* TOOLING */]: "Tooling",
471
+ ["testing" /* TESTING */]: "Testing",
472
+ ["i18n" /* I18N */]: "Internationalization",
473
+ ["animation" /* ANIMATION */]: "Animation",
474
+ ["utils" /* UTILS */]: "Utilities"
475
+ };
476
+ return categoryMap[category] || category;
477
+ }
478
+ function formatPluginChoice(plugin, _lang) {
479
+ return {
480
+ name: `${plugin.displayName}
481
+ ${plugin.description}`,
482
+ value: plugin.name,
483
+ short: plugin.displayName
484
+ };
485
+ }
486
+ async function promptPluginSelection(ctx, availablePlugins, lang) {
487
+ const translations = getTranslations(lang);
488
+ const selectedPlugins = [];
489
+ const pluginsByCategory = /* @__PURE__ */ new Map();
490
+ for (const plugin of availablePlugins) {
491
+ if (!plugin.frameworks.includes(ctx.framework)) {
492
+ continue;
493
+ }
494
+ if (plugin.requiresTypeScript === true && !ctx.typescript) {
495
+ continue;
496
+ }
497
+ if (plugin.bundlers && ctx.bundler && !plugin.bundlers.includes(ctx.bundler)) {
498
+ continue;
499
+ }
500
+ const category = plugin.category;
501
+ if (!pluginsByCategory.has(category)) {
502
+ pluginsByCategory.set(category, []);
503
+ }
504
+ const categoryPluginsList = pluginsByCategory.get(category);
505
+ if (categoryPluginsList !== void 0) {
506
+ categoryPluginsList.push(plugin);
507
+ }
508
+ }
509
+ const categories = Array.from(pluginsByCategory.keys()).sort();
510
+ for (const category of categories) {
511
+ const plugins = pluginsByCategory.get(category);
512
+ if (plugins === void 0 || plugins.length === 0) {
513
+ continue;
514
+ }
515
+ const categoryName = getCategoryName(category, lang);
516
+ const categoryPlugins = getPluginsByCategory(category).filter(
517
+ (p) => plugins.some((ap) => ap.name === p.name)
518
+ );
519
+ const choices = [
520
+ ...categoryPlugins.map((plugin) => formatPluginChoice(plugin, lang)),
521
+ new inquirer2.Separator(),
522
+ {
523
+ name: translations.common.none,
524
+ value: "__none__",
525
+ short: translations.common.none
526
+ }
527
+ ];
528
+ const { selected } = await inquirer2.prompt([
529
+ {
530
+ type: "checkbox",
531
+ name: "selected",
532
+ message: `${translations.plugins.selectCategory(categoryName)}
533
+ ${translations.plugins.pressSpace} | ${translations.plugins.pressEnter}`,
534
+ choices,
535
+ pageSize: 10,
536
+ loop: false
537
+ }
538
+ ]);
539
+ const pluginNames = selected.filter((name) => name !== "__none__");
540
+ for (const pluginName of pluginNames) {
541
+ const plugin = categoryPlugins.find((p) => p.name === pluginName);
542
+ if (plugin !== void 0) {
543
+ selectedPlugins.push(plugin);
544
+ }
545
+ }
546
+ }
547
+ return selectedPlugins;
548
+ }
549
+
550
+ // src/cli/prompts/confirm.ts
551
+ import inquirer3 from "inquirer";
552
+ async function promptConfirmation(selectedPlugins, lang) {
553
+ const translations = getTranslations(lang);
554
+ console.log(`
555
+ ${translations.confirmation.summary}
556
+ `);
557
+ console.log(`${translations.confirmation.packagesToInstall}:`);
558
+ for (const plugin of selectedPlugins) {
559
+ console.log(
560
+ ` \u2022 ${plugin.displayName}${plugin.version ? ` (${plugin.version})` : ""}`
561
+ );
562
+ }
563
+ const { confirm } = await inquirer3.prompt([
564
+ {
565
+ type: "confirm",
566
+ name: "confirm",
567
+ message: translations.confirmation.continueQuestion,
568
+ default: true
569
+ }
570
+ ]);
571
+ return confirm;
572
+ }
573
+
574
+ // src/core/installer.ts
575
+ var Installer = class {
576
+ /**
577
+ * @param ctx - Contexte du projet détecté
578
+ * @param validator - Validateur de compatibilité
579
+ * @param writer - Writer de configuration
580
+ * @param backupManager - Gestionnaire de backups
581
+ */
582
+ constructor(ctx, validator, writer, backupManager) {
583
+ this.ctx = ctx;
584
+ this.validator = validator;
585
+ this.writer = writer;
586
+ this.backupManager = backupManager;
587
+ }
588
+ /**
589
+ * Installe un ensemble de plugins
590
+ *
591
+ * @param plugins - Liste des plugins à installer
592
+ * @param options - Options d'installation (skipPackageInstall pour --no-install)
593
+ * @returns Rapport d'installation avec détails
594
+ * @throws {Error} Si l'installation échoue (après rollback)
595
+ *
596
+ * @example
597
+ * ```typescript
598
+ * const report = await installer.install([plugin1, plugin2])
599
+ * console.log(`Installed ${report.installed.length} plugin(s)`)
600
+ * ```
601
+ */
602
+ async install(plugins, options) {
603
+ const startTime = Date.now();
604
+ logger.info(`Starting installation of ${plugins.length} plugin(s)`);
605
+ try {
606
+ logger.debug("Validating plugins compatibility...");
607
+ const validationResult = this.validator.validate(plugins);
608
+ if (!validationResult.valid) {
609
+ const errorMessages = validationResult.errors.map((e) => e.message).join("; ");
610
+ throw new Error(
611
+ `Validation failed: ${errorMessages}. Please fix compatibility issues before installing.`
612
+ );
613
+ }
614
+ if (validationResult.warnings.length > 0) {
615
+ logger.warn(
616
+ `Found ${validationResult.warnings.length} warning(s):`,
617
+ validationResult.warnings.map((w) => w.message)
618
+ );
619
+ }
620
+ logger.debug("Resolving dependencies...");
621
+ const resolved = this.resolveDependencies(plugins);
622
+ const allPlugins = resolved.plugins;
623
+ if (resolved.autoInstalled.length > 0) {
624
+ logger.info(
625
+ `Auto-installing ${resolved.autoInstalled.length} required dependency(ies): ${resolved.autoInstalled.join(", ")}`
626
+ );
627
+ }
628
+ logger.debug("Running pre-install hooks...");
629
+ await this.runPreInstallHooks(allPlugins);
630
+ if (options?.skipPackageInstall) {
631
+ logger.info("Skipping package installation (--no-install mode)");
632
+ } else {
633
+ logger.debug("Installing packages...");
634
+ const installResults = await this.installPackages(allPlugins);
635
+ const failedInstalls = installResults.filter((r) => !r.success);
636
+ if (failedInstalls.length > 0) {
637
+ throw new Error(
638
+ `Failed to install packages for ${failedInstalls.length} plugin(s)`
639
+ );
640
+ }
641
+ }
642
+ logger.debug("Configuring plugins...");
643
+ const configResults = [];
644
+ const filesCreated = [];
645
+ for (const plugin of allPlugins) {
646
+ try {
647
+ logger.debug(`Configuring ${plugin.displayName}...`);
648
+ const configResult = await plugin.configure(this.ctx);
649
+ configResults.push(configResult);
650
+ filesCreated.push(...configResult.files || []);
651
+ if (!configResult.success) {
652
+ throw new Error(
653
+ `Configuration failed for ${plugin.displayName}: ${configResult.message || "Unknown error"}`
654
+ );
655
+ }
656
+ } catch (error) {
657
+ const errorMessage = error instanceof Error ? error.message : String(error);
658
+ logger.error(
659
+ `Configuration failed for ${plugin.displayName}: ${errorMessage}`
660
+ );
661
+ throw error;
662
+ }
663
+ }
664
+ logger.debug("Running post-install hooks...");
665
+ await this.runPostInstallHooks(allPlugins);
666
+ const duration = Date.now() - startTime;
667
+ const installed = allPlugins.map((p) => p.name);
668
+ logger.info(
669
+ `Successfully installed ${installed.length} plugin(s) in ${duration}ms`
670
+ );
671
+ return {
672
+ success: true,
673
+ duration,
674
+ installed,
675
+ warnings: validationResult.warnings,
676
+ filesCreated
677
+ };
678
+ } catch (error) {
679
+ const errorMessage = error instanceof Error ? error.message : String(error);
680
+ logger.error(`Installation failed: ${errorMessage}`);
681
+ logger.debug("Rolling back changes...");
682
+ try {
683
+ await this.rollback(plugins);
684
+ } catch (rollbackError) {
685
+ const rollbackMessage = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
686
+ logger.error(`Rollback failed: ${rollbackMessage}`);
687
+ }
688
+ const duration = Date.now() - startTime;
689
+ return {
690
+ success: false,
691
+ duration,
692
+ installed: [],
693
+ warnings: [],
694
+ filesCreated: []
695
+ };
696
+ }
697
+ }
698
+ /**
699
+ * Résout les dépendances requises et recommandées
700
+ *
701
+ * @param plugins - Liste des plugins initiaux
702
+ * @returns Plugins avec dépendances résolues
703
+ *
704
+ * @internal
705
+ */
706
+ resolveDependencies(plugins) {
707
+ const pluginMap = /* @__PURE__ */ new Map();
708
+ const autoInstalled = [];
709
+ for (const plugin of plugins) {
710
+ pluginMap.set(plugin.name, plugin);
711
+ }
712
+ let changed = true;
713
+ while (changed) {
714
+ changed = false;
715
+ for (const plugin of Array.from(pluginMap.values())) {
716
+ if (!plugin.requires) {
717
+ continue;
718
+ }
719
+ for (const required of plugin.requires) {
720
+ if (!pluginMap.has(required)) {
721
+ logger.warn(
722
+ `Plugin ${plugin.name} requires ${required}, but it's not available in the registry`
723
+ );
724
+ }
725
+ }
726
+ }
727
+ }
728
+ return {
729
+ plugins: Array.from(pluginMap.values()),
730
+ autoInstalled
731
+ };
732
+ }
733
+ /**
734
+ * Installe les packages pour tous les plugins
735
+ *
736
+ * @param plugins - Liste des plugins
737
+ * @returns Résultats d'installation pour chaque plugin
738
+ *
739
+ * @internal
740
+ */
741
+ async installPackages(plugins) {
742
+ const results = [];
743
+ const installPromises = plugins.map(async (plugin) => {
744
+ try {
745
+ if (plugin.detect && await plugin.detect(this.ctx)) {
746
+ logger.debug(`${plugin.displayName} is already installed`);
747
+ return {
748
+ packages: {},
749
+ success: true,
750
+ message: "Already installed"
751
+ };
752
+ }
753
+ if (plugin.preInstall) {
754
+ await plugin.preInstall(this.ctx);
755
+ }
756
+ const result = await plugin.install(this.ctx);
757
+ if (plugin.postInstall) {
758
+ await plugin.postInstall(this.ctx);
759
+ }
760
+ return result;
761
+ } catch (error) {
762
+ const errorMessage = error instanceof Error ? error.message : String(error);
763
+ logger.error(`Failed to install ${plugin.displayName}: ${errorMessage}`);
764
+ return {
765
+ packages: {},
766
+ success: false,
767
+ message: errorMessage
768
+ };
769
+ }
770
+ });
771
+ const installResults = await Promise.all(installPromises);
772
+ results.push(...installResults);
773
+ const allDependencies = [];
774
+ const allDevDependencies = [];
775
+ for (const result of installResults) {
776
+ if (result.success && result.packages) {
777
+ if (result.packages.dependencies) {
778
+ allDependencies.push(...result.packages.dependencies);
779
+ }
780
+ if (result.packages.devDependencies) {
781
+ allDevDependencies.push(...result.packages.devDependencies);
782
+ }
783
+ }
784
+ }
785
+ if (allDependencies.length > 0) {
786
+ await installPackages(allDependencies, {
787
+ packageManager: this.ctx.packageManager,
788
+ projectRoot: this.ctx.projectRoot,
789
+ dev: false,
790
+ silent: false
791
+ });
792
+ }
793
+ if (allDevDependencies.length > 0) {
794
+ await installPackages(allDevDependencies, {
795
+ packageManager: this.ctx.packageManager,
796
+ projectRoot: this.ctx.projectRoot,
797
+ dev: true,
798
+ silent: false
799
+ });
800
+ }
801
+ return results;
802
+ }
803
+ /**
804
+ * Exécute les hooks pre-install de tous les plugins
805
+ *
806
+ * @param plugins - Liste des plugins
807
+ *
808
+ * @internal
809
+ */
810
+ async runPreInstallHooks(plugins) {
811
+ for (const plugin of plugins) {
812
+ if (plugin.preInstall) {
813
+ try {
814
+ await plugin.preInstall(this.ctx);
815
+ } catch (error) {
816
+ const errorMessage = error instanceof Error ? error.message : String(error);
817
+ logger.warn(
818
+ `Pre-install hook failed for ${plugin.displayName}: ${errorMessage}`
819
+ );
820
+ }
821
+ }
822
+ }
823
+ }
824
+ /**
825
+ * Exécute les hooks post-install de tous les plugins
826
+ *
827
+ * @param plugins - Liste des plugins
828
+ *
829
+ * @internal
830
+ */
831
+ async runPostInstallHooks(plugins) {
832
+ for (const plugin of plugins) {
833
+ if (plugin.postInstall) {
834
+ try {
835
+ await plugin.postInstall(this.ctx);
836
+ } catch (error) {
837
+ const errorMessage = error instanceof Error ? error.message : String(error);
838
+ logger.warn(
839
+ `Post-install hook failed for ${plugin.displayName}: ${errorMessage}`
840
+ );
841
+ }
842
+ }
843
+ }
844
+ }
845
+ /**
846
+ * Effectue un rollback complet en cas d'erreur
847
+ *
848
+ * @param plugins - Liste des plugins installés
849
+ *
850
+ * @internal
851
+ */
852
+ async rollback(plugins) {
853
+ logger.debug("Starting rollback...");
854
+ for (const plugin of plugins.reverse()) {
855
+ if (plugin.rollback) {
856
+ try {
857
+ await plugin.rollback(this.ctx);
858
+ logger.debug(`Rolled back ${plugin.displayName}`);
859
+ } catch (error) {
860
+ const errorMessage = error instanceof Error ? error.message : String(error);
861
+ logger.error(
862
+ `Rollback failed for ${plugin.displayName}: ${errorMessage}`
863
+ );
864
+ }
865
+ }
866
+ }
867
+ try {
868
+ await this.backupManager.restoreAll();
869
+ logger.debug("Restored all file backups");
870
+ } catch (error) {
871
+ const errorMessage = error instanceof Error ? error.message : String(error);
872
+ logger.error(`Failed to restore backups: ${errorMessage}`);
873
+ }
874
+ }
875
+ };
876
+
877
+ // src/cli/ui/spinner.ts
878
+ import ora from "ora";
879
+ var SpinnerManager = class {
880
+ spinner = null;
881
+ constructor() {
882
+ }
883
+ /**
884
+ * Démarre un spinner avec un message
885
+ *
886
+ * @param message - Message à afficher (peut être une clé de traduction)
887
+ * @param text - Texte personnalisé (optionnel, remplace le message traduit)
888
+ */
889
+ start(message, text) {
890
+ this.stop();
891
+ const displayText = text || message;
892
+ this.spinner = ora({
893
+ text: displayText,
894
+ spinner: "dots"
895
+ }).start();
896
+ }
897
+ /**
898
+ * Met à jour le message du spinner
899
+ *
900
+ * @param message - Nouveau message
901
+ */
902
+ update(message) {
903
+ if (this.spinner) {
904
+ this.spinner.text = message;
905
+ }
906
+ }
907
+ /**
908
+ * Arrête le spinner avec succès
909
+ *
910
+ * @param message - Message de succès (optionnel)
911
+ */
912
+ succeed(message) {
913
+ if (this.spinner) {
914
+ this.spinner.succeed(message);
915
+ this.spinner = null;
916
+ }
917
+ }
918
+ /**
919
+ * Arrête le spinner avec une erreur
920
+ *
921
+ * @param message - Message d'erreur (optionnel)
922
+ */
923
+ fail(message) {
924
+ if (this.spinner) {
925
+ this.spinner.fail(message);
926
+ this.spinner = null;
927
+ }
928
+ }
929
+ /**
930
+ * Arrête le spinner avec un avertissement
931
+ *
932
+ * @param message - Message d'avertissement (optionnel)
933
+ */
934
+ warn(message) {
935
+ if (this.spinner) {
936
+ this.spinner.warn(message);
937
+ this.spinner = null;
938
+ }
939
+ }
940
+ /**
941
+ * Arrête le spinner avec un message d'information
942
+ *
943
+ * @param message - Message d'information (optionnel)
944
+ */
945
+ info(message) {
946
+ if (this.spinner) {
947
+ this.spinner.info(message);
948
+ this.spinner = null;
949
+ }
950
+ }
951
+ /**
952
+ * Arrête le spinner sans message
953
+ */
954
+ stop() {
955
+ if (this.spinner) {
956
+ this.spinner.stop();
957
+ this.spinner = null;
958
+ }
959
+ }
960
+ /**
961
+ * Vérifie si un spinner est actif
962
+ */
963
+ isSpinning() {
964
+ return this.spinner !== null && this.spinner.isSpinning;
965
+ }
966
+ };
967
+
968
+ // src/cli/ui/report.ts
969
+ function displayInstallationReport(report, plugins, lang) {
970
+ const t = getTranslations(lang);
971
+ console.log(`
972
+ ${t.report.title}`);
973
+ if (report.duration > 0) {
974
+ const durationSeconds = (report.duration / 1e3).toFixed(1);
975
+ console.log(` (${durationSeconds}s)
976
+ `);
977
+ } else {
978
+ console.log("");
979
+ }
980
+ if (report.installed.length > 0) {
981
+ console.log(`${t.report.packagesInstalled}:`);
982
+ for (const pluginName of report.installed) {
983
+ const plugin = plugins.find((p) => p.name === pluginName);
984
+ const version2 = plugin?.version ? ` (${plugin.version})` : "";
985
+ console.log(` \u2713 ${plugin?.displayName || pluginName}${version2}`);
986
+ }
987
+ console.log("");
988
+ }
989
+ const createdFiles = report.filesCreated.filter((f) => f.type === "create");
990
+ if (createdFiles.length > 0) {
991
+ console.log(`${t.report.filesCreated}:`);
992
+ for (const file of createdFiles) {
993
+ console.log(` \u2022 ${file.path}`);
994
+ }
995
+ console.log("");
996
+ }
997
+ const modifiedFiles = report.filesCreated.filter((f) => f.type === "modify");
998
+ if (modifiedFiles.length > 0) {
999
+ console.log(`${t.report.filesModified}:`);
1000
+ for (const file of modifiedFiles) {
1001
+ console.log(` \u2022 ${file.path}`);
1002
+ }
1003
+ console.log("");
1004
+ }
1005
+ if (report.warnings.length > 0) {
1006
+ console.log(`\u26A0\uFE0F ${report.warnings.length} warning(s):`);
1007
+ for (const warning of report.warnings) {
1008
+ console.log(` \u26A0 ${warning.message}`);
1009
+ if (warning.plugins && warning.plugins.length > 0) {
1010
+ console.log(` Plugins: ${warning.plugins.join(", ")}`);
1011
+ }
1012
+ }
1013
+ console.log("");
1014
+ }
1015
+ displayNextSteps(lang);
1016
+ }
1017
+ function displayNextSteps(lang) {
1018
+ const t = getTranslations(lang);
1019
+ console.log(`${t.report.nextSteps}:`);
1020
+ console.log(" 1. npm run dev");
1021
+ console.log(" 2. Visitez http://localhost:5173");
1022
+ console.log(" 3. Consultez la documentation dans src/");
1023
+ console.log("");
1024
+ }
1025
+
1026
+ // src/cli/commands/install.ts
1027
+ async function installReact(options) {
1028
+ try {
1029
+ const language = await promptLanguage();
1030
+ const t = getTranslations(language);
1031
+ console.log(`
1032
+ ${t.detection.detecting}`);
1033
+ const projectRoot = process.cwd();
1034
+ const ctx = await detectContext(projectRoot);
1035
+ console.log(
1036
+ ` \u2713 ${t.detection.framework}: ${ctx.framework} ${ctx.frameworkVersion}`
1037
+ );
1038
+ console.log(
1039
+ ` \u2713 ${t.detection.typescript}: ${ctx.typescript ? "Oui" : "Non"}`
1040
+ );
1041
+ if (ctx.bundler) {
1042
+ console.log(
1043
+ ` \u2713 ${t.detection.bundler}: ${ctx.bundler} ${ctx.bundlerVersion || ""}`
1044
+ );
1045
+ }
1046
+ console.log(` \u2713 ${t.detection.packageManager}: ${ctx.packageManager}
1047
+ `);
1048
+ let selectedPlugins = [];
1049
+ if (options.yes) {
1050
+ logger.info("Using default recommendations (--yes mode)");
1051
+ } else {
1052
+ selectedPlugins = await promptPluginSelection(
1053
+ ctx,
1054
+ pluginRegistry,
1055
+ language
1056
+ );
1057
+ }
1058
+ if (selectedPlugins.length === 0) {
1059
+ console.log(`
1060
+ ${t.common.selected(0)}`);
1061
+ console.log("Exiting...");
1062
+ return;
1063
+ }
1064
+ console.log(`
1065
+ ${t.common.selected(selectedPlugins.length)}`);
1066
+ if (!options.yes && !options.silent) {
1067
+ const confirmed = await promptConfirmation(selectedPlugins, language);
1068
+ if (!confirmed) {
1069
+ console.log(t.common.cancel);
1070
+ return;
1071
+ }
1072
+ }
1073
+ if (options.dryRun) {
1074
+ console.log("\n\u{1F50D} MODE DRY-RUN (simulation uniquement)");
1075
+ console.log("\u2501".repeat(50));
1076
+ console.log("\n\u{1F4E6} Packages \xE0 installer :");
1077
+ for (const plugin of selectedPlugins) {
1078
+ console.log(
1079
+ ` ${plugin.name}${plugin.version ? `@${plugin.version}` : ""}`
1080
+ );
1081
+ }
1082
+ console.log("\n\u{1F4DD} Fichiers qui seraient cr\xE9\xE9s/modifi\xE9s :");
1083
+ for (const plugin of selectedPlugins) {
1084
+ console.log(` ${plugin.displayName} configuration`);
1085
+ }
1086
+ console.log("\n\u26A0\uFE0F Aucune modification n'a \xE9t\xE9 effectu\xE9e (dry-run)");
1087
+ console.log("\u{1F4A1} Ex\xE9cutez sans --dry-run pour appliquer les changements");
1088
+ return;
1089
+ }
1090
+ const backupManager = new BackupManager();
1091
+ const configWriter = new ConfigWriter(backupManager);
1092
+ const validator = new CompatibilityValidator(compatibilityRules);
1093
+ const installer = new Installer(ctx, validator, configWriter, backupManager);
1094
+ if (options.install === false) {
1095
+ console.log("\n\u2699\uFE0F Mode configuration uniquement (--no-install)");
1096
+ console.log("Les packages ne seront PAS install\xE9s\n");
1097
+ }
1098
+ const spinner = new SpinnerManager();
1099
+ spinner.start(t.installation.installing);
1100
+ try {
1101
+ const result = await installer.install(selectedPlugins, {
1102
+ skipPackageInstall: options.install === false
1103
+ });
1104
+ spinner.succeed(t.installation.success);
1105
+ if (result.success) {
1106
+ displayInstallationReport(result, selectedPlugins, language);
1107
+ } else {
1108
+ console.error(`
1109
+ ${t.installation.error}`);
1110
+ process.exit(1);
1111
+ }
1112
+ } catch (error) {
1113
+ spinner.fail(t.installation.error);
1114
+ throw error;
1115
+ }
1116
+ } catch (error) {
1117
+ logger.error("Installation failed:", error);
1118
+ if (error instanceof Error) {
1119
+ console.error(`
1120
+ \u274C ${error.message}`);
1121
+ }
1122
+ process.exit(1);
1123
+ }
1124
+ }
1125
+ export {
1126
+ installReact
1127
+ };