slicejs-cli 2.7.9 → 2.8.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
@@ -17,13 +17,37 @@
17
17
 
18
18
  ## Installation
19
19
 
20
- - Local (recommended):
20
+ ### Local (Recommended)
21
+
22
+ 1. Install as a development dependency:
21
23
 
22
24
  ```bash
23
25
  npm install slicejs-cli --save-dev
24
26
  ```
25
27
 
26
- - Global:
28
+ 2. Add to your `package.json` scripts:
29
+
30
+ ```json
31
+ {
32
+ "scripts": {
33
+ "dev": "slice dev",
34
+ "build": "slice build",
35
+ "slice": "slice"
36
+ }
37
+ }
38
+ ```
39
+
40
+ 3. usage:
41
+
42
+ ```bash
43
+ npm run dev
44
+ # or pass arguments
45
+ npm run slice -- get Button
46
+ ```
47
+
48
+ ### Global (Not Recommended)
49
+
50
+ Global installations can lead to version mismatches and "works on my machine" issues.
27
51
 
28
52
  ```bash
29
53
  npm install -g slicejs-cli
package/client.js CHANGED
@@ -132,6 +132,33 @@ sliceClient
132
132
  await versionChecker.showVersionInfo();
133
133
  });
134
134
 
135
+ // BUNDLE COMMAND
136
+ const bundleCommand = sliceClient.command("bundle")
137
+ .description("Build component bundles for production")
138
+ .action(async (options) => {
139
+ await runWithVersionCheck(async () => {
140
+ await bundle(options);
141
+ });
142
+ });
143
+
144
+ bundleCommand
145
+ .command("clean")
146
+ .description("Remove all generated bundles")
147
+ .action(async () => {
148
+ await cleanBundles();
149
+ });
150
+
151
+ bundleCommand
152
+ .command("info")
153
+ .description("Show information about generated bundles")
154
+ .action(async () => {
155
+ await bundleInfo();
156
+ });
157
+
158
+ bundleCommand
159
+ .option("-a, --analyze", "Analyze project dependencies without bundling")
160
+ .option("-v, --verbose", "Show detailed output");
161
+
135
162
  // DEV COMMAND (DEVELOPMENT) - COMANDO PRINCIPAL
136
163
  sliceClient
137
164
  .command("dev")
@@ -453,25 +480,6 @@ sliceClient
453
480
  subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage()
454
481
  });
455
482
 
456
- sliceClient
457
- .command('bundle')
458
- .description('Generate production bundles for optimal loading')
459
- .option('-a, --analyze', 'Only analyze without generating bundles')
460
- .option('-v, --verbose', 'Show detailed information')
461
- .action(bundle);
462
-
463
- // Subcomando: limpiar bundles
464
- sliceClient
465
- .command('bundle:clean')
466
- .description('Remove all generated bundles')
467
- .action(cleanBundles);
468
-
469
- // Subcomando: información
470
- sliceClient
471
- .command('bundle:info')
472
- .description('Show information about generated bundles')
473
- .action(bundleInfo);
474
-
475
483
 
476
484
  // Custom help - SIMPLIFICADO para development only
477
485
  sliceClient.addHelpText('after', `
@@ -1,5 +1,6 @@
1
1
  // commands/startServer/startServer.js - MEJORADO CON VALIDACIÓN Y FEEDBACK
2
2
 
3
+ import bundle from '../bundle/bundle.js';
3
4
  import fs from 'fs-extra';
4
5
  import path from 'path';
5
6
  import { fileURLToPath } from 'url';
@@ -184,19 +185,17 @@ export default async function startServer(options = {}) {
184
185
  throw new Error('Project structure not found. Run "slice init" first.');
185
186
  }
186
187
 
