slicejs-cli 2.5.1 → 2.7.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.
@@ -3,6 +3,8 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import ora from 'ora';
5
5
  import Print from '../Print.js';
6
+ import { getProjectRoot, getApiPath, getSrcPath } from '../utils/PathHelper.js';
7
+ import { execSync } from 'child_process';
6
8
 
7
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
10
 
@@ -11,12 +13,39 @@ import { ComponentRegistry } from '../getComponent/getComponent.js';
11
13
 
12
14
  export default async function initializeProject(projectType) {
13
15
  try {
14
- // Directorio de origen para API (mantener copia local)
15
- let sliceBaseDir = path.join(__dirname, '../../../slicejs-web-framework');
16
- let apiDir = path.join(sliceBaseDir, 'api');
17
- let srcDir = path.join(sliceBaseDir, 'src');
18
- let destinationApi = path.join(__dirname, '../../../../api');
19
- let destinationSrc = path.join(__dirname, '../../../../src');
16
+ const projectRoot = getProjectRoot(import.meta.url);
17
+ const destinationApi = getApiPath(import.meta.url);
18
+ const destinationSrc = getSrcPath(import.meta.url);
19
+
20
+ const fwSpinner = ora('Ensuring latest Slice framework...').start();
21
+ let sliceBaseDir;
22
+ try {
23
+ const latest = execSync('npm view slicejs-web-framework version', { cwd: projectRoot }).toString().trim();
24
+ const installedPkgPath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'package.json');
25
+ let installed = null;
26
+ if (await fs.pathExists(installedPkgPath)) {
27
+ const pkg = await fs.readJson(installedPkgPath);
28
+ installed = pkg.version;
29
+ }
30
+ if (installed !== latest) {
31
+ execSync(`npm install slicejs-web-framework@${latest} --save`, { cwd: projectRoot, stdio: 'inherit' });
32
+ }
33
+ sliceBaseDir = path.join(projectRoot, 'node_modules', 'slicejs-web-framework');
34
+ fwSpinner.succeed(`slicejs-web-framework@${latest} ready`);
35
+ } catch (err) {
36
+ const fallback = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../slicejs-web-framework');
37
+ if (await fs.pathExists(fallback)) {
38
+ sliceBaseDir = fallback;
39
+ fwSpinner.warn('Using local slicejs-web-framework fallback');
40
+ } else {
41
+ fwSpinner.fail('Failed to ensure latest slicejs-web-framework');
42
+ Print.error(err.message);
43
+ return;
44
+ }
45
+ }
46
+
47
+ const apiDir = path.join(sliceBaseDir, 'api');
48
+ const srcDir = path.join(sliceBaseDir, 'src');
20
49
 
21
50
  try {
22
51
  // Verificar si los directorios de destino ya existen
@@ -131,6 +160,73 @@ export default async function initializeProject(projectType) {
131
160
  Print.info('You can add them later using "slice get <component-name>"');
132
161
  }
133
162
 
163
+ // 4. CONFIGURAR SCRIPTS EN package.json DEL PROYECTO
164
+ const pkgSpinner = ora('Configuring npm scripts...').start();
165
+ try {
166
+ const projectRoot = getProjectRoot(import.meta.url);
167
+ const pkgPath = path.join(projectRoot, 'package.json');
168
+
169
+ let pkg;
170
+ if (await fs.pathExists(pkgPath)) {
171
+ pkg = await fs.readJson(pkgPath);
172
+ } else {
173
+ pkg = {
174
+ name: path.basename(projectRoot),
175
+ version: '1.0.0',
176
+ description: 'Slice.js project',
177
+ main: 'api/index.js',
178
+ scripts: {}
179
+ };
180
+ }
181
+
182
+ pkg.scripts = pkg.scripts || {};
183
+
184
+ // Comandos principales
185
+ pkg.scripts['dev'] = 'slice dev';
186
+ pkg.scripts['start'] = 'slice start';
187
+
188
+ // Gestión de componentes
189
+ pkg.scripts['component:create'] = 'slice component create';
190
+ pkg.scripts['component:list'] = 'slice component list';
191
+ pkg.scripts['component:delete'] = 'slice component delete';
192
+
193
+ // Atajos de repositorio
194
+ pkg.scripts['get'] = 'slice get';
195
+ pkg.scripts['browse'] = 'slice browse';
196
+ pkg.scripts['sync'] = 'slice sync';
197
+
198
+ // Utilidades
199
+ pkg.scripts['slice:version'] = 'slice version';
200
+ pkg.scripts['slice:update'] = 'slice update';
201
+
202
+ // Legacy (compatibilidad)
203
+ pkg.scripts['slice:init'] = 'slice init';
204
+ pkg.scripts['slice:start'] = 'slice start';
205
+ pkg.scripts['slice:dev'] = 'slice dev';
206
+ pkg.scripts['slice:create'] = 'slice component create';
207
+ pkg.scripts['slice:list'] = 'slice component list';
208
+ pkg.scripts['slice:delete'] = 'slice component delete';
209
+ pkg.scripts['slice:get'] = 'slice get';
210
+ pkg.scripts['slice:browse'] = 'slice browse';
211
+ pkg.scripts['slice:sync'] = 'slice sync';
212
+ pkg.scripts['run'] = 'slice dev';
213
+
214
+ // Configuración de módulo
215
+ pkg.type = pkg.type || 'module';
216
+ pkg.engines = pkg.engines || { node: '>=20.0.0' };
217
+
218
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
219
+ pkgSpinner.succeed('npm scripts configured successfully');
220
+
221
+ console.log('\n🎯 New recommended commands:');
222
+ console.log(' npm run dev - Start development server');
223
+ console.log(' npm run get - Install components');
224
+ console.log(' npm run browse - Browse components');
225
+ } catch (error) {
226
+ pkgSpinner.fail('Failed to configure npm scripts');
227
+ Print.error(error.message);
228
+ }
229
+
134
230
  Print.success('Proyecto inicializado correctamente.');
135
231
  Print.newLine();
136
232
  Print.info('Next steps:');
@@ -155,4 +251,4 @@ async function getAllVisualComponents(registry) {
155
251
  Print.info(`Found ${allVisualComponents.length} Visual components in official repository`);
156
252
 
157
253
  return allVisualComponents;
158
- }
254
+ }
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
4
4
  import Table from 'cli-table3';
5
5
  import chalk from 'chalk';
6
6
  import Print from '../Print.js';
7
+ import { getSrcPath, getComponentsJsPath, getConfigPath } from '../utils/PathHelper.js';
7
8
 
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
10
 
@@ -13,7 +14,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
14
  */
14
15
  const loadConfig = () => {
15
16
  try {
16
- const configPath = path.join(__dirname, '../../../../src/sliceConfig.json');
17
+ const configPath = getConfigPath(import.meta.url);
17
18
  if (!fs.existsSync(configPath)) {
18
19
  Print.error('sliceConfig.json not found');
19
20
  Print.info('Run "slice init" to initialize your project');
@@ -72,7 +73,8 @@ const getComponents = () => {
72
73
  let allComponents = new Map();
73
74
 
74
75
  Object.entries(componentPaths).forEach(([category, { path: folderPath }]) => {
75
- const fullPath = path.join(__dirname, `../../../../${folderSuffix}`, folderPath);
76
+ const cleanFolderPath = folderPath ? folderPath.replace(/^[/\\]+/, '') : '';
77
+ const fullPath = getSrcPath(import.meta.url, cleanFolderPath);
76
78
  const files = listComponents(fullPath);
77
79
 
78
80
  files.forEach(file => {
@@ -150,7 +152,7 @@ function listComponentsReal() {
150
152
  Print.info(`Total: ${Object.keys(components).length} component${Object.keys(components).length !== 1 ? 's' : ''} found`);
151
153
 
152
154
  // Ruta donde se generará components.js
153
- const outputPath = path.join(__dirname, '../../../../src/Components/components.js');
155
+ const outputPath = getComponentsJsPath(import.meta.url);
154
156
 
155
157
  // Asegurar que el directorio existe
156
158
  const outputDir = path.dirname(outputPath);
@@ -7,6 +7,7 @@ import { spawn } from 'child_process';
7
7
  import { createServer } from 'net';
8
8
  import setupWatcher, { stopWatcher } from './watchServer.js';
9
9
  import Print from '../Print.js';
10
+ import { getConfigPath, getApiPath, getSrcPath, getDistPath } from '../utils/PathHelper.js';
10
11
 
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
 
@@ -15,7 +16,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
16
  */
16
17
  const loadConfig = () => {
17
18
  try {
18
- const configPath = path.join(__dirname, '../../../../src/sliceConfig.json');
19
+ const configPath = getConfigPath(import.meta.url);
19
20
  const rawData = fs.readFileSync(configPath, 'utf-8');
20
21
  return JSON.parse(rawData);
21
22
  } catch (error) {
@@ -52,7 +53,7 @@ async function isPortAvailable(port) {
52
53
  * Verifica si existe un build de producción
53
54
  */
54
55
  async function checkProductionBuild() {
55
- const distDir = path.join(__dirname, '../../../../dist');
56
+ const distDir = getDistPath(import.meta.url);
56
57
  return await fs.pathExists(distDir);
57
58
  }
58
59
 
@@ -60,8 +61,8 @@ async function checkProductionBuild() {
60
61
  * Verifica si existe la estructura de desarrollo
61
62
  */
62
63
  async function checkDevelopmentStructure() {
63
- const srcDir = path.join(__dirname, '../../../../src');
64
- const apiDir = path.join(__dirname, '../../../../api');
64
+ const srcDir = getSrcPath(import.meta.url);
65
+ const apiDir = getApiPath(import.meta.url);
65
66
 
66
67
  return (await fs.pathExists(srcDir)) && (await fs.pathExists(apiDir));
67
68
  }
@@ -71,7 +72,7 @@ async function checkDevelopmentStructure() {
71
72
  */
72
73
  function startNodeServer(port, mode) {
73
74
  return new Promise((resolve, reject) => {
74
- const apiIndexPath = path.join(__dirname, '../../../../api/index.js');
75
+ const apiIndexPath = getApiPath(import.meta.url, 'index.js');
75
76
 
76
77
  // Verificar que el archivo existe
77
78
  if (!fs.existsSync(apiIndexPath)) {
@@ -235,4 +236,4 @@ export default async function startServer(options = {}) {
235
236
  /**
236
237
  * Funciones de utilidad exportadas
237
238
  */
238
- export { checkProductionBuild, checkDevelopmentStructure, isPortAvailable };
239
+ export { checkProductionBuild, checkDevelopmentStructure, isPortAvailable };
@@ -0,0 +1,58 @@
1
+ import path from 'path'
2
+ import fs from 'fs-extra'
3
+ import { fileURLToPath } from 'url'
4
+
5
+ const sanitize = (s) => (s || '').replace(/^[/\\]+/, '')
6
+ const dirOf = (url) => path.dirname(fileURLToPath(url))
7
+
8
+ function candidates(moduleUrl) {
9
+ const dir = dirOf(moduleUrl)
10
+ return [
11
+ path.join(dir, '../../'),
12
+ path.join(dir, '../../../../')
13
+ ]
14
+ }
15
+
16
+ function resolveProjectRoot(moduleUrl) {
17
+ const dirs = candidates(moduleUrl)
18
+ for (const root of dirs) {
19
+ const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
20
+ const hasApi = fs.pathExistsSync(path.join(root, 'api'))
21
+ if (hasSrc || hasApi) return root
22
+ }
23
+ return dirs[1]
24
+ }
25
+
26
+ function joinProject(moduleUrl, ...segments) {
27
+ const root = resolveProjectRoot(moduleUrl)
28
+ const clean = segments.map(sanitize)
29
+ return path.join(root, ...clean)
30
+ }
31
+
32
+ export function getProjectRoot(moduleUrl) {
33
+ return resolveProjectRoot(moduleUrl)
34
+ }
35
+
36
+ export function getPath(moduleUrl, folder, ...segments) {
37
+ return joinProject(moduleUrl, folder, ...segments)
38
+ }
39
+
40
+ export function getSrcPath(moduleUrl, ...segments) {
41
+ return joinProject(moduleUrl, 'src', ...segments)
42
+ }
43
+
44
+ export function getApiPath(moduleUrl, ...segments) {
45
+ return joinProject(moduleUrl, 'api', ...segments)
46
+ }
47
+
48
+ export function getDistPath(moduleUrl, ...segments) {
49
+ return joinProject(moduleUrl, 'dist', ...segments)
50
+ }
51
+
52
+ export function getConfigPath(moduleUrl) {
53
+ return joinProject(moduleUrl, 'src', 'sliceConfig.json')
54
+ }
55
+
56
+ export function getComponentsJsPath(moduleUrl) {
57
+ return joinProject(moduleUrl, 'src', 'Components', 'components.js')
58
+ }
@@ -1,9 +1,10 @@
1
1
  // commands/utils/VersionChecker.js
2
2
 
3
- import fs from "fs-extra";
4
- import path from "path";
5
- import { fileURLToPath } from "url";
6
- import Print from "../Print.js";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+ import Print from "../Print.js";
7
+ import { getProjectRoot } from "../utils/PathHelper.js";
7
8
 
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
10
 
@@ -22,8 +23,9 @@ class VersionChecker {
22
23
  const cliPackage = await fs.readJson(cliPackagePath);
23
24
  this.currentCliVersion = cliPackage.version;
24
25
 
25
- // Get Framework version from node_modules
26
- const frameworkPackagePath = path.join(__dirname, '../../../../node_modules/slicejs-web-framework/package.json');
26
+ // Get Framework version from project node_modules
27
+ const projectRoot = getProjectRoot(import.meta.url);
28
+ const frameworkPackagePath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'package.json');
27
29
  if (await fs.pathExists(frameworkPackagePath)) {
28
30
  const frameworkPackage = await fs.readJson(frameworkPackagePath);
29
31
  this.currentFrameworkVersion = frameworkPackage.version;
@@ -162,4 +164,4 @@ class VersionChecker {
162
164
  // Singleton instance
163
165
  const versionChecker = new VersionChecker();
164
166
 
165
- export default versionChecker;
167
+ export default versionChecker;
@@ -0,0 +1,377 @@
1
+ // commands/utils/updateManager.js
2
+
3
+ import { exec } from "child_process";
4
+ import { promisify } from "util";
5
+ import inquirer from "inquirer";
6
+ import ora from "ora";
7
+ import Print from "../Print.js";
8
+ import versionChecker from "./VersionChecker.js";
9
+ import { getProjectRoot } from "../utils/PathHelper.js";
10
+ import path from "path";
11
+ import { fileURLToPath } from "url";
12
+
13
+ const execAsync = promisify(exec);
14
+
15
+ class UpdateManager {
16
+ constructor() {
17
+ this.packagesToUpdate = [];
18
+ }
19
+
20
+ async detectCliInstall() {
21
+ try {
22
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
23
+ const cliRoot = path.join(moduleDir, '../../');
24
+ const projectRoot = getProjectRoot(import.meta.url);
25
+ let globalPrefix = '';
26
+ try {
27
+ const { stdout } = await execAsync('npm config get prefix');
28
+ globalPrefix = stdout.toString().trim();
29
+ } catch {}
30
+ const localNodeModules = path.join(projectRoot, 'node_modules');
31
+ const globalNodeModules = globalPrefix ? path.join(globalPrefix, 'node_modules') : '';
32
+
33
+ if (cliRoot.startsWith(localNodeModules)) {
34
+ return { type: 'local', cliRoot, projectRoot, globalPrefix };
35
+ }
36
+ if (globalNodeModules && cliRoot.startsWith(globalNodeModules)) {
37
+ return { type: 'global', cliRoot, projectRoot, globalPrefix };
38
+ }
39
+ return { type: 'unknown', cliRoot, projectRoot, globalPrefix };
40
+ } catch (error) {
41
+ return { type: 'unknown' };
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Check for available updates and return structured info
47
+ */
48
+ async checkForUpdates() {
49
+ try {
50
+ const updateInfo = await versionChecker.checkForUpdates(true); // Silent mode
51
+
52
+ if (!updateInfo) {
53
+ return null;
54
+ }
55
+
56
+ const updates = [];
57
+
58
+ if (updateInfo.cli.status === 'outdated') {
59
+ updates.push({
60
+ name: 'slicejs-cli',
61
+ displayName: 'Slice.js CLI',
62
+ current: updateInfo.cli.current,
63
+ latest: updateInfo.cli.latest,
64
+ type: 'cli'
65
+ });
66
+ }
67
+
68
+ if (updateInfo.framework.status === 'outdated') {
69
+ updates.push({
70
+ name: 'slicejs-web-framework',
71
+ displayName: 'Slice.js Framework',
72
+ current: updateInfo.framework.current,
73
+ latest: updateInfo.framework.latest,
74
+ type: 'framework'
75
+ });
76
+ }
77
+
78
+ return {
79
+ hasUpdates: updates.length > 0,
80
+ updates,
81
+ allCurrent: updateInfo.cli.status === 'current' && updateInfo.framework.status === 'current'
82
+ };
83
+ } catch (error) {
84
+ Print.error(`Checking for updates: ${error.message}`);
85
+ return null;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Display available updates in a formatted way
91
+ */
92
+ displayUpdates(updateInfo) {
93
+ if (!updateInfo || !updateInfo.hasUpdates) {
94
+ return;
95
+ }
96
+
97
+ console.log('');
98
+ Print.warning('📦 Actualizaciones Disponibles:');
99
+ console.log('');
100
+
101
+ updateInfo.updates.forEach(pkg => {
102
+ console.log(` ${pkg.type === 'cli' ? '🔧' : '⚡'} ${pkg.displayName}`);
103
+ console.log(` ${pkg.current} → ${pkg.latest}`);
104
+ });
105
+
106
+ console.log('');
107
+ console.log(' 📚 Changelog: https://github.com/VKneider/slice.js/releases');
108
+ console.log('');
109
+ }
110
+
111
+ /**
112
+ * Prompt user to select which packages to update
113
+ */
114
+ async promptForUpdates(updateInfo, options = {}) {
115
+ if (!updateInfo || !updateInfo.hasUpdates) {
116
+ return [];
117
+ }
118
+
119
+ // If --yes flag is set, return all updates
120
+ if (options.yes) {
121
+ return updateInfo.updates.map(pkg => pkg.name);
122
+ }
123
+
124
+ // If specific package flags are set
125
+ if (options.cli || options.framework) {
126
+ const selected = [];
127
+ if (options.cli) {
128
+ const cliUpdate = updateInfo.updates.find(pkg => pkg.type === 'cli');
129
+ if (cliUpdate) selected.push(cliUpdate.name);
130
+ }
131
+ if (options.framework) {
132
+ const frameworkUpdate = updateInfo.updates.find(pkg => pkg.type === 'framework');
133
+ if (frameworkUpdate) selected.push(frameworkUpdate.name);
134
+ }
135
+ return selected;
136
+ }
137
+
138
+ // Interactive selection
139
+ const choices = updateInfo.updates.map(pkg => ({
140
+ name: `${pkg.displayName} (${pkg.current} → ${pkg.latest})`,
141
+ value: pkg.name,
142
+ checked: true
143
+ }));
144
+
145
+ const answers = await inquirer.prompt([
146
+ {
147
+ type: 'checkbox',
148
+ name: 'packages',
149
+ message: '¿Qué paquetes deseas actualizar?',
150
+ choices,
151
+ validate: (answer) => {
152
+ if (answer.length === 0) {
153
+ return 'Debes seleccionar al menos un paquete';
154
+ }
155
+ return true;
156
+ }
157
+ }
158
+ ]);
159
+
160
+ return answers.packages;
161
+ }
162
+
163
+ async buildUpdatePlan(packages) {
164
+ const plan = [];
165
+ const info = await this.detectCliInstall();
166
+ for (const pkg of packages) {
167
+ if (pkg === 'slicejs-cli') {
168
+ if (info.type === 'global') {
169
+ plan.push({ package: pkg, target: 'global', command: 'npm install -g slicejs-cli@latest' });
170
+ } else {
171
+ plan.push({ package: pkg, target: 'project', command: 'npm install slicejs-cli@latest' });
172
+ }
173
+ } else if (pkg === 'slicejs-web-framework') {
174
+ plan.push({ package: pkg, target: 'project', command: 'npm install slicejs-web-framework@latest' });
175
+ } else {
176
+ plan.push({ package: pkg, target: 'project', command: `npm install ${pkg}@latest` });
177
+ }
178
+ }
179
+ return plan;
180
+ }
181
+
182
+ /**
183
+ * Execute npm update command for a specific package
184
+ */
185
+ async updatePackage(packageName) {
186
+ try {
187
+ let command = `npm install ${packageName}@latest`;
188
+ let options = {};
189
+
190
+ if (packageName === 'slicejs-cli') {
191
+ const info = await this.detectCliInstall();
192
+ if (info.type === 'global') {
193
+ command = `npm install -g slicejs-cli@latest`;
194
+ } else {
195
+ options.cwd = info.projectRoot || getProjectRoot(import.meta.url);
196
+ }
197
+ } else {
198
+ options.cwd = getProjectRoot(import.meta.url);
199
+ }
200
+
201
+ const { stdout, stderr } = await execAsync(command, options);
202
+
203
+ return {
204
+ success: true,
205
+ packageName,
206
+ stdout,
207
+ stderr
208
+ };
209
+ } catch (error) {
210
+ return {
211
+ success: false,
212
+ packageName,
213
+ error: error.message
214
+ };
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Update multiple packages with progress indication
220
+ */
221
+ async installUpdates(packages) {
222
+ const results = [];
223
+
224
+ for (const packageName of packages) {
225
+ const spinner = ora(`Actualizando ${packageName}...`).start();
226
+
227
+ try {
228
+ const result = await this.updatePackage(packageName);
229
+
230
+ if (result.success) {
231
+ spinner.succeed(`${packageName} actualizado exitosamente`);
232
+ results.push({ packageName, success: true });
233
+ } else {
234
+ spinner.fail(`Error actualizando ${packageName}`);
235
+ Print.error(`Detalles: ${result.error}`);
236
+ results.push({ packageName, success: false, error: result.error });
237
+ }
238
+ } catch (error) {
239
+ spinner.fail(`Error actualizando ${packageName}`);
240
+ Print.error(`Detalles: ${error.message}`);
241
+ results.push({ packageName, success: false, error: error.message });
242
+ }
243
+ }
244
+
245
+ return results;
246
+ }
247
+
248
+ /**
249
+ * Main method to check and prompt for updates
250
+ */
251
+ async checkAndPromptUpdates(options = {}) {
252
+ const spinner = ora('Verificando actualizaciones...').start();
253
+
254
+ try {
255
+ const updateInfo = await this.checkForUpdates();
256
+ spinner.stop();
257
+
258
+ if (!updateInfo) {
259
+ Print.error('No se pudo verificar actualizaciones. Verifica tu conexión a internet.');
260
+ return false;
261
+ }
262
+
263
+ if (updateInfo.allCurrent) {
264
+ Print.success('✅ Todos los componentes están actualizados!');
265
+ return true;
266
+ }
267
+
268
+ if (!updateInfo.hasUpdates) {
269
+ Print.success('✅ Todos los componentes están actualizados!');
270
+ return true;
271
+ }
272
+
273
+ // Display available updates
274
+ this.displayUpdates(updateInfo);
275
+
276
+ // Get packages to update
277
+ const packagesToUpdate = await this.promptForUpdates(updateInfo, options);
278
+
279
+ if (!packagesToUpdate || packagesToUpdate.length === 0) {
280
+ Print.info('No se seleccionaron paquetes para actualizar.');
281
+ return false;
282
+ }
283
+
284
+ // Show plan and confirm installation if not auto-confirmed
285
+ let plan = await this.buildUpdatePlan(packagesToUpdate);
286
+ console.log('');
287
+ Print.info('🧭 Plan de actualización:');
288
+ plan.forEach(item => {
289
+ const where = item.target === 'global' ? 'GLOBAL' : 'PROYECTO';
290
+ console.log(` • ${item.package} → ${where}`);
291
+ console.log(` ${item.command}`);
292
+ });
293
+ console.log('');
294
+
295
+ const cliInfo = await this.detectCliInstall();
296
+ if (cliInfo.type === 'global' && !packagesToUpdate.includes('slicejs-cli')) {
297
+ if (!options.yes && !options.cli) {
298
+ const { addCli } = await inquirer.prompt([
299
+ {
300
+ type: 'confirm',
301
+ name: 'addCli',
302
+ message: 'Se detectó CLI global. ¿Agregar la actualización global del CLI al plan?',
303
+ default: true
304
+ }
305
+ ]);
306
+ if (addCli) {
307
+ packagesToUpdate.push('slicejs-cli');
308
+ plan = await this.buildUpdatePlan(packagesToUpdate);
309
+ console.log('');
310
+ Print.info('🧭 Plan actualizado:');
311
+ plan.forEach(item => {
312
+ const where = item.target === 'global' ? 'GLOBAL' : 'PROYECTO';
313
+ console.log(` • ${item.package} → ${where}`);
314
+ console.log(` ${item.command}`);
315
+ });
316
+ console.log('');
317
+ }
318
+ } else {
319
+ Print.warning('CLI global detectado. Se recomienda actualizar slicejs-cli global para mantener alineado con el framework.');
320
+ console.log(' Sugerencia: npm install -g slicejs-cli@latest');
321
+ console.log('');
322
+ }
323
+ }
324
+
325
+ if (!options.yes && !options.cli && !options.framework) {
326
+ const { confirm } = await inquirer.prompt([
327
+ {
328
+ type: 'confirm',
329
+ name: 'confirm',
330
+ message: '¿Deseas continuar con la actualización según el plan mostrado?',
331
+ default: true
332
+ }
333
+ ]);
334
+
335
+ if (!confirm) {
336
+ Print.info('Actualización cancelada.');
337
+ return false;
338
+ }
339
+ }
340
+
341
+ console.log(''); // Line break
342
+ Print.info('📥 Instalando actualizaciones...');
343
+ console.log('');
344
+
345
+ // Install updates
346
+ const results = await this.installUpdates(packagesToUpdate);
347
+
348
+ // Summary
349
+ console.log('');
350
+ const successCount = results.filter(r => r.success).length;
351
+ const failCount = results.filter(r => !r.success).length;
352
+
353
+ if (failCount === 0) {
354
+ Print.success(`✅ ${successCount} paquete(s) actualizado(s) exitosamente!`);
355
+ } else {
356
+ Print.warning(`⚠️ ${successCount} exitoso(s), ${failCount} fallido(s)`);
357
+ }
358
+
359
+ if (successCount > 0) {
360
+ console.log('');
361
+ Print.info('💡 Se recomienda reiniciar el servidor de desarrollo si está ejecutándose.');
362
+ }
363
+
364
+ return failCount === 0;
365
+
366
+ } catch (error) {
367
+ spinner.stop();
368
+ Print.error(`Error durante la actualización: ${error.message}`);
369
+ return false;
370
+ }
371
+ }
372
+ }
373
+
374
+ // Singleton instance
375
+ const updateManager = new UpdateManager();
376
+
377
+ export default updateManager;