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.
package/README.md CHANGED
@@ -110,8 +110,11 @@ slice registry sync → slice sync
110
110
  slice version
111
111
  slice -v
112
112
 
113
- # Check for available updates
114
- slice update
113
+ # Update CLI and Framework
114
+ slice update # Check and prompt for updates
115
+ slice update --yes # Update all automatically
116
+ slice update --cli # Update only CLI
117
+ slice update --framework # Update only Framework
115
118
 
116
119
  # Show help
117
120
  slice --help
package/client.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import { program } from "commander";
3
3
  import inquirer from "inquirer";
4
4
  import initializeProject from "./commands/init/init.js";
@@ -8,10 +8,12 @@ import deleteComponent from "./commands/deleteComponent/deleteComponent.js";
8
8
  import getComponent, { listComponents as listRemoteComponents, syncComponents } from "./commands/getComponent/getComponent.js";
9
9
  import startServer from "./commands/startServer/startServer.js";
10
10
  import runDiagnostics from "./commands/doctor/doctor.js";
11
- import versionChecker from "./commands/utils/versionChecker.js";
11
+ import versionChecker from "./commands/utils/VersionChecker.js";
12
+ import updateManager from "./commands/utils/updateManager.js";
12
13
  import fs from "fs";
13
14
  import path from "path";
14
15
  import { fileURLToPath } from "url";
16
+ import { getConfigPath } from "./commands/utils/PathHelper.js";
15
17
  import validations from "./commands/Validations.js";
16
18
  import Print from "./commands/Print.js";
17
19
 
@@ -19,7 +21,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
21
 
