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 +26 -2
- package/client.js +27 -19
- package/commands/startServer/startServer.js +40 -13
- package/commands/startServer/watchServer.js +19 -7
- package/commands/utils/bundling/BundleGenerator.js +124 -19
- package/package.json +3 -4
- package/post.js +7 -7
package/README.md
CHANGED
|
@@ -17,13 +17,37 @@
|
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
550
|
+
* Cleans JavaScript code by processing imports and ensuring class is available globally
|
|
551
551
|
*/
|
|
552
|
-
cleanJavaScript(code, componentName) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
//
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
566
|
-
code += `\nwindow.${componentName} = ${componentName};`;
|
|
671
|
+
newCode += `\nwindow.${componentName} = ${componentName};`;
|
|
567
672
|
}
|
|
568
673
|
|
|
569
|
-
// Add return statement for bundle evaluation compatibility
|
|
570
|
-
|
|
674
|
+
// 5. Add return statement for bundle evaluation compatibility
|
|
675
|
+
newCode += `\nreturn ${componentName};`;
|
|
571
676
|
|
|
572
|
-
return
|
|
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.
|
|
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('
|
|
16
|
-
console.log('
|
|
17
|
-
console.log('
|
|
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('
|
|
23
|
-
console.log('
|
|
24
|
-
console.log('
|
|
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);
|