slicejs-cli 3.4.0 → 3.4.1

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 (35) hide show
  1. package/AGENTS.md +247 -0
  2. package/client.js +63 -64
  3. package/commands/Print.js +11 -15
  4. package/commands/Validations.js +12 -23
  5. package/commands/buildProduction/buildProduction.js +23 -26
  6. package/commands/bundle/bundle.js +10 -11
  7. package/commands/createComponent/createComponent.js +14 -16
  8. package/commands/deleteComponent/deleteComponent.js +6 -6
  9. package/commands/doctor/doctor.js +11 -14
  10. package/commands/getComponent/getComponent.js +99 -162
  11. package/commands/init/init.js +25 -21
  12. package/commands/listComponents/listComponents.js +18 -21
  13. package/commands/startServer/startServer.js +21 -24
  14. package/commands/startServer/watchServer.js +7 -7
  15. package/commands/types/types.js +53 -18
  16. package/commands/utils/PathHelper.js +9 -2
  17. package/commands/utils/VersionChecker.js +3 -3
  18. package/commands/utils/bundling/DependencyAnalyzer.js +8 -16
  19. package/commands/utils/loadConfig.js +31 -0
  20. package/commands/utils/updateManager.js +3 -4
  21. package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +105 -105
  22. package/package.json +15 -3
  23. package/post.js +2 -2
  24. package/tests/bundle-generator.test.js +3 -20
  25. package/tests/component-registry-parse.test.js +34 -0
  26. package/tests/fixtures/components.js +8 -0
  27. package/tests/fixtures/sliceConfig.json +74 -0
  28. package/tests/getcomponent.test.js +407 -0
  29. package/tests/helpers/setup.js +97 -0
  30. package/tests/init-command-contract.test.js +46 -0
  31. package/tests/local-cli-delegation.test.js +7 -5
  32. package/tests/path-helper.test.js +206 -0
  33. package/tests/types-breakage.test.js +491 -0
  34. package/tests/types-generator-errors.test.js +361 -0
  35. package/tests/types-generator.test.js +172 -184
@@ -1,16 +1,13 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { fileURLToPath } from 'url';
4
3
  import Table from 'cli-table3';
5
4
  import chalk from 'chalk';
6
5
  import Print from '../Print.js';
7
6
  import { getSrcPath, getComponentsJsPath, getConfigPath } from '../utils/PathHelper.js';
8
7
 
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
-
11
8
  /**
12
- * Carga la configuración desde sliceConfig.json
13
- * @returns {object} - Objeto de configuración
9
+ * Loads configuration from sliceConfig.json
10
+ * @returns {object} - Configuration object
14
11
  */