20
22
  const loadConfig = () => {
21
23
  try {
22
- const configPath = path.join(__dirname, "../../src/sliceConfig.json");
24
+ const configPath = getConfigPath(import.meta.url);
23
25
  const rawData = fs.readFileSync(configPath, "utf-8");
24
26
  return JSON.parse(rawData);
25
27
  } catch (error) {
@@ -36,10 +38,13 @@ const getCategories = () => {
36
38
  // Function to run version check for all commands
37
39
  async function runWithVersionCheck(commandFunction, ...args) {
38
40
  try {
39
- // Run the command first
41
+ const updateInfo = await updateManager.checkForUpdates();
42
+ if (updateInfo && updateInfo.hasUpdates) {
43
+ await updateManager.checkAndPromptUpdates({});
44
+ }
45
+
40
46
  const result = await commandFunction(...args);
41
47
 
42
- // Then check for updates (non-blocking)
43
48
  setTimeout(() => {
44
49
  versionChecker.checkForUpdates(false);
45
50
  }, 100);
@@ -53,7 +58,7 @@ async function runWithVersionCheck(commandFunction, ...args) {
53
58
 
54
59
  const sliceClient = program;
55
60
 
56
- sliceClient.version("2.5.0").description("CLI for managing Slice.js framework components");
61
+ sliceClient.version("2.6.1").description("CLI for managing Slice.js framework components");
57
62
 
58
63
  // INIT COMMAND
59
64
  sliceClient
@@ -336,29 +341,27 @@ sliceClient
336
341
  });
337
342
  });
338
343
 
344
+ // LIST COMMAND - Quick shortcut for listing local components
345
+ sliceClient
346
+ .command("list")
347
+ .description("Quick list all local components (alias for component list)")
348
+ .action(async () => {
349
+ await runWithVersionCheck(() => {
350
+ listComponents();
351
+ return Promise.resolve();
352
+ });
353
+ });
354
+
339
355
  // UPDATE COMMAND
340
356
  sliceClient
341
357
  .command("update")
342
358
  .alias("upgrade")
343
- .description("Check for and show available updates for CLI and framework")
344
- .action(async () => {
345
- Print.info("Checking for updates...");
346
-
347
- try {
348
- const updateInfo = await versionChecker.checkForUpdates(false);
349
-
350
- if (updateInfo) {
351
- if (updateInfo.cli.status === 'current' && updateInfo.framework.status === 'current') {
352
- Print.success("All components are up to date!");
353
- } else {
354
- Print.info("Updates available - see details above");
355
- }
356
- } else {
357
- Print.error("Could not check for updates. Please check your internet connection");
358
- }
359
- } catch (error) {
360
- Print.error(`Checking updates: ${error.message}`);
361
- }
359
+ .description("Update CLI and framework to latest versions")
360
+ .option("-y, --yes", "Skip confirmation and update all packages automatically")
361
+ .option("--cli", "Update only the Slice.js CLI")
362
+ .option("-f, --framework", "Update only the Slice.js Framework")
363
+ .action(async (options) => {
364
+ await updateManager.checkAndPromptUpdates(options);
362
365
  });
363
366
 
364
367
  // DOCTOR COMMAND - Diagnose project issues
@@ -391,11 +394,12 @@ Common Usage Examples:
391
394
  slice browse - Browse all available components
392
395
  slice sync - Update local components to latest versions
393
396
  slice component create - Create new local component
397
+ slice list - List all local components
394
398
  slice doctor - Run project diagnostics
395
399
 
396
400
  Command Categories:
397
401
  • init, dev, start - Project lifecycle (development only)
398
- • get, browse, sync - Quick registry shortcuts
402
+ • get, browse, sync, list - Quick shortcuts
399
403
  • component <cmd> - Local component management
400
404
  • registry <cmd> - Official repository operations
401
405
  • version, update, doctor - Maintenance commands
@@ -431,4 +435,4 @@ const helpCommand = sliceClient.command("help").description("Display help inform
431
435
  sliceClient.outputHelp();
432
436
  });
433
437
 
434
- program.parse();
438
+ program.parse();
@@ -1,14 +1,28 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import { getConfigPath, getComponentsJsPath } from './utils/PathHelper.js';
4
5
 
5
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
7
 
7
8
  class Validations {
8
9
  constructor() {
9
- this.config = this.loadConfig(); // Cargamos la configuración solo una vez al instanciar
10
- this.categories = this.config?.paths?.components;
10
+ this._config = null;
11
+ this._categories = null;
12
+ }
13
+
14
+ _ensureConfig() {
15
+ if (!this._config) {
16
+ this._config = this.loadConfig();
17
+ if (this._config) {
18
+ this._categories = this._config.paths?.components;
19
+ }
20
+ }
21
+ }
11
22
 
23
+ get config() {
24
+ this._ensureConfig();
25
+ return this._config;
12
26
  }
13
27
 
14
28
  isValidComponentName(componentName) {
@@ -19,36 +33,40 @@ class Validations {
19
33
 
20
34
  loadConfig() {
21
35
  try {
22
- const configPath = path.join(__dirname, '../../../src/sliceConfig.json');
36
+ const configPath = getConfigPath(import.meta.url);
23
37
  if (!fs.existsSync(configPath)) {
24
- console.error('\x1b[31m', '❌ Error: sliceConfig.json not found', '\x1b[0m');
25
- console.log('\x1b[36m', 'ℹ️ Info: Run "slice init" to initialize your project', '\x1b[0m');
38
+ // Return null silently - let commands handle missing config if needed
26
39
  return null;
27
- }
40
+ }
28
41
  const rawData = fs.readFileSync(configPath, 'utf-8');
29
-
42
+
30
43
  return JSON.parse(rawData);
31
44
  } catch (error) {
32
45
  console.error('\x1b[31m', `❌ Error loading configuration: ${error.message}`, '\x1b[0m');
33
- console.log('\x1b[36m', 'ℹ️ Info: Check that sliceConfig.json is valid JSON', '\x1b[0m');
34
46
  return null;
35
47
  }
36
48
  }
37
49
 
38
50
  getCategories() {
39
- return this.categories; // Usamos las categorías cargadas en el constructor
51
+ this._ensureConfig();
52
+ return this._categories;
40
53
  }
41
54
 
42
55
  getCategoryPath(category) {
43
- return this.categories[category].path;
56
+ this._ensureConfig();
57
+ return this._categories && this._categories[category] ? this._categories[category].path : null;
44
58
  }
45
59
 
46
- getCategoryType(category){
47
- return this.categories[category].type;
60
+ getCategoryType(category) {
61
+ this._ensureConfig();
62
+ return this._categories && this._categories[category] ? this._categories[category].type : null;
48
63
  }
49
64
 
50
65
  isValidCategory(category) {
51
- const availableCategories = Object.keys(this.categories).map(cat => cat.toLowerCase());
66
+ this._ensureConfig();
67
+ if (!this._categories) return { isValid: false, category: null };
68
+
69
+ const availableCategories = Object.keys(this._categories).map(cat => cat.toLowerCase());
52
70
 
53
71
  if (availableCategories.includes(category.toLowerCase())) {
54
72
  return { isValid: true, category };
@@ -59,17 +77,17 @@ class Validations {
59
77
 
60
78
  componentExists(componentName) {
61
79
  try {
62
- const componentFilePath = path.join(__dirname, '../../../src/Components/components.js');
63
-
80
+ const componentFilePath = getComponentsJsPath(import.meta.url);
81
+
64
82
  if (!fs.existsSync(componentFilePath)) {
65
83
  console.error('\x1b[31m', '❌ Error: components.js not found in expected path', '\x1b[0m');
66
84
  console.log('\x1b[36m', 'ℹ️ Info: Run "slice component list" to generate components.js', '\x1b[0m');
67
85
  return false;
68
86
  }
69
-
87
+
70
88
  const fileContent = fs.readFileSync(componentFilePath, 'utf-8');
71
89
  const components = eval(fileContent.replace('export default', '')); // Evalúa el contenido como objeto
72
-
90
+
73
91
  return components.hasOwnProperty(componentName);
74
92
 
75
93
  } catch (error) {
@@ -82,4 +100,4 @@ class Validations {
82
100
 
83
101
  const validations = new Validations();
84
102
 
85
- export default validations;
103
+ export default validations;
@@ -7,6 +7,7 @@ import { minify as terserMinify } from 'terser';
7
7
  import { minify } from 'html-minifier-terser';
8
8
  import CleanCSS from 'clean-css';
9
9
  import Print from '../Print.js';
10
+ import { getSrcPath, getDistPath, getConfigPath } 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) {
@@ -28,7 +29,7 @@ const loadConfig = () => {
28
29
  * Verifica dependencias necesarias para el build
29
30
  */
30
31
  async function checkBuildDependencies() {
31
- const srcDir = path.join(__dirname, '../../../../src');
32
+ const srcDir = getSrcPath(import.meta.url);
32
33
 
33
34
  if (!await fs.pathExists(srcDir)) {
34
35
  Print.error('Source directory (/src) not found');
@@ -103,8 +104,8 @@ async function verifyBuildIntegrity(distDir) {
103
104
  * Copia sliceConfig.json al directorio dist
104
105
  */
105
106
  async function copySliceConfig() {
106
- const srcConfig = path.join(__dirname, '../../../../src/sliceConfig.json');
107
- const distConfig = path.join(__dirname, '../../../../dist/sliceConfig.json');
107
+ const srcConfig = getConfigPath(import.meta.url);
108
+ const distConfig = getDistPath(import.meta.url, 'sliceConfig.json');
108
109
 
109
110
  if (await fs.pathExists(srcConfig)) {
110
111
  await fs.copy(srcConfig, distConfig);
@@ -336,7 +337,7 @@ async function minifyHTML(srcPath, distPath) {
336
337
  async function createOptimizedBundle() {
337
338
  Print.buildProgress('Creating optimized bundle...');
338
339
 
339
- const mainJSPath = path.join(__dirname, '../../../../dist/App/index.js');
340
+ const mainJSPath = getDistPath(import.meta.url, 'App', 'index.js');
340
341
 
341
342
  if (await fs.pathExists(mainJSPath)) {
342
343
  Print.success('Main bundle optimized');
@@ -389,7 +390,7 @@ async function generateBuildStats(srcDir, distDir) {
389
390
  * Analiza el build sin construir
390
391
  */
391
392
  async function analyzeBuild() {
392
- const distDir = path.join(__dirname, '../../../../dist');
393
+ const distDir = getDistPath(import.meta.url);
393
394
 
394
395
  if (!await fs.pathExists(distDir)) {
395
396
  Print.error('No build found to analyze. Run "slice build" first.');
@@ -398,7 +399,7 @@ async function analyzeBuild() {
398
399
 
399
400
  Print.info('Analyzing production build...');
400
401
  await generateBuildStats(
401
- path.join(__dirname, '../../../../src'),
402
+ getSrcPath(import.meta.url),
402
403
  distDir
403
404
  );
404
405
  }
@@ -413,8 +414,8 @@ export default async function buildProduction(options = {}) {
413
414
  Print.title('🔨 Building Slice.js project for production...');
414
415
  Print.newLine();
415
416
 
416
- const srcDir = path.join(__dirname, '../../../../src');
417
- const distDir = path.join(__dirname, '../../../../dist');
417
+ const srcDir = getSrcPath(import.meta.url);
418
+ const distDir = getDistPath(import.meta.url);
418
419
 
419
420
  if (!await fs.pathExists(srcDir)) {
420
421
  throw new Error('Source directory not found. Run "slice init" first.');
@@ -470,7 +471,7 @@ export async function serveProductionBuild(port) {
470
471
  const defaultPort = config?.server?.port || 3001;
471
472
  const finalPort = port || defaultPort;
472
473
 
473
- const distDir = path.join(__dirname, '../../../../dist');
474
+ const distDir = getDistPath(import.meta.url);
474
475
 
475
476
  if (!await fs.pathExists(distDir)) {
476
477
  throw new Error('No production build found. Run "slice build" first.');
@@ -540,4 +541,4 @@ export async function buildCommand(options = {}) {
540
541
  }
541
542
 
542
543
  return success;
543
- }
544
+ }
@@ -5,7 +5,8 @@ import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import Validations from '../Validations.js';
7
7
  import Print from '../Print.js';
8
- const __dirname = path.dirname(new URL(import.meta.url).pathname);
8
+ import { getSrcPath } from '../utils/PathHelper.js';
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
10
 
10
11
  function createComponent(componentName, category) {
11
12
  // Validación: Nombre de componente requerido
@@ -62,10 +63,8 @@ function createComponent(componentName, category) {
62
63
  }
63
64
 
64
65
  const categoryPath = Validations.getCategoryPath(category);
65
-
66
- // Determinar la ruta del directorio del componente
67
- let componentDir = path.join(__dirname, '../../../../src/', categoryPath, className);
68
- componentDir = componentDir.slice(1);
66
+ const categoryPathClean = categoryPath ? categoryPath.replace(/^[/\\]+/, '') : '';
67
+ const componentDir = getSrcPath(import.meta.url, categoryPathClean, className);
69
68
 
70
69
  try {
71
70
  // Crear directorio del componente
@@ -2,7 +2,9 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import Validations from '../Validations.js';
4
4
  import Print from '../Print.js';
5
- const __dirname = path.dirname(new URL(import.meta.url).pathname);
5
+ import { fileURLToPath } from 'url';
6
+ import { getSrcPath } from '../utils/PathHelper.js';
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
8
 
7
9
  function deleteComponent(componentName, category) {
8
10
  // Validación: Nombre de componente requerido
@@ -31,10 +33,8 @@ function deleteComponent(componentName, category) {
31
33
  category = flagCategory.category;
32
34
 
33
35
  const categoryPath = Validations.getCategoryPath(category);
34
-
35
- // Construir la ruta del directorio del componente
36
- let componentDir = path.join(__dirname, '../../../../src/', categoryPath, componentName);
37
- componentDir = componentDir.slice(1);
36
+ const categoryPathClean = categoryPath ? categoryPath.replace(/^[/\\]+/, '') : '';
37
+ const componentDir = getSrcPath(import.meta.url, categoryPathClean, componentName);
38
38
 
39
39
  // Verificar si el directorio del componente existe
40
40
  if (!fs.existsSync(componentDir)) {
@@ -5,6 +5,7 @@ import { createServer } from 'net';
5
5
  import chalk from 'chalk';
6
6
  import Table from 'cli-table3';
7
7
  import Print from '../Print.js';
8
+ import { getProjectRoot, getSrcPath, getApiPath, getConfigPath, getPath } from '../utils/PathHelper.js';
8
9
 
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
11
 
@@ -34,9 +35,8 @@ async function checkNodeVersion() {
34
35
  * Verifica la estructura de directorios
35
36
  */
36
37
  async function checkDirectoryStructure() {
37
- const projectRoot = path.join(__dirname, '../../../../');
38
- const srcPath = path.join(projectRoot, 'src');
39
- const apiPath = path.join(projectRoot, 'api');
38
+ const srcPath = getSrcPath(import.meta.url);
39
+ const apiPath = getApiPath(import.meta.url);
40
40
 
41
41
  const srcExists = await fs.pathExists(srcPath);
42
42
  const apiExists = await fs.pathExists(apiPath);
@@ -63,7 +63,7 @@ async function checkDirectoryStructure() {
63
63
  * Verifica sliceConfig.json
64
64
  */
65
65
  async function checkConfig() {
66
- const configPath = path.join(__dirname, '../../../../src/sliceConfig.json');
66
+ const configPath = getConfigPath(import.meta.url);
67
67
 
68
68
  if (!await fs.pathExists(configPath)) {
69
69
  return {
@@ -101,7 +101,7 @@ async function checkConfig() {
101
101
  * Verifica disponibilidad del puerto
102
102
  */
103
103
  async function checkPort() {
104
- const configPath = path.join(__dirname, '../../../../src/sliceConfig.json');
104
+ const configPath = getConfigPath(import.meta.url);
105
105
  let port = 3000;
106
106
 
107
107
  try {
@@ -145,7 +145,7 @@ async function checkPort() {
145
145
  * Verifica dependencias en package.json
146
146
  */
147
147
  async function checkDependencies() {
148
- const packagePath = path.join(__dirname, '../../../../package.json');
148
+ const packagePath = getPath(import.meta.url, '', 'package.json');
149
149
 
150
150
  if (!await fs.pathExists(packagePath)) {
151
151
  return {
@@ -189,8 +189,8 @@ async function checkDependencies() {
189
189
  * Verifica integridad de componentes
190
190
  */
191
191
  async function checkComponents() {
192
- const configPath = path.join(__dirname, '../../../../src/sliceConfig.json');
193
- const projectRoot = path.join(__dirname, '../../../../');
192
+ const configPath = getConfigPath(import.meta.url);
193
+ const projectRoot = getProjectRoot(import.meta.url);
194
194
 
195
195
  if (!await fs.pathExists(configPath)) {
196
196
  return {
@@ -208,7 +208,7 @@ async function checkComponents() {
208
208
  let componentIssues = 0;
209
209
 
210
210
  for (const [category, { path: compPath }] of Object.entries(componentPaths)) {
211
- const fullPath = path.join(projectRoot, 'src', compPath);
211
+ const fullPath = getSrcPath(import.meta.url, compPath);
212
212
 
213
213
  if (await fs.pathExists(fullPath)) {
214
214
  const items = await fs.readdir(fullPath);
@@ -6,6 +6,7 @@ import { fileURLToPath } from "url";
6
6
  import inquirer from "inquirer";
7
7
  import validations from "../Validations.js";
8
8
  import Print from "../Print.js";
9
+ import { getConfigPath, getComponentsJsPath, getPath } from "../utils/PathHelper.js";
9
10
 
10
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
12
 
@@ -19,8 +20,7 @@ const COMPONENTS_REGISTRY_URL = 'https://raw.githubusercontent.com/VKneider/slic
19
20
  */
20
21
  const loadConfig = () => {
21
22
  try {
22
- // CORREGIDO: Usar 4 niveles como en listComponents para compatibilidad con node_modules
23
- const configPath = path.join(__dirname, '../../../../src/sliceConfig.json');
23
+ const configPath = getConfigPath(import.meta.url);
24
24
  if (!fs.existsSync(configPath)) {
25
25
  throw new Error('sliceConfig.json not found in src folder');
26
26
  }
@@ -98,8 +98,7 @@ filterOfficialComponents(allComponents) {
98
98
 
99
99
  async getLocalComponents() {
100
100
  try {
101
- // CORREGIDO: Usar 4 niveles como en listComponents para compatibilidad con node_modules
102
- const componentsPath = path.join(__dirname, '../../../../src/Components/components.js');
101
+ const componentsPath = getComponentsJsPath(import.meta.url);
103
102
 
104
103
  if (!await fs.pathExists(componentsPath)) {
105
104
  return {};
@@ -132,8 +131,7 @@ filterOfficialComponents(allComponents) {
132
131
  // ✅ CORREGIDO: Usar 4 niveles para compatibilidad con node_modules
133
132
  const isProduction = this.config?.production?.enabled === true;
134
133
  const folderSuffix = isProduction ? 'dist' : 'src';
135
-
136
- const componentPath = path.join(__dirname, `../../../../${folderSuffix}`, categoryPath, name);
134
+ const componentPath = getPath(import.meta.url, folderSuffix, categoryPath, name);
137
135
 
138
136
  if (fs.pathExistsSync(componentPath)) {
139
137
  updatableComponents.push({
@@ -180,43 +178,6 @@ filterOfficialComponents(allComponents) {
180
178
  return components;
181
179
  }
182
180
 
183
- displayAvailableComponents() {
184
- if (!this.componentsRegistry) {
185
- Print.error('❌ No se pudo cargar el registro de componentes');
186
- return;
187
- }
188
-
189
- console.log('\n📚 Componentes disponibles en el repositorio oficial de Slice.js:\n');
190
-
191
- const visualComponents = this.getAvailableComponents('Visual');
192
- const serviceComponents = this.getAvailableComponents('Service');
193
-
194
- // ✅ SIMPLIFICADO: Solo mostrar nombres sin descripciones
195
- Print.info('🎨 Visual Components (UI):');
196
- Object.keys(visualComponents).forEach(name => {
197
- const files = visualComponents[name].files;
198
- const fileIcons = files.map(file => {
199
- if (file.endsWith('.js')) return '📜';
200
- if (file.endsWith('.html')) return '🌐';
201
- if (file.endsWith('.css')) return '🎨';
202
- return '📄';
203
- }).join(' ');
204
- console.log(` • ${name} ${fileIcons}`);
205
- });
206
-
207
- Print.info('\n⚙️ Service Components (Logic):');
208
- Object.keys(serviceComponents).forEach(name => {
209
- console.log(` • ${name} 📜`);
210
- });
211
-
212
- Print.newLine();
213
- Print.info(`Total: ${Object.keys(visualComponents).length} Visual + ${Object.keys(serviceComponents).length} Service components`);
214
-
215
- console.log(`\n💡 Ejemplos de uso:`);
216
- console.log(`slice get Button Card Input # Obtener componentes Visual`);
217
- console.log(`slice get FetchManager --service # Obtener componente Service`);
218
- console.log(`slice sync # Sincronizar componentes Visual`);
219
- }
220
181
 
221
182
  async downloadComponentFiles(componentName, category, targetPath) {
222
183
  const component = this.getAvailableComponents(category)[componentName];
@@ -270,6 +231,36 @@ filterOfficialComponents(allComponents) {
270
231
  return downloadedFiles;
271
232
  }
272
233
 
234
+ async updateLocalRegistrySafe(componentName, category) {
235
+ const componentsPath = path.join(__dirname, '../../../../src/Components/components.js');
236
+ try {
237
+ if (!await fs.pathExists(componentsPath)) {
238
+ const dir = path.dirname(componentsPath);
239
+ await fs.ensureDir(dir);
240
+ const initial = `const components = {};\n\nexport default components;\n`;
241
+ await fs.writeFile(componentsPath, initial, 'utf8');
242
+ }
243
+ const content = await fs.readFile(componentsPath, 'utf8');
244
+ const match = content.match(/const components = ({[\s\S]*?});/);
245
+ if (!match) throw new Error('Invalid components.js format in local project');
246
+ const componentsObj = eval('(' + match[1] + ')');
247
+ if (!componentsObj[componentName]) {
248
+ componentsObj[componentName] = category;
249
+ const sorted = Object.keys(componentsObj)
250
+ .sort()
251
+ .reduce((obj, key) => { obj[key] = componentsObj[key]; return obj; }, {});
252
+ const newContent = `const components = ${JSON.stringify(sorted, null, 2)};\n\nexport default components;\n`;
253
+ await fs.writeFile(componentsPath, newContent, 'utf8');
254
+ Print.registryUpdate(`Registered ${componentName} in local components.js`);
255
+ } else {
256
+ Print.info(`${componentName} already exists in local registry`);
257
+ }
258
+ } catch (error) {
259
+ Print.error(`Updating local components.js: ${error.message}`);
260
+ throw error;
261
+ }
262
+ }
263
+
273
264
  async updateLocalRegistry(componentName, category) {
274
265
  // ✅ CORREGIDO: Usar 4 niveles para compatibilidad con node_modules
275
266
  const componentsPath = path.join(__dirname, '../../../../src/Components/components.js');
@@ -331,14 +322,12 @@ filterOfficialComponents(allComponents) {
331
322
  validations.config.paths.components[category];
332
323
 
333
324
  if (hasValidConfig) {
334
- // Usar validations cuando la config está disponible
335
325
  categoryPath = validations.getCategoryPath(category);
336
326
  } else {
337
- // Usar rutas por defecto cuando no hay config (durante init o error)
338
327
  if (category === 'Visual') {
339
- categoryPath = '/Components/Visual';
328
+ categoryPath = 'Components/Visual';
340
329
  } else if (category === 'Service') {
341
- categoryPath = '/Components/Service';
330
+ categoryPath = 'Components/Service';
342
331
  } else {
343
332
  throw new Error(`Unknown category: ${category}`);
344
333
  }
@@ -347,7 +336,8 @@ filterOfficialComponents(allComponents) {
347
336
  const isProduction = this.config?.production?.enabled === true;
348
337
  const folderSuffix = isProduction ? 'dist' : 'src';
349
338
 
350
- const targetPath = path.join(__dirname, `../../../../${folderSuffix}`, categoryPath, componentName);
339
+ const cleanCategoryPath = categoryPath ? categoryPath.replace(/^[/\\]+/, '') : '';
340
+ const targetPath = getPath(import.meta.url, folderSuffix, cleanCategoryPath, componentName);
351
341
 
352
342
 
353
343
 
@@ -375,8 +365,7 @@ filterOfficialComponents(allComponents) {
375
365
  // Download component files
376
366
  const downloadedFiles = await this.downloadComponentFiles(componentName, category, targetPath);
377
367
 
378
- // Update components registry
379
- await this.updateLocalRegistry(componentName, category);
368
+ await this.updateLocalRegistrySafe(componentName, category);
380
369
 
381
370
  Print.success(`${componentName} installed successfully from official repository!`);
382
371
  console.log(`📁 Location: ${folderSuffix}/${categoryPath}/${componentName}/`);
@@ -717,4 +706,4 @@ async function syncComponents(options = {}) {
717
706
  }
718
707
 
719
708
  export default getComponents;
720
- export { listComponents, syncComponents, ComponentRegistry };
709
+ export { listComponents, syncComponents, ComponentRegistry };