187
- // Verificar disponibilidad del puerto
188
- Print.checkingPort(port);
189
- const portAvailable = await isPortAvailable(port);
190
-
191
- if (!portAvailable) {
192
- throw new Error(
193
- `Port ${port} is already in use. Please:\n` +
194
- ` 1. Stop the process using port ${port}, or\n` +
195
- ` 2. Use a different port: slice ${mode === 'development' ? 'dev' : mode === 'bundled' ? 'start --bundled' : 'start'} -p <port>`
196
- );
188
+ let actualPort = await isPortAvailable(port) ? port : port + 1; // Try one more port
189
+ if(actualPort !== port) {
190
+ // Check if the fallback is available
191
+ const fallbackAvailable = await isPortAvailable(actualPort);
192
+ if(!fallbackAvailable) {
193
+ throw new Error(`Ports ${port} and ${actualPort} are in use.`);
194
+ }
195
+ Print.info(`ℹ️ Port ${port} in use, using ${actualPort} instead.`);
197
196
  }
198
197
 
199
- Print.serverStatus('checking', 'Port available ✓');
198
+ Print.serverStatus('checking', `Port ${actualPort} available ✓`);
200
199
  Print.newLine();
201
200
 
202
201
  if (mode === 'production') {
@@ -214,12 +213,40 @@ export default async function startServer(options = {}) {
214
213
  Print.newLine();
215
214
 
216
215
  // Iniciar el servidor con argumentos
217
- const serverProcess = await startNodeServer(port, mode);
216
+ let serverProcess = await startNodeServer(actualPort, mode);
218
217
 
219
218
  // Configurar watch mode si está habilitado
220
219
  if (watch) {
221
220
  Print.newLine();
222
- const watcher = setupWatcher(serverProcess);
221
+ const watcher = setupWatcher(serverProcess, async (changedPath) => {
222
+ if (serverProcess) {
223
+ serverProcess.kill();
224
+ }
225
+
226
+ // Short delay to ensure port is freed
227
+ await new Promise(r => setTimeout(r, 500));
228
+
229
+ try {
230
+ // If we are in bundled mode, regenerate bundles before restarting
231
+ if (mode === 'bundled') {
232
+ Print.info('🔄 File changed. Regenerating bundles...');
233
+ try {
234
+ await bundle({ verbose: false });
235
+ } catch (err) {
236
+ Print.error('Bundle generation failed during watch restart');
237
+ console.error(err);
238
+ // We continue restarting anyway to show error in browser if possible,
239
+ // or maybe just to keep process alive.
240
+ }
241
+ } else {
242
+ Print.info('🔄 File changed. Restarting server...');
243
+ }
244
+
245
+ serverProcess = await startNodeServer(actualPort, mode);
246
+ } catch (e) {
247
+ Print.error(`Failed to restart server: ${e.message}`);
248
+ }
249
+ });
223
250
 
224
251
  // Cleanup en exit
225
252
  const cleanup = () => {
@@ -7,7 +7,7 @@ import Print from '../Print.js';
7
7
  * @param {ChildProcess} serverProcess - Proceso del servidor
8
8
  * @returns {FSWatcher} - Watcher de chokidar
9
9
  */
10
- export default function setupWatcher(serverProcess) {
10
+ export default function setupWatcher(serverProcess, onRestart) {
11
11
  Print.info('Watch mode enabled - monitoring file changes...');
12
12
  Print.newLine();
13
13
 
@@ -16,6 +16,7 @@ export default function setupWatcher(serverProcess) {
16
16
  /(^|[\/\\])\../, // archivos ocultos
17
17
  '**/node_modules/**',
18
18
  '**/dist/**',
19
+ '**/bundles/**',
19
20
  '**/*.log'
20
21
  ],
21
22
  persistent: true,
@@ -30,19 +31,30 @@ export default function setupWatcher(serverProcess) {
30
31
 
31
32
  watcher
32
33
  .on('change', (path) => {
33
- console.log(chalk.cyan(`📝 File changed: ${path}`));
34
-
35
34
  // Debounce para evitar múltiples reloads
36
35
  clearTimeout(reloadTimeout);
37
36
  reloadTimeout = setTimeout(() => {
38
- console.log(chalk.yellow('🔄 Changes detected, server will reload automatically...'));
39
- }, 200);
37
+ if(onRestart) {
38
+ console.log(chalk.yellow('🔄 Changes detected, restarting server...'));
39
+ onRestart(path);
40
+ } else {
41
+ console.log(chalk.yellow('🔄 Changes detected, server will reload automatically... (No handler)'));
42
+ }
43
+ }, 500);
40
44
  })
41
45
  .on('add', (path) => {
42
- console.log(chalk.green(`➕ New file added: ${path}`));
46
+ // console.log(chalk.green(`➕ New file added: ${path}`));
47
+ clearTimeout(reloadTimeout);
48
+ reloadTimeout = setTimeout(() => {
49
+ if (onRestart) onRestart(path);
50
+ }, 500);
43
51
  })
44
52
  .on('unlink', (path) => {
45
- console.log(chalk.red(`➖ File removed: ${path}`));
53
+ // console.log(chalk.red(`➖ File removed: ${path}`));
54
+ clearTimeout(reloadTimeout);
55
+ reloadTimeout = setTimeout(() => {
56
+ if (onRestart) onRestart(path);
57
+ }, 500);
46
58
  })
47
59
  .on('error', (error) => {
48
60
  Print.error(`Watcher error: ${error.message}`);
@@ -524,7 +524,7 @@ export default class BundleGenerator {
524
524
  name: comp.name,
525
525
  category: comp.category,
526
526
  categoryType: comp.categoryType,
527
- js: this.cleanJavaScript(jsContent, comp.name),
527
+ js: this.cleanJavaScript(jsContent, comp.name, comp.path),
528
528
  externalDependencies: dependencyContents, // Files imported with import statements
529
529
  componentDependencies: Array.from(comp.dependencies), // Other components this one depends on
530
530
  html: htmlContent,
@@ -547,29 +547,134 @@ export default class BundleGenerator {
547
547
  }
548
548
 
549
549
  /**
550
- * Cleans JavaScript code by removing imports/exports and ensuring class is available globally
550
+ * Cleans JavaScript code by processing imports and ensuring class is available globally
551
551
  */
552
- cleanJavaScript(code, componentName) {
553
- // Remove export default
554
- code = code.replace(/export\s+default\s+/g, '');
555
-
556
- // Remove imports (components will already be available)
557
- code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
558
-
559
- // Make sure the class is available globally for bundle evaluation
560
- // Preserve original customElements.define if it exists
561
- if (code.includes('customElements.define')) {
562
- // Add global assignment before customElements.define
563
- code = code.replace(/customElements\.define\([^;]+\);?\s*$/, `window.${componentName} = ${componentName};\n$&`);
552
+ cleanJavaScript(code, componentName, componentPath) {
553
+ let newCode = code;
554
+
555
+ // 1. In-line local imports to support data files and helpers
556
+ // Matches: import [clause] from '[path]'
557
+ const importRegex = /import\s+([\w\s{},*]+)\s+from\s+['"`]([\.\/][^'"`]+)['"`];?/g;
558
+
559
+ newCode = newCode.replace(importRegex, (match, importClause, importPath) => {
560
+ try {
561
+ const absolutePath = path.resolve(componentPath, importPath);
562
+
563
+ // Try different extensions if file doesn't exist
564
+ let finalPath = absolutePath;
565
+ if (!fs.existsSync(finalPath)) {
566
+ if (fs.existsSync(finalPath + '.js')) finalPath += '.js';
567
+ else if (fs.existsSync(finalPath + '.json')) finalPath += '.json';
568
+ else {
569
+ console.warn(`BundleGenerator: Could not resolve ${importPath} in ${componentName}`);
570
+ return `/* Fail: File not found ${importPath} */`; // return comment so regular import stripper deletes it
571
+ }
572
+ }
573
+
574
+ let fileContent = fs.readFileSync(finalPath, 'utf-8');
575
+
576
+ // Parse Import Clause
577
+ let defaultImport = null;
578
+ let namedImports = [];
579
+
580
+ const cleanClause = importClause.trim();
581
+
582
+ if (cleanClause.includes('{')) {
583
+ // Has named imports
584
+ const parts = cleanClause.split('{');
585
+ const preBrace = parts[0].trim(); // "Default, " or ""
586
+ const braceContent = parts[1].replace('}', '').trim(); // "A, B as C"
587
+
588
+ if (preBrace) {
589
+ defaultImport = preBrace.replace(',', '').trim();
590
+ }
591
+
592
+ if (braceContent) {
593
+ namedImports = braceContent.split(',').map(i => {
594
+ const [name, alias] = i.split(/\s+as\s+/).map(s => s.trim());
595
+ return { name, alias: alias || name };
596
+ });
597
+ }
598
+ } else {
599
+ if (cleanClause.includes('*')) return match; // Skip namespace
600
+ defaultImport = cleanClause;
601
+ }
602
+
603
+ // Transform exported content to local variables
604
+
605
+ // STRATEGY:
606
+ // 1. Convert all 'const/let' to 'var' to allow redeclaration (resolves naming collisions)
607
+ // 2. Wrap exports
608
+
609
+ // Replace const/let with var globally to prevent "Identifier already declared" errors
610
+ // Use a sophisticated regex that handles:
611
+ // - Start of line or indentation
612
+ // - Previous statement termination (; or })
613
+ // - Optional export keyword
614
+ fileContent = fileContent.replace(/(^|[\s;}])(export\s+)?(const|let)(?=\s+)/gm, '$1$2var');
615
+
616
+ const defaultExportName = `__default_${path.basename(finalPath, path.extname(finalPath)).replace(/\W/g, '_')}_${Math.random().toString(36).substr(2, 5)}`;
617
+
618
+ // Handle export default
619
+ if (fileContent.includes('export default')) {
620
+ fileContent = fileContent.replace(/export\s+default\s+/g, `var ${defaultExportName} = `);
621
+ } else {
622
+ // If no export default, but we are importing default, try to find a variable with the same name
623
+ // This happens in legacy data files like: const data = ...; (no export default)
624
+ if (defaultImport) {
625
+ // Check if there is a variable matching the import name in the file content
626
+ const varRegex = new RegExp(`var\\s+${defaultImport}\\s*=`);
627
+ if (varRegex.test(fileContent)) {
628
+ // Found it, map it to our default export name for consistency
629
+ fileContent += `\nvar ${defaultExportName} = ${defaultImport};\n`;
630
+ }
631
+ }
632
+ }
633
+
634
+ // Handle named exports: remove 'export' keyword
635
+ fileContent = fileContent.replace(/^\s*export\s+/gm, '');
636
+ // Also clean up export { ... } statements
637
+ fileContent = fileContent.replace(/^\s*export\s*\{[^}]+\};?/gm, '');
638
+
639
+ let aliasCode = '';
640
+
641
+ // Map Default Import
642
+ if (defaultImport) {
643
+ // Use var for alias to be safe against existing variable
644
+ aliasCode += `if (typeof ${defaultImport} === 'undefined') { var ${defaultImport} = (typeof ${defaultExportName} !== 'undefined') ? ${defaultExportName} : undefined; }\n`;
645
+ }
646
+
647
+ // Map Named Imports with Aliases
648
+ namedImports.forEach(({ name, alias }) => {
649
+ if (name !== alias) {
650
+ aliasCode += `var ${alias} = ${name};\n`;
651
+ }
652
+ });
653
+
654
+ return `/* Inlined: ${importPath} */\n${fileContent}\n${aliasCode}`;
655
+ } catch (e) {
656
+ console.warn(`Failed to inline ${importPath} in ${componentName}: ${e.message}`);
657
+ return `/* Error inlining ${importPath}: ${e.message} */`;
658
+ }
659
+ });
660
+
661
+ // 2. Remove any remaining import statements (non-local or failed ones)
662
+ newCode = newCode.replace(/^.*import\s+.*from\s+['"`].*['"`];?.*$/gm, '');
663
+
664
+ // 3. Remove export default from the component itself
665
+ newCode = newCode.replace(/export\s+default\s+/g, '');
666
+
667
+ // 4. Make sure the class is available globally for bundle evaluation
668
+ if (newCode.includes('customElements.define')) {
669
+ newCode = newCode.replace(/customElements\.define\([^;]+\);?\s*$/, `window.${componentName} = ${componentName};\n$&`);
564
670
  } else {
565
- // If no customElements.define found, just assign to global
566
- code += `\nwindow.${componentName} = ${componentName};`;
671
+ newCode += `\nwindow.${componentName} = ${componentName};`;
567
672
  }
568
673
 
569
- // Add return statement for bundle evaluation compatibility
570
- code += `\nreturn ${componentName};`;
674
+ // 5. Add return statement for bundle evaluation compatibility
675
+ newCode += `\nreturn ${componentName};`;
571
676
 
572
- return code;
677
+ return newCode;
573
678
  }
574
679
 
575
680
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-cli",
3
- "version": "2.7.9",
3
+ "version": "2.8.0",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "bin": {
@@ -26,6 +26,7 @@
26
26
  ],
27
27
  "author": "vkneider",
28
28
  "type": "module",
29
+ "preferGlobal": false,
29
30
  "license": "ISC",
30
31
  "dependencies": {
31
32
  "chalk": "^5.6.2",
@@ -38,9 +39,7 @@
38
39
  "inquirer": "^12.4.2",
39
40
  "ora": "^8.2.0",
40
41
  "slicejs-web-framework": "latest",
41
- "terser": "^5.43.1"
42
- },
43
- "devDependencies": {
42
+ "terser": "^5.43.1",
44
43
  "@babel/parser": "^7.28.5",
45
44
  "@babel/traverse": "^7.28.5"
46
45
  }
package/post.js CHANGED
@@ -12,14 +12,14 @@ const targetRoot = initCwd || path.resolve(__dirname, '../../');
12
12
  const projectPackageJsonPath = path.join(targetRoot, 'package.json');
13
13
 
14
14
  if (isGlobal) {
15
- console.log('ℹ️ Global installation of slicejs-cli detected.');
16
- console.log(' Skipping scripts setup. Use the binary directly:');
17
- console.log(' slice dev');
18
- console.log(' slice get Button');
15
+ console.log('⚠️ Global installation of slicejs-cli detected.');
16
+ console.log(' We strongly recommend using a local installation to avoid version mismatches.');
17
+ console.log(' Uninstall global: npm uninstall -g slicejs-cli');
19
18
  process.exit(0);
20
19
  }
21
20
 
22
- console.log('ℹ️ Local installation of slicejs-cli detected.');
23
- console.log(' Skipping automatic scripts setup in postinstall.');
24
- console.log(' Use "slice init" to configure project scripts.');
21
+ console.log(' slicejs-cli installed successfully.');
22
+ console.log(' Add the CLI to your package.json scripts:');
23
+ console.log(' "dev": "slice dev"');
24
+ console.log(' Then run: npm run dev');
25
25
  process.exit(0);