15
12
  const loadConfig = () => {
16
13
  try {
@@ -30,9 +27,9 @@ const loadConfig = () => {
30
27
  };
31
28
 
32
29
  /**
33
- * Lista los archivos en una carpeta dada, filtrando solo los archivos .js
34
- * @param {string} folderPath - Ruta de la carpeta a leer
35
- * @returns {string[]} - Lista de archivos encontrados
30
+ * Lists files in a given folder, filtering only .js files
31
+ * @param {string} folderPath - Path of the folder to read
32
+ * @returns {string[]} - List of found files
36
33
  */
37
34
  const listComponents = (folderPath) => {
38
35
  try {
@@ -48,7 +45,7 @@ const listComponents = (folderPath) => {
48
45
  };
49
46
 
50
47
  /**
51
- * Cuenta archivos en un directorio de componente
48
+ * Counts files in a component directory
52
49
  */
53
50
  const countComponentFiles = (componentPath) => {
54
51
  try {
@@ -61,14 +58,14 @@ const countComponentFiles = (componentPath) => {
61
58
  };
62
59
 
63
60
  /**
64
- * Obtiene los componentes dinámicamente desde sliceConfig.json
65
- * @returns {object} - Mapeo de componentes con su categoría
61
+ * Gets components dynamically from sliceConfig.json
62
+ * @returns {object} - Component mapping with their category
66
63
  */
67
64
  const getComponents = () => {
68
65
  const config = loadConfig();
69
66
  if (!config) return {};
70
67
 
71
- const folderSuffix = 'src'; // Siempre usar 'src' para desarrollo
68
+ const folderSuffix = 'src'; // Always use 'src' for development
72
69
  const componentPaths = config.paths?.components || {};
73
70
  let allComponents = new Map();
74
71
 
@@ -91,7 +88,7 @@ const getComponents = () => {
91
88
 
92
89
  function listComponentsReal() {
93
90
  try {
94
- // Obtener componentes dinámicamente
91
+ // Get components dynamically
95
92
  const components = getComponents();
96
93
 
97
94
  if (Object.keys(components).length === 0) {
@@ -100,7 +97,7 @@ function listComponentsReal() {
100
97
  return;
101
98
  }
102
99
 
103
- // Crear tabla con cli-table3
100
+ // Create table with cli-table3
104
101
  const table = new Table({
105
102
  head: [
106
103
  chalk.cyan.bold('Component'),
@@ -114,7 +111,7 @@ function listComponentsReal() {
114
111
  }
115
112
  });
116
113
 
117
- // Agrupar por categoría para mejor visualización
114
+ // Group by category for better visualization
118
115
  const byCategory = {};
119
116
  Object.entries(components).forEach(([name, data]) => {
120
117
  if (!byCategory[data.category]) {
@@ -123,18 +120,18 @@ function listComponentsReal() {
123
120
  byCategory[data.category].push({ name, files: data.files });
124
121
  });
125
122
 
126
- // Agregar filas a la tabla
123
+ // Add rows to the table
127
124
  Object.entries(byCategory).forEach(([category, comps]) => {
128
125
  comps.forEach((comp, index) => {
129
126
  if (index === 0) {
130
- // Primera fila de la categoría
127
+ // First row of the category
131
128
  table.push([
132
129
  chalk.bold(comp.name),
133
130
  chalk.yellow(category),
134
131
  comp.files.toString()
135
132
  ]);
136
133
  } else {
137
- // Resto de componentes en la categoría
134
+ // Rest of components in the category
138
135
  table.push([
139
136
  chalk.bold(comp.name),
140
137
  chalk.gray('″'), // Ditto mark
@@ -151,16 +148,16 @@ function listComponentsReal() {
151
148
  Print.newLine();
152
149
  Print.info(`Total: ${Object.keys(components).length} component${Object.keys(components).length !== 1 ? 's' : ''} found`);
153
150
 
154
- // Ruta donde se generará components.js
151
+ // Path where components.js will be generated
155
152
  const outputPath = getComponentsJsPath(import.meta.url);
156
153
 
157
- // Asegurar que el directorio existe
154
+ // Ensure the directory exists
158
155
  const outputDir = path.dirname(outputPath);
159
156
  if (!fs.existsSync(outputDir)) {
160
157
  fs.mkdirSync(outputDir, { recursive: true });
161
158
  }
162
159
 
163
- // Generar archivo components.js con los componentes detectados
160
+ // Generate components.js file with detected components
164
161
  const componentsForExport = Object.fromEntries(
165
162
  Object.entries(components).map(([name, data]) => [name, data.category])
166
163
  );
@@ -1,19 +1,16 @@
1
- // commands/startServer/startServer.js - MEJORADO CON VALIDACIÓN Y FEEDBACK
1
+ // commands/startServer/startServer.js - IMPROVED WITH VALIDATION AND FEEDBACK
2
2
 
3
3
  import fs from 'fs-extra';
4
4
  import path from 'path';
5
- import { fileURLToPath } from 'url';
6
5
  import { spawn } from 'child_process';
7
6
  import { createServer } from 'net';
8
7
  import setupWatcher, { stopWatcher } from './watchServer.js';
9
8
  import Print from '../Print.js';
10
- import { getConfigPath, getApiPath, getSrcPath, getDistPath } from '../utils/PathHelper.js';
9
+ import { getConfigPath, getApiPath, getSrcPath, getDistPath, getPath } from '../utils/PathHelper.js';
11
10
  import build from '../build/build.js';
12
11
 
13
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
-
15
12
  /**
16
- * Carga la configuración desde sliceConfig.json
13
+ * Loads configuration from sliceConfig.json
17
14
  */
18
15
  const loadConfig = () => {
19
16
  try {
@@ -27,7 +24,7 @@ const loadConfig = () => {
27
24
  };
28
25
 
29
26
  /**
30
- * Verifica si un puerto está disponible
27
+ * Checks if a port is available
31
28
  */
32
29
  async function isPortAvailable(port) {
33
30
  return new Promise((resolve) => {
@@ -51,16 +48,16 @@ async function isPortAvailable(port) {
51
48
  }
52
49
 
53
50
  /**
54
- * Verifica si existe un build de producción
51
+ * Checks if a production build exists
55
52
  */
56
53
  async function checkProductionBuild() {
57
54
  const distDir = getDistPath(import.meta.url);
58
- const bundleConfigPath = path.join(distDir, 'bundles', 'bundle.config.json');
55
+ const bundleConfigPath = getPath(import.meta.url, 'dist', 'bundles', 'bundle.config.json');
59
56
  return (await fs.pathExists(distDir)) && (await fs.pathExists(bundleConfigPath));
60
57
  }
61
58
 
62
59
  /**
63
- * Verifica si existe la estructura de desarrollo
60
+ * Checks if the development structure exists
64
61
  */
65
62
  async function checkDevelopmentStructure() {
66
63
  const srcDir = getSrcPath(import.meta.url);
@@ -70,13 +67,13 @@ async function checkDevelopmentStructure() {
70
67
  }
71
68
 
72
69
  /**
73
- * Inicia el servidor Node.js con argumentos y mejor feedback
70
+ * Starts the Node.js server with arguments and improved feedback
74
71
  */
75
72
  function startNodeServer(port, mode) {
76
73
  return new Promise((resolve, reject) => {
77
74
  const apiIndexPath = getApiPath(import.meta.url, 'index.js');
78
75
 
79
- // Verificar que el archivo existe
76
+ // Verify the file exists
80
77
  if (!fs.existsSync(apiIndexPath)) {
81
78
  reject(new Error(`Server file not found: ${apiIndexPath}`));
82
79
  return;
@@ -84,7 +81,7 @@ function startNodeServer(port, mode) {
84
81
 
85
82
  Print.serverStatus('starting', 'Starting server...');
86
83
 
87
- // Construir argumentos basados en el modo
84
+ // Build arguments based on mode
88
85
  const args = [apiIndexPath];
89
86
  if (mode === 'production') {
90
87
  args.push('--production');
@@ -109,12 +106,12 @@ function startNodeServer(port, mode) {
109
106
  let serverStarted = false;
110
107
  let outputBuffer = '';
111
108
 
112
- // Capturar la salida para detectar cuando el servidor está listo
109
+ // Capture output to detect when the server is ready
113
110
  serverProcess.stdout.on('data', (data) => {
114
111
  const output = data.toString();
115
112
  outputBuffer += output;
116
113
 
117
- // Detectar mensajes comunes que indican que el servidor ha iniciado
114
+ // Detect common messages indicating the server has started
118
115
  if (!serverStarted && (
119
116
  output.includes('Server running') ||
120
117
  output.includes('listening on') ||
@@ -125,7 +122,7 @@ function startNodeServer(port, mode) {
125
122
  Print.serverReady(port);
126
123
  }
127
124
 
128
- // Mostrar la salida del servidor
125
+ // Display server output
129
126
  process.stdout.write(output);
130
127
  });
131
128
 
@@ -161,7 +158,7 @@ function startNodeServer(port, mode) {
161
158
  serverProcess.kill('SIGTERM');
162
159
  });
163
160
 
164
- // Si después de 3 segundos no detectamos inicio, asumimos que está listo
161
+ // If after 3 seconds we haven't detected startup, assume it's ready
165
162
  setTimeout(() => {
166
163
  if (!serverStarted) {
167
164
  serverStarted = true;
@@ -173,7 +170,7 @@ function startNodeServer(port, mode) {
173
170
  }
174
171
 
175
172
  /**
176
- * Función principal para iniciar servidor
173
+ * Main function to start the server
177
174
  */
178
175
  export default async function startServer(options = {}) {
179
176
  const config = loadConfig();
@@ -185,7 +182,7 @@ export default async function startServer(options = {}) {
185
182
  Print.title(`🚀 Starting Slice.js ${mode} server...`);
186
183
  Print.newLine();
187
184
 
188
- // Verificar estructura del proyecto
185
+ // Verify project structure
189
186
  if (!await checkDevelopmentStructure()) {
190
187
  throw new Error('Project structure not found. Run "slice init" first.');
191
188
  }
@@ -204,7 +201,7 @@ export default async function startServer(options = {}) {
204
201
  Print.newLine();
205
202
 
206
203
  if (mode === 'production') {
207
- // Verificar que existe build de producción
204
+ // Verify production build exists
208
205
  if (!await checkProductionBuild()) {
209
206
  Print.info('No production build found. Running "slice build"...');
210
207
  const success = await build({});
@@ -219,10 +216,10 @@ export default async function startServer(options = {}) {
219
216
 
220
217
  Print.newLine();
221
218
 
222
- // Iniciar el servidor con argumentos
219
+ // Start the server with arguments
223
220
  let serverProcess = await startNodeServer(actualPort, mode);
224
221
 
225
- // Configurar watch mode si está habilitado
222
+ // Configure watch mode if enabled
226
223
  if (watch) {
227
224
  Print.newLine();
228
225
  const watcher = setupWatcher(serverProcess, async (changedPath) => {
@@ -253,12 +250,12 @@ export default async function startServer(options = {}) {
253
250
 
254
251
  } catch (error) {
255
252
  Print.newLine();
256
- Print.error(error.message);
253
+ Print.error(`Failed to start development server: ${error.message}`);
257
254
  throw error;
258
255
  }
259
256
  }
260
257
 
261
258
  /**
262
- * Funciones de utilidad exportadas
259
+ * Exported utility functions
263
260
  */
264
261
  export { checkProductionBuild, checkDevelopmentStructure, isPortAvailable };
@@ -3,9 +3,9 @@ import chalk from 'chalk';
3
3
  import Print from '../Print.js';
4
4
 
5
5
  /**
6
- * Configura el watcher para archivos del proyecto
7
- * @param {ChildProcess} serverProcess - Proceso del servidor
8
- * @returns {FSWatcher} - Watcher de chokidar
6
+ * Configures the watcher for project files
7
+ * @param {ChildProcess} serverProcess - Server process
8
+ * @returns {FSWatcher} - Chokidar watcher
9
9
  */
10
10
  export default function setupWatcher(serverProcess, onRestart) {
11
11
  Print.info('Watch mode enabled - monitoring file changes...');
@@ -13,7 +13,7 @@ export default function setupWatcher(serverProcess, onRestart) {
13
13
 
14
14
  const watcher = chokidar.watch(['src/**/*', 'api/**/*'], {
15
15
  ignored: [
16
- /(^|[\/\\])\../, // archivos ocultos
16
+ /(^|[\/\\])\../, // hidden files
17
17
  '**/node_modules/**',
18
18
  '**/dist/**',
19
19
  '**/bundles/**',
@@ -31,7 +31,7 @@ export default function setupWatcher(serverProcess, onRestart) {
31
31
 
32
32
  watcher
33
33
  .on('change', (path) => {
34
- // Debounce para evitar múltiples reloads
34
+ // Debounce to avoid multiple reloads
35
35
  clearTimeout(reloadTimeout);
36
36
  reloadTimeout = setTimeout(() => {
37
37
  if(onRestart) {
@@ -68,8 +68,8 @@ export default function setupWatcher(serverProcess, onRestart) {
68
68
  }
69
69
 
70
70
  /**
71
- * Detiene el watcher de forma segura
72
- * @param {FSWatcher} watcher - Watcher a detener
71
+ * Stops the watcher safely
72
+ * @param {FSWatcher} watcher - Watcher to stop
73
73
  */
74
74
  export function stopWatcher(watcher) {
75
75
  if (watcher) {
@@ -4,6 +4,7 @@ import { parse } from '@babel/parser';
4
4
  import traverse from '@babel/traverse';
5
5
 
6
6
  import Print from '../Print.js';
7
+ import { getConfigPath, getComponentsJsPath, joinRoot } from '../utils/PathHelper.js';
7
8
 
8
9
  const TYPE_MAP = {
9
10
  string: 'string',
@@ -114,22 +115,29 @@ const sortKeys = (obj) => {
114
115
  }, {});
115
116
  };
116
117
 
117
- const parseComponentsRegistry = (content) => {
118
+ const parseComponentsRegistry = (content, filePath) => {
118
119
  const match = content.match(/const components = ({[\s\S]*?});/);
119
120
  if (!match) {
120
- throw new Error('Invalid components.js format. Expected: const components = { ... };');
121
+ throw new Error(`Invalid format in ${filePath}. Expected: const components = { ... };`);
122
+ }
123
+ try {
124
+ return JSON.parse(match[1]);
125
+ } catch (parseError) {
126
+ throw new Error(`Failed to parse components registry in ${filePath}: ${parseError.message}`);
121
127
  }
122
- return JSON.parse(match[1]);
123
128
  };
124
129
 
125
- const extractStaticPropsFromSource = (source) => {
130
+ const extractStaticPropsFromSource = (source, filePath) => {
126
131
  let ast;
127
132
  try {
128
133
  ast = parse(source, {
129
134
  sourceType: 'module',
130
135
  plugins: ['classProperties']
131
136
  });
132
- } catch {
137
+ } catch (parseError) {
138
+ const sourceDesc = filePath || 'unknown file';
139
+ const loc = parseError.loc ? ` at line ${parseError.loc.line}, column ${parseError.loc.column}` : '';
140
+ Print.warning(`Parse error in ${sourceDesc}${loc}: ${parseError.message}`);
133
141
  return null;
134
142
  }
135
143
 
@@ -194,7 +202,7 @@ const DEFAULT_EDITOR_EXCLUDE = ['node_modules', 'dist', 'src/libs/**', 'tests/**
194
202
  const NOISY_INCLUDE_PATTERNS = new Set(['src/**/*.js', 'api/**/*.js', 'tests/**/*.js']);
195
203
 
196
204
  const readPublicFolderExcludes = async (projectRoot) => {
197
- const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
205
+ const configPath = getConfigPath(import.meta.url, projectRoot);
198
206
  if (!(await fs.pathExists(configPath))) return [];
199
207
 
200
208
  try {
@@ -230,7 +238,7 @@ const collectJavaScriptFiles = async (dirPath) => {
230
238
  };
231
239
 
232
240
  const ensureNoCheckInPublicVendorFiles = async (projectRoot) => {
233
- const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
241
+ const configPath = getConfigPath(import.meta.url, projectRoot);
234
242
  if (!(await fs.pathExists(configPath))) return { updatedFiles: 0, scannedFiles: 0 };
235
243
 
236
244
  let parsed;
@@ -245,7 +253,7 @@ const ensureNoCheckInPublicVendorFiles = async (projectRoot) => {
245
253
  .map((folder) => String(folder || '').trim())
246
254
  .filter(Boolean)
247
255
  .map((folder) => folder.replace(/^[/\\]+/, ''))
248
- .map((folder) => path.join(projectRoot, 'src', folder));
256
+ .map((folder) => joinRoot(projectRoot, 'src', folder));
249
257
 
250
258
  const uniqueDirs = Array.from(new Set(candidateDirs));
251
259
  let scannedFiles = 0;
@@ -360,9 +368,11 @@ const readCategoryPathFromConfig = async (configPath, category) => {
360
368
  };
361
369
 
362
370
  const loadComponentStaticProps = async ({ projectRoot, registryMap }) => {
363
- const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
371
+ const configPath = getConfigPath(import.meta.url, projectRoot);
364
372
  const categoryPathCache = new Map();
365
373
  const componentPropsMap = {};
374
+ let skippedCount = 0;
375
+ let processedCount = 0;
366
376
 
367
377
  for (const [componentName, category] of Object.entries(sortKeys(registryMap))) {
368
378
  if (!categoryPathCache.has(category)) {
@@ -372,26 +382,50 @@ const loadComponentStaticProps = async ({ projectRoot, registryMap }) => {
372
382
 
373
383
  const categoryPath = categoryPathCache.get(category);
374
384
  if (!categoryPath) {
385
+ skippedCount++;
375
386
  continue;
376
387
  }
377
388
 
378
- const componentFile = path.join(projectRoot, 'src', categoryPath, componentName, `${componentName}.js`);
389
+ const componentFile = joinRoot(projectRoot, 'src', categoryPath, componentName, `${componentName}.js`);
379
390
  if (!(await fs.pathExists(componentFile))) {
391
+ skippedCount++;
392
+ continue;
393
+ }
394
+
395
+ let source;
396
+ try {
397
+ source = await fs.readFile(componentFile, 'utf8');
398
+ } catch (readError) {
399
+ Print.warning(`Cannot read ${componentFile}: ${readError.message}`);
400
+ skippedCount++;
380
401
  continue;
381
402
  }
382
403
 
383
- const source = await fs.readFile(componentFile, 'utf8');
384
- const props = extractStaticPropsFromSource(source);
385
- componentPropsMap[componentName] = props || { [DYNAMIC_FALLBACK_PROP]: { type: 'any', required: false } };
404
+ const props = extractStaticPropsFromSource(source, componentFile);
405
+ if (props) {
406
+ componentPropsMap[componentName] = props;
407
+ processedCount++;
408
+ } else {
409
+ componentPropsMap[componentName] = { [DYNAMIC_FALLBACK_PROP]: { type: 'any', required: false } };
410
+ }
411
+ }
412
+
413
+ if (skippedCount > 0) {
414
+ Print.info(`Skipped ${skippedCount} component(s) with missing or unreadable files`);
386
415
  }
387
416
 
388
417
  return componentPropsMap;
389
418
  };
390
419
 
391
420
  const generateTypesFile = async ({ projectRoot, outputPath }) => {
392
- const registryPath = path.join(projectRoot, 'src', 'Components', 'components.js');
393
- const registryContent = await fs.readFile(registryPath, 'utf8');
394
- const registryMap = parseComponentsRegistry(registryContent);
421
+ const registryPath = getComponentsJsPath(import.meta.url, projectRoot);
422
+ let registryContent;
423
+ try {
424
+ registryContent = await fs.readFile(registryPath, 'utf8');
425
+ } catch (readError) {
426
+ throw new Error(`Cannot read components registry at ${registryPath}: ${readError.message}`);
427
+ }
428
+ const registryMap = parseComponentsRegistry(registryContent, registryPath);
395
429
 
396
430
  const componentPropsMap = await loadComponentStaticProps({ projectRoot, registryMap });
397
431
 
@@ -412,12 +446,12 @@ const toPosixRelative = (projectRoot, targetPath) => {
412
446
 
413
447
  const ensureEditorConfigForTypes = async ({ projectRoot, outputPath }) => {
414
448
  try {
415
- const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
449
+ const tsconfigPath = joinRoot(projectRoot, 'tsconfig.json');
416
450
  if (await fs.pathExists(tsconfigPath)) {
417
451
  return { mode: 'tsconfig_exists', filePath: tsconfigPath, includeAdded: false };
418
452
  }
419
453
 
420
- const jsconfigPath = path.join(projectRoot, 'jsconfig.json');
454
+ const jsconfigPath = joinRoot(projectRoot, 'jsconfig.json');
421
455
  const declarationGlob = (() => {
422
456
  const relative = toPosixRelative(projectRoot, outputPath);
423
457
  if (!relative) return 'src/**/*.d.ts';
@@ -541,5 +575,6 @@ export {
541
575
  extractStaticPropsFromSource,
542
576
  generateDeclarationContent,
543
577
  generateTypesFile,
578
+ parseComponentsRegistry,
544
579
  runGenerateTypes
545
580
  };
@@ -59,10 +59,17 @@ export function getDistPath(moduleUrl, ...segments) {
59
59
  return joinProject(moduleUrl, 'dist', ...segments)
60
60
  }
61
61
 
62
- export function getConfigPath(moduleUrl) {
62
+ export function getConfigPath(moduleUrl, root) {
63
+ if (root) return path.join(root, 'src', 'sliceConfig.json')
63
64
  return joinProject(moduleUrl, 'src', 'sliceConfig.json')
64
65
  }
65
66
 
66
- export function getComponentsJsPath(moduleUrl) {
67
+ export function getComponentsJsPath(moduleUrl, root) {
68
+ if (root) return path.join(root, 'src', 'Components', 'components.js')
67
69
  return joinProject(moduleUrl, 'src', 'Components', 'components.js')
68
70
  }
71
+
72
+ export function joinRoot(root, ...segments) {
73
+ const clean = segments.map(sanitize)
74
+ return path.join(root, ...clean)
75
+ }
@@ -4,7 +4,7 @@ import fs from "fs-extra";
4
4
  import path from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import Print from "../Print.js";
7
- import { getProjectRoot } from "../utils/PathHelper.js";
7
+ import { getProjectRoot, getPath } from "../utils/PathHelper.js";
8
8
 
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
 
@@ -25,14 +25,14 @@ class VersionChecker {
25
25
 
26
26
  // Get Framework version from project node_modules
27
27
  const projectRoot = getProjectRoot(import.meta.url);
28
- const frameworkPackagePath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'package.json');
28
+ const frameworkPackagePath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'package.json');
29
29
  if (await fs.pathExists(frameworkPackagePath)) {
30
30
  const frameworkPackage = await fs.readJson(frameworkPackagePath);
31
31
  this.currentFrameworkVersion = frameworkPackage.version;
32
32
  }
33
33
 
34
34
  // Get Project's CLI version
35
- const projectPackagePath = path.join(__dirname, '../../../../package.json');
35
+ const projectPackagePath = getPath(import.meta.url, 'package.json');
36
36
  if (await fs.pathExists(projectPackagePath)) {
37
37
  const projectPackage = await fs.readJson(projectPackagePath);
38
38
  if (projectPackage.dependencies && projectPackage.dependencies['slicejs-cli']) {
@@ -3,21 +3,13 @@ import fs from 'fs-extra';
3
3
  import path from 'path';
4
4
  import { parse } from '@babel/parser';
5
5
  import traverse from '@babel/traverse';
6
- import { getSrcPath, getComponentsJsPath, getProjectRoot, getConfigPath } from '../PathHelper.js';
6
+ import { getSrcPath, getComponentsJsPath, getConfigPath, getPath } from '../PathHelper.js';
7
7
 
8
8
  export default class DependencyAnalyzer {
9
9
  constructor(moduleUrl) {
10
10
  this.moduleUrl = moduleUrl;
11
- this.projectRoot = getProjectRoot(moduleUrl);
12
11
  this.componentsPath = path.dirname(getComponentsJsPath(moduleUrl));
13
- this.frameworkComponentsPath = path.join(
14
- this.projectRoot,
15
- 'node_modules',
16
- 'slicejs-web-framework',
17
- 'Slice',
18
- 'Components',
19
- 'Structural'
20
- );
12
+ this.frameworkComponentsPath = getPath(moduleUrl, 'node_modules', 'slicejs-web-framework', 'Slice', 'Components', 'Structural');
21
13
  this.routesPath = getSrcPath(moduleUrl, 'routes.js');
22
14
 
23
15
  // Analysis storage
@@ -229,7 +221,7 @@ export default class DependencyAnalyzer {
229
221
  if (!await fs.pathExists(this.frameworkComponentsPath)) {
230
222
  return;
231
223
  }
232
- const frameworkConfigPath = path.join(this.projectRoot, 'src', 'sliceConfig.json');
224
+ const frameworkConfigPath = getConfigPath(this.moduleUrl);
233
225
  let frameworkConfig = {};
234
226
  try {
235
227
  frameworkConfig = await fs.readJson(frameworkConfigPath);
@@ -723,7 +715,7 @@ export default class DependencyAnalyzer {
723
715
  */
724
716
  async analyzeRoutes() {
725
717
  if (!await fs.pathExists(this.routesPath)) {
726
- throw new Error('routes.js no encontrado');
718
+ throw new Error(`routes.js not found at expected path: ${this.routesPath}. Ensure your routes.js file exists in the src directory.`);
727
719
  }
728
720
 
729
721
  const content = await fs.readFile(this.routesPath, 'utf-8');
@@ -735,11 +727,11 @@ export default class DependencyAnalyzer {
735
727
  });
736
728
 
737
729
  let currentRoute = null;
738
- const self = this; // Guardar referencia a la instancia
730
+ const self = this; // Save reference to instance
739
731
 
740
732
  traverse.default(ast, {
741
733
  ObjectExpression(path) {
742
- // Buscar objetos de ruta: { path: '/', component: 'HomePage' }
734
+ // Look for route objects: { path: '/', component: 'HomePage' }
743
735
  const properties = path.node.properties;
744
736
  const pathProp = properties.find(p => p.key?.name === 'path');
745
737
  const componentProp = properties.find(p => p.key?.name === 'component');
@@ -756,7 +748,7 @@ export default class DependencyAnalyzer {
756
748
 
757
749
  self.routes.set(routePath, currentRoute);
758
750
 
759
- // Marcar el componente como usado por esta ruta
751
+ // Mark the component as used by this route
760
752
  if (self.components.has(componentName)) {
761
753
  self.components.get(componentName).routes.add(routePath);
762
754
  }
@@ -768,7 +760,7 @@ export default class DependencyAnalyzer {
768
760
  this.routeGroups = this.detectRouteGroups();
769
761
 
770
762
  } catch (error) {
771
- console.warn(`⚠️ Error parseando rutas: ${error.message}`);
763
+ console.warn(`⚠️ Error parsing routes at ${this.routesPath}: ${error.message}`);
772
764
  }
773
765
  }
774
766
 
@@ -0,0 +1,31 @@
1
+ import fs from 'fs-extra';
2
+ import { getConfigPath } from './PathHelper.js';
3
+ import Print from '../Print.js';
4
+
5
+ export async function loadConfig(moduleUrl) {
6
+ try {
7
+ const configPath = getConfigPath(moduleUrl);
8
+ if (!await fs.pathExists(configPath)) {
9
+ return null;
10
+ }
11
+ const rawData = await fs.readFile(configPath, 'utf-8');
12
+ return JSON.parse(rawData);
13
+ } catch (error) {
14
+ Print.error(`Error loading configuration: ${error.message}`);
15
+ return null;
16
+ }
17
+ }
18
+
19
+ export function loadConfigSync(moduleUrl) {
20
+ try {
21
+ const configPath = getConfigPath(moduleUrl);
22
+ if (!fs.existsSync(configPath)) {
23
+ return null;
24
+ }
25
+ const rawData = fs.readFileSync(configPath, 'utf-8');
26
+ return JSON.parse(rawData);
27
+ } catch (error) {
28
+ Print.error(`Error loading configuration: ${error.message}`);
29
+ return null;
30
+ }
31
+ }
@@ -6,7 +6,7 @@ import inquirer from "inquirer";
6
6
  import ora from "ora";
7
7
  import Print from "../Print.js";
8
8
  import versionChecker from "./VersionChecker.js";
9
- import { getProjectRoot } from "../utils/PathHelper.js";
9
+ import { getProjectRoot, getApiPath, getPath } from "../utils/PathHelper.js";
10
10
  import path from "path";
11
11
  import { fileURLToPath } from "url";
12
12
  import fs from "fs-extra";
@@ -401,9 +401,8 @@ export class UpdateManager {
401
401
 
402
402
  async updateApiIndexIfNeeded(options = {}) {
403
403
  try {
404
- const projectRoot = getProjectRoot(import.meta.url);
405
- const projectApiPath = path.join(projectRoot, 'api', 'index.js');
406
- const frameworkApiPath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'api', 'index.js');
404
+ const projectApiPath = getApiPath(import.meta.url, 'index.js');
405
+ const frameworkApiPath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'api', 'index.js');
407
406
 
408
407
  if (!await fs.pathExists(projectApiPath) || !await fs.pathExists(frameworkApiPath)) {
409
408
  return;