slicejs-cli 2.7.9 → 2.8.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.
- package/README.md +26 -2
- package/client.js +27 -19
- package/commands/init/init.js +56 -56
- package/commands/startServer/startServer.js +40 -13
- package/commands/startServer/watchServer.js +19 -7
- package/commands/utils/PathHelper.js +68 -68
- package/commands/utils/VersionChecker.js +11 -11
- package/commands/utils/bundling/BundleGenerator.js +5 -5
- package/commands/utils/bundling/DependencyAnalyzer.js +270 -12
- package/commands/utils/updateManager.js +87 -87
- 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', `
|
package/commands/init/init.js
CHANGED
|
@@ -48,29 +48,29 @@ export default async function initializeProject(projectType) {
|
|
|
48
48
|
const srcDir = path.join(sliceBaseDir, 'src');
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
|
-
if (fs.existsSync(destinationApi)) throw new Error(`The "api" directory already exists: ${destinationApi}`);
|
|
52
|
-
if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
Print.error('Validating destination directories:', error.message);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
51
|
+
if (fs.existsSync(destinationApi)) throw new Error(`The "api" directory already exists: ${destinationApi}`);
|
|
52
|
+
if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
Print.error('Validating destination directories:', error.message);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
57
|
|
|
58
58
|
// 1. COPIAR LA CARPETA API (mantener lógica original)
|
|
59
|
-
const apiSpinner = ora('Copying API structure...').start();
|
|
60
|
-
try {
|
|
61
|
-
if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
|
|
62
|
-
await fs.copy(apiDir, destinationApi, { recursive: true });
|
|
63
|
-
apiSpinner.succeed('API structure created successfully');
|
|
64
|
-
} catch (error) {
|
|
65
|
-
apiSpinner.fail('Error copying API structure');
|
|
66
|
-
Print.error(error.message);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
59
|
+
const apiSpinner = ora('Copying API structure...').start();
|
|
60
|
+
try {
|
|
61
|
+
if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
|
|
62
|
+
await fs.copy(apiDir, destinationApi, { recursive: true });
|
|
63
|
+
apiSpinner.succeed('API structure created successfully');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
apiSpinner.fail('Error copying API structure');
|
|
66
|
+
Print.error(error.message);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
69
|
|
|
70
70
|
// 2. CREAR ESTRUCTURA SRC BÁSICA (sin copiar componentes Visual)
|
|
71
|
-
const srcSpinner = ora('Creating src structure...').start();
|
|
72
|
-
try {
|
|
73
|
-
if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
|
|
71
|
+
const srcSpinner = ora('Creating src structure...').start();
|
|
72
|
+
try {
|
|
73
|
+
if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
|
|
74
74
|
|
|
75
75
|
// Copiar solo los archivos base de src, excluyendo Components/Visual
|
|
76
76
|
await fs.ensureDir(destinationSrc);
|
|
@@ -94,22 +94,22 @@ export default async function initializeProject(projectType) {
|
|
|
94
94
|
const destComponentItemPath = path.join(destItemPath, componentItem);
|
|
95
95
|
|
|
96
96
|
if (componentItem !== 'Visual') {
|
|
97
|
-
// Copy Service and other component types
|
|
98
|
-
await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
|
|
99
|
-
} else {
|
|
100
|
-
// Only create empty Visual directory
|
|
101
|
-
await fs.ensureDir(destComponentItemPath);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
// Copy other folders normally
|
|
106
|
-
await fs.copy(srcItemPath, destItemPath, { recursive: true });
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
// Copy files normally
|
|
110
|
-
await fs.copy(srcItemPath, destItemPath);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
97
|
+
// Copy Service and other component types
|
|
98
|
+
await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
|
|
99
|
+
} else {
|
|
100
|
+
// Only create empty Visual directory
|
|
101
|
+
await fs.ensureDir(destComponentItemPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// Copy other folders normally
|
|
106
|
+
await fs.copy(srcItemPath, destItemPath, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// Copy files normally
|
|
110
|
+
await fs.copy(srcItemPath, destItemPath);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
113
|
|
|
114
114
|
srcSpinner.succeed('Source structure created successfully');
|
|
115
115
|
} catch (error) {
|
|
@@ -142,15 +142,15 @@ export default async function initializeProject(projectType) {
|
|
|
142
142
|
if (successful > 0 && failed === 0) {
|
|
143
143
|
componentsSpinner.succeed(`All ${successful} Visual components installed successfully`);
|
|
144
144
|
} else if (successful > 0) {
|
|
145
|
-
componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
|
|
146
|
-
Print.info('You can install failed components later using "slice get <component-name>"');
|
|
147
|
-
} else {
|
|
148
|
-
componentsSpinner.fail('Failed to install Visual components');
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
151
|
-
componentsSpinner.warn('No Visual components found in registry');
|
|
152
|
-
Print.info('You can add components later using "slice get <component-name>"');
|
|
153
|
-
}
|
|
145
|
+
componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
|
|
146
|
+
Print.info('You can install failed components later using "slice get <component-name>"');
|
|
147
|
+
} else {
|
|
148
|
+
componentsSpinner.fail('Failed to install Visual components');
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
componentsSpinner.warn('No Visual components found in registry');
|
|
152
|
+
Print.info('You can add components later using "slice get <component-name>"');
|
|
153
|
+
}
|
|
154
154
|
|
|
155
155
|
} catch (error) {
|
|
156
156
|
componentsSpinner.fail('Could not download Visual components from official repository');
|
|
@@ -222,21 +222,21 @@ export default async function initializeProject(projectType) {
|
|
|
222
222
|
console.log(' npm run get - Install components');
|
|
223
223
|
console.log(' npm run browse - Browse components');
|
|
224
224
|
} catch (error) {
|
|
225
|
-
pkgSpinner.fail('Failed to configure npm scripts');
|
|
226
|
-
Print.error(error.message);
|
|
227
|
-
}
|
|
225
|
+
pkgSpinner.fail('Failed to configure npm scripts');
|
|
226
|
+
Print.error(error.message);
|
|
227
|
+
}
|
|
228
228
|
|
|
229
|
-
Print.success('Project initialized successfully.');
|
|
230
|
-
Print.newLine();
|
|
231
|
-
Print.info('Next steps:');
|
|
232
|
-
console.log(' slice browse - View available components');
|
|
233
|
-
console.log(' slice get Button - Install specific components');
|
|
234
|
-
console.log(' slice sync - Update all components to latest versions');
|
|
229
|
+
Print.success('Project initialized successfully.');
|
|
230
|
+
Print.newLine();
|
|
231
|
+
Print.info('Next steps:');
|
|
232
|
+
console.log(' slice browse - View available components');
|
|
233
|
+
console.log(' slice get Button - Install specific components');
|
|
234
|
+
console.log(' slice sync - Update all components to latest versions');
|
|
235
235
|
|
|
236
236
|
} catch (error) {
|
|
237
|
-
Print.error('Unexpected error initializing project:', error.message);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
237
|
+
Print.error('Unexpected error initializing project:', error.message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
240
|
|
|
241
241
|
/**
|
|
242
242
|
* Obtiene TODOS los componentes Visual disponibles en el registry
|
|
@@ -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}`);
|
|
@@ -1,68 +1,68 @@
|
|
|
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 initCwd = process.env.INIT_CWD
|
|
18
|
-
if (initCwd && fs.pathExistsSync(initCwd)) {
|
|
19
|
-
return initCwd
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const cwd = process.cwd()
|
|
23
|
-
if (cwd && fs.pathExistsSync(cwd)) {
|
|
24
|
-
return cwd
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const dirs = candidates(moduleUrl)
|
|
28
|
-
for (const root of dirs) {
|
|
29
|
-
const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
|
|
30
|
-
const hasApi = fs.pathExistsSync(path.join(root, 'api'))
|
|
31
|
-
if (hasSrc || hasApi) return root
|
|
32
|
-
}
|
|
33
|
-
return dirs[1]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function joinProject(moduleUrl, ...segments) {
|
|
37
|
-
const root = resolveProjectRoot(moduleUrl)
|
|
38
|
-
const clean = segments.map(sanitize)
|
|
39
|
-
return path.join(root, ...clean)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function getProjectRoot(moduleUrl) {
|
|
43
|
-
return resolveProjectRoot(moduleUrl)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function getPath(moduleUrl, folder, ...segments) {
|
|
47
|
-
return joinProject(moduleUrl, folder, ...segments)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function getSrcPath(moduleUrl, ...segments) {
|
|
51
|
-
return joinProject(moduleUrl, 'src', ...segments)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function getApiPath(moduleUrl, ...segments) {
|
|
55
|
-
return joinProject(moduleUrl, 'api', ...segments)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function getDistPath(moduleUrl, ...segments) {
|
|
59
|
-
return joinProject(moduleUrl, 'dist', ...segments)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function getConfigPath(moduleUrl) {
|
|
63
|
-
return joinProject(moduleUrl, 'src', 'sliceConfig.json')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function getComponentsJsPath(moduleUrl) {
|
|
67
|
-
return joinProject(moduleUrl, 'src', 'Components', 'components.js')
|
|
68
|
-
}
|
|
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 initCwd = process.env.INIT_CWD
|
|
18
|
+
if (initCwd && fs.pathExistsSync(initCwd)) {
|
|
19
|
+
return initCwd
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const cwd = process.cwd()
|
|
23
|
+
if (cwd && fs.pathExistsSync(cwd)) {
|
|
24
|
+
return cwd
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const dirs = candidates(moduleUrl)
|
|
28
|
+
for (const root of dirs) {
|
|
29
|
+
const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
|
|
30
|
+
const hasApi = fs.pathExistsSync(path.join(root, 'api'))
|
|
31
|
+
if (hasSrc || hasApi) return root
|
|
32
|
+
}
|
|
33
|
+
return dirs[1]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function joinProject(moduleUrl, ...segments) {
|
|
37
|
+
const root = resolveProjectRoot(moduleUrl)
|
|
38
|
+
const clean = segments.map(sanitize)
|
|
39
|
+
return path.join(root, ...clean)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getProjectRoot(moduleUrl) {
|
|
43
|
+
return resolveProjectRoot(moduleUrl)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getPath(moduleUrl, folder, ...segments) {
|
|
47
|
+
return joinProject(moduleUrl, folder, ...segments)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getSrcPath(moduleUrl, ...segments) {
|
|
51
|
+
return joinProject(moduleUrl, 'src', ...segments)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getApiPath(moduleUrl, ...segments) {
|
|
55
|
+
return joinProject(moduleUrl, 'api', ...segments)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getDistPath(moduleUrl, ...segments) {
|
|
59
|
+
return joinProject(moduleUrl, 'dist', ...segments)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getConfigPath(moduleUrl) {
|
|
63
|
+
return joinProject(moduleUrl, 'src', 'sliceConfig.json')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getComponentsJsPath(moduleUrl) {
|
|
67
|
+
return joinProject(moduleUrl, 'src', 'Components', 'components.js')
|
|
68
|
+
}
|
|
@@ -1,10 +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";
|
|
7
|
-
import { getProjectRoot } from "../utils/PathHelper.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";
|
|
8
8
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
|
|
@@ -23,9 +23,9 @@ class VersionChecker {
|
|
|
23
23
|
const cliPackage = await fs.readJson(cliPackagePath);
|
|
24
24
|
this.currentCliVersion = cliPackage.version;
|
|
25
25
|
|
|
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');
|
|
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');
|
|
29
29
|
if (await fs.pathExists(frameworkPackagePath)) {
|
|
30
30
|
const frameworkPackage = await fs.readJson(frameworkPackagePath);
|
|
31
31
|
this.currentFrameworkVersion = frameworkPackage.version;
|
|
@@ -111,7 +111,7 @@ class VersionChecker {
|
|
|
111
111
|
|
|
112
112
|
if (!silent && (cliStatus === 'outdated' || frameworkStatus === 'outdated')) {
|
|
113
113
|
console.log(''); // Line break
|
|
114
|
-
Print.warning('📦 Available Updates:');
|
|
114
|
+
Print.warning('📦 Available Updates:');
|
|
115
115
|
|
|
116
116
|
if (cliStatus === 'outdated') {
|
|
117
117
|
console.log(` 🔧 CLI: ${current.cli} → ${latest.cli}`);
|
|
@@ -142,7 +142,7 @@ class VersionChecker {
|
|
|
142
142
|
const current = await this.getCurrentVersions();
|
|
143
143
|
const latest = await this.getLatestVersions();
|
|
144
144
|
|
|
145
|
-
console.log('\n📋 Version Information:');
|
|
145
|
+
console.log('\n📋 Version Information:');
|
|
146
146
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
147
147
|
|
|
148
148
|
if (current?.cli) {
|
|
@@ -164,4 +164,4 @@ class VersionChecker {
|
|
|
164
164
|
// Singleton instance
|
|
165
165
|
const versionChecker = new VersionChecker();
|
|
166
166
|
|
|
167
|
-
export default versionChecker;
|
|
167
|
+
export default versionChecker;
|
|
@@ -256,7 +256,7 @@ export default class BundleGenerator {
|
|
|
256
256
|
paths: groupData.routes,
|
|
257
257
|
components: uniqueComponents,
|
|
258
258
|
size: totalSize,
|
|
259
|
-
file: `slice-bundle.${groupKey}.js`
|
|
259
|
+
file: `slice-bundle.${this.routeToFileName(groupKey)}.js`
|
|
260
260
|
};
|
|
261
261
|
|
|
262
262
|
console.log(`✓ Bundle ${groupKey}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB (${groupData.routes.length} routes)`);
|
|
@@ -313,7 +313,7 @@ export default class BundleGenerator {
|
|
|
313
313
|
paths: routePaths,
|
|
314
314
|
components: uniqueComponents,
|
|
315
315
|
size: totalSize,
|
|
316
|
-
file: `slice-bundle.${category}.js`
|
|
316
|
+
file: `slice-bundle.${this.routeToFileName(category)}.js`
|
|
317
317
|
};
|
|
318
318
|
|
|
319
319
|
console.log(`✓ Bundle ${category}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB (${routes.length} routes)`);
|
|
@@ -401,7 +401,7 @@ export default class BundleGenerator {
|
|
|
401
401
|
const routeFile = await this.createBundleFile(
|
|
402
402
|
bundle.components,
|
|
403
403
|
'route',
|
|
404
|
-
bundle.path || routeKey // Use routeKey as fallback for hybrid bundles
|
|
404
|
+
bundle.path || bundle.paths || routeKey // Use routeKey as fallback for hybrid bundles
|
|
405
405
|
);
|
|
406
406
|
files.push(routeFile);
|
|
407
407
|
}
|
|
@@ -629,7 +629,7 @@ if (window.slice && window.slice.controller) {
|
|
|
629
629
|
for (const [key, bundle] of Object.entries(this.bundles.routes)) {
|
|
630
630
|
config.bundles.routes[key] = {
|
|
631
631
|
path: bundle.path || bundle.paths || key, // Support both single path and array of paths, fallback to key
|
|
632
|
-
file: bundle.
|
|
632
|
+
file: `slice-bundle.${this.routeToFileName(bundle.path || bundle.paths || key)}.js`,
|
|
633
633
|
size: bundle.size,
|
|
634
634
|
components: bundle.components.map(c => c.name),
|
|
635
635
|
dependencies: ['critical']
|
|
@@ -727,4 +727,4 @@ if (typeof window !== 'undefined' && window.slice && window.slice.controller) {
|
|
|
727
727
|
}
|
|
728
728
|
`;
|
|
729
729
|
}
|
|
730
|
-
}
|
|
730
|
+
}
|
|
@@ -133,16 +133,280 @@ export default class DependencyAnalyzer {
|
|
|
133
133
|
component.size = await this.calculateComponentSize(component.path);
|
|
134
134
|
|
|
135
135
|
// Parse and extract dependencies
|
|
136
|
-
component.dependencies = await this.extractDependencies(content);
|
|
136
|
+
component.dependencies = await this.extractDependencies(content, jsFile);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
141
|
* Extracts dependencies from a component file
|
|
142
142
|
*/
|
|
143
|
-
async extractDependencies(code) {
|
|
143
|
+
async extractDependencies(code, componentFilePath = null) {
|
|
144
144
|
const dependencies = new Set();
|
|
145
145
|
|
|
146
|
+
const resolveRoutesArray = (node, scope) => {
|
|
147
|
+
if (!node) return null;
|
|
148
|
+
|
|
149
|
+
if (node.type === 'ArrayExpression') {
|
|
150
|
+
return node;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (node.type === 'ObjectExpression') {
|
|
154
|
+
const routesProp = node.properties.find(p => p.key?.name === 'routes');
|
|
155
|
+
if (routesProp?.value) {
|
|
156
|
+
return resolveRoutesArray(routesProp.value, scope);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (node.type === 'Identifier' && scope) {
|
|
161
|
+
const binding = scope.getBinding(node.name);
|
|
162
|
+
if (!binding) return null;
|
|
163
|
+
const bindingNode = binding.path?.node;
|
|
164
|
+
|
|
165
|
+
if (bindingNode?.type === 'VariableDeclarator') {
|
|
166
|
+
const init = bindingNode.init;
|
|
167
|
+
if (init?.type === 'ArrayExpression') {
|
|
168
|
+
return init;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (init?.type === 'Identifier') {
|
|
172
|
+
return resolveRoutesArray(init, binding.path.scope);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (init?.type === 'ObjectExpression') {
|
|
176
|
+
return resolveRoutesArray(init, binding.path.scope);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (init?.type === 'MemberExpression') {
|
|
180
|
+
return resolveRoutesArray(init, binding.path.scope);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
|
|
185
|
+
const parent = binding.path.parentPath?.node;
|
|
186
|
+
if (parent?.type === 'ImportDeclaration') {
|
|
187
|
+
const importedName = bindingNode.type === 'ImportDefaultSpecifier'
|
|
188
|
+
? 'default'
|
|
189
|
+
: bindingNode.imported?.name;
|
|
190
|
+
const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
|
|
191
|
+
return resolveRoutesArray(importedNode, null);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (node.type === 'MemberExpression' && scope) {
|
|
197
|
+
const objectNode = resolveObjectExpression(node.object, scope);
|
|
198
|
+
if (objectNode) {
|
|
199
|
+
const propName = node.property?.name || node.property?.value;
|
|
200
|
+
if (propName) {
|
|
201
|
+
const prop = objectNode.properties.find(p => p.key?.name === propName || p.key?.value === propName);
|
|
202
|
+
if (prop?.value) {
|
|
203
|
+
return resolveRoutesArray(prop.value, scope);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return null;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const resolveObjectExpression = (node, scope) => {
|
|
213
|
+
if (!node) return null;
|
|
214
|
+
if (node.type === 'ObjectExpression') return node;
|
|
215
|
+
|
|
216
|
+
if (node.type === 'Identifier' && scope) {
|
|
217
|
+
const binding = scope.getBinding(node.name);
|
|
218
|
+
const bindingNode = binding?.path?.node;
|
|
219
|
+
if (bindingNode?.type === 'VariableDeclarator') {
|
|
220
|
+
const init = bindingNode.init;
|
|
221
|
+
if (init?.type === 'ObjectExpression') {
|
|
222
|
+
return init;
|
|
223
|
+
}
|
|
224
|
+
if (init?.type === 'Identifier') {
|
|
225
|
+
return resolveObjectExpression(init, binding.path.scope);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
|
|
230
|
+
const parent = binding.path.parentPath?.node;
|
|
231
|
+
if (parent?.type === 'ImportDeclaration') {
|
|
232
|
+
const importedName = bindingNode.type === 'ImportDefaultSpecifier'
|
|
233
|
+
? 'default'
|
|
234
|
+
: bindingNode.imported?.name;
|
|
235
|
+
const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
|
|
236
|
+
return resolveObjectExpression(importedNode, null);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return null;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const resolveStringValue = (node, scope) => {
|
|
245
|
+
if (!node) return null;
|
|
246
|
+
if (node.type === 'StringLiteral') return node.value;
|
|
247
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
248
|
+
return node.quasis.map(q => q.value.cooked).join('');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (node.type === 'Identifier' && scope) {
|
|
252
|
+
const binding = scope.getBinding(node.name);
|
|
253
|
+
const bindingNode = binding?.path?.node;
|
|
254
|
+
if (bindingNode?.type === 'VariableDeclarator') {
|
|
255
|
+
return resolveStringValue(bindingNode.init, binding.path.scope);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (bindingNode?.type === 'ImportSpecifier' || bindingNode?.type === 'ImportDefaultSpecifier') {
|
|
259
|
+
const parent = binding.path.parentPath?.node;
|
|
260
|
+
if (parent?.type === 'ImportDeclaration') {
|
|
261
|
+
const importedName = bindingNode.type === 'ImportDefaultSpecifier'
|
|
262
|
+
? 'default'
|
|
263
|
+
: bindingNode.imported?.name;
|
|
264
|
+
const importedNode = resolveImportedValue(parent.source.value, importedName, componentFilePath);
|
|
265
|
+
return resolveStringValue(importedNode, null);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (node.type === 'MemberExpression' && scope) {
|
|
271
|
+
const objectNode = resolveObjectExpression(node.object, scope);
|
|
272
|
+
if (objectNode) {
|
|
273
|
+
const propName = node.property?.name || node.property?.value;
|
|
274
|
+
if (propName) {
|
|
275
|
+
const prop = objectNode.properties.find(p => p.key?.name === propName || p.key?.value === propName);
|
|
276
|
+
if (prop?.value) {
|
|
277
|
+
return resolveStringValue(prop.value, scope);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return null;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const resolveImportedValue = (importPath, importedName, fromFilePath) => {
|
|
287
|
+
if (!fromFilePath) return null;
|
|
288
|
+
|
|
289
|
+
const baseDir = path.dirname(fromFilePath);
|
|
290
|
+
const resolvedPath = resolveImportPath(importPath, baseDir);
|
|
291
|
+
if (!resolvedPath) {
|
|
292
|
+
console.warn(`⚠️ Cannot resolve import for MultiRoute routes: ${importPath}`);
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const cacheKey = `${resolvedPath}:${importedName || 'default'}`;
|
|
297
|
+
if (!resolveImportedValue.cache) {
|
|
298
|
+
resolveImportedValue.cache = new Map();
|
|
299
|
+
}
|
|
300
|
+
if (resolveImportedValue.cache.has(cacheKey)) {
|
|
301
|
+
return resolveImportedValue.cache.get(cacheKey);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const source = fs.readFileSync(resolvedPath, 'utf-8');
|
|
306
|
+
const importAst = parse(source, {
|
|
307
|
+
sourceType: 'module',
|
|
308
|
+
plugins: ['jsx']
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const topLevelBindings = new Map();
|
|
312
|
+
for (const node of importAst.program.body) {
|
|
313
|
+
if (node.type === 'VariableDeclaration') {
|
|
314
|
+
node.declarations.forEach(decl => {
|
|
315
|
+
if (decl.id?.type === 'Identifier') {
|
|
316
|
+
topLevelBindings.set(decl.id.name, decl.init);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let exportNode = null;
|
|
323
|
+
for (const node of importAst.program.body) {
|
|
324
|
+
if (node.type === 'ExportDefaultDeclaration' && importedName === 'default') {
|
|
325
|
+
exportNode = node.declaration;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
330
|
+
if (node.declaration?.type === 'VariableDeclaration') {
|
|
331
|
+
for (const decl of node.declaration.declarations) {
|
|
332
|
+
if (decl.id?.name === importedName) {
|
|
333
|
+
exportNode = decl.init;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!exportNode && node.specifiers?.length) {
|
|
340
|
+
const specifier = node.specifiers.find(s => s.exported?.name === importedName);
|
|
341
|
+
if (specifier && specifier.local?.name) {
|
|
342
|
+
exportNode = topLevelBindings.get(specifier.local.name) || null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (exportNode) break;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (exportNode?.type === 'Identifier') {
|
|
351
|
+
exportNode = topLevelBindings.get(exportNode.name) || exportNode;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
resolveImportedValue.cache.set(cacheKey, exportNode || null);
|
|
355
|
+
return exportNode || null;
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.warn(`⚠️ Error resolving import ${importPath}: ${error.message}`);
|
|
358
|
+
resolveImportedValue.cache.set(cacheKey, null);
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const resolveImportPath = (importPath, baseDir) => {
|
|
364
|
+
if (!importPath.startsWith('.')) return null;
|
|
365
|
+
|
|
366
|
+
const resolvedBase = path.resolve(baseDir, importPath);
|
|
367
|
+
const extensions = ['.js', '.mjs', '.cjs', '.json'];
|
|
368
|
+
|
|
369
|
+
if (fs.existsSync(resolvedBase) && fs.statSync(resolvedBase).isFile()) {
|
|
370
|
+
return resolvedBase;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!path.extname(resolvedBase)) {
|
|
374
|
+
for (const ext of extensions) {
|
|
375
|
+
const candidate = resolvedBase + ext;
|
|
376
|
+
if (fs.existsSync(candidate)) {
|
|
377
|
+
return candidate;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return null;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const addMultiRouteDependencies = (routesArrayNode, scope) => {
|
|
386
|
+
if (!routesArrayNode || routesArrayNode.type !== 'ArrayExpression') return;
|
|
387
|
+
|
|
388
|
+
routesArrayNode.elements.forEach(routeElement => {
|
|
389
|
+
if (!routeElement) return;
|
|
390
|
+
|
|
391
|
+
if (routeElement.type === 'SpreadElement') {
|
|
392
|
+
const spreadArray = resolveRoutesArray(routeElement.argument, scope);
|
|
393
|
+
addMultiRouteDependencies(spreadArray, scope);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const routeObject = resolveObjectExpression(routeElement, scope) || routeElement;
|
|
398
|
+
if (routeObject?.type === 'ObjectExpression') {
|
|
399
|
+
const componentProp = routeObject.properties.find(p => p.key?.name === 'component');
|
|
400
|
+
if (componentProp?.value) {
|
|
401
|
+
const componentName = resolveStringValue(componentProp.value, scope);
|
|
402
|
+
if (componentName) {
|
|
403
|
+
dependencies.add(componentName);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
|
|
146
410
|
try {
|
|
147
411
|
const ast = parse(code, {
|
|
148
412
|
sourceType: 'module',
|
|
@@ -168,15 +432,9 @@ export default class DependencyAnalyzer {
|
|
|
168
432
|
|
|
169
433
|
// Extract routes from MultiRoute props
|
|
170
434
|
const routesProp = args[1].properties.find(p => p.key?.name === 'routes');
|
|
171
|
-
if (routesProp
|
|
172
|
-
routesProp.value.
|
|
173
|
-
|
|
174
|
-
const componentProp = routeElement.properties.find(p => p.key?.name === 'component');
|
|
175
|
-
if (componentProp?.value?.type === 'StringLiteral') {
|
|
176
|
-
dependencies.add(componentProp.value.value);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
});
|
|
435
|
+
if (routesProp) {
|
|
436
|
+
const routesArrayNode = resolveRoutesArray(routesProp.value, path.scope);
|
|
437
|
+
addMultiRouteDependencies(routesArrayNode);
|
|
180
438
|
}
|
|
181
439
|
}
|
|
182
440
|
// Regular slice.build() calls
|
|
@@ -418,4 +676,4 @@ export default class DependencyAnalyzer {
|
|
|
418
676
|
console.log(` ${i + 1}. ${comp.name} - ${comp.routes} routes - ${(comp.size / 1024).toFixed(1)} KB`);
|
|
419
677
|
});
|
|
420
678
|
}
|
|
421
|
-
}
|
|
679
|
+
}
|
|
@@ -95,7 +95,7 @@ class UpdateManager {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
console.log('');
|
|
98
|
-
Print.warning('📦 Available Updates:');
|
|
98
|
+
Print.warning('📦 Available Updates:');
|
|
99
99
|
console.log('');
|
|
100
100
|
|
|
101
101
|
updateInfo.updates.forEach(pkg => {
|
|
@@ -145,17 +145,17 @@ class UpdateManager {
|
|
|
145
145
|
const answers = await inquirer.prompt([
|
|
146
146
|
{
|
|
147
147
|
type: 'checkbox',
|
|
148
|
-
name: 'packages',
|
|
149
|
-
message: 'Which packages do you want to update?',
|
|
150
|
-
choices,
|
|
151
|
-
validate: (answer) => {
|
|
152
|
-
if (answer.length === 0) {
|
|
153
|
-
return 'You must select at least one package';
|
|
154
|
-
}
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
]);
|
|
148
|
+
name: 'packages',
|
|
149
|
+
message: 'Which packages do you want to update?',
|
|
150
|
+
choices,
|
|
151
|
+
validate: (answer) => {
|
|
152
|
+
if (answer.length === 0) {
|
|
153
|
+
return 'You must select at least one package';
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]);
|
|
159
159
|
|
|
160
160
|
return answers.packages;
|
|
161
161
|
}
|
|
@@ -229,25 +229,25 @@ class UpdateManager {
|
|
|
229
229
|
const results = [];
|
|
230
230
|
|
|
231
231
|
for (const packageName of packages) {
|
|
232
|
-
const spinner = ora(`Updating ${packageName}...`).start();
|
|
232
|
+
const spinner = ora(`Updating ${packageName}...`).start();
|
|
233
233
|
|
|
234
234
|
try {
|
|
235
235
|
const result = await this.updatePackage(packageName);
|
|
236
236
|
|
|
237
|
-
if (result.success) {
|
|
238
|
-
spinner.succeed(`${packageName} updated successfully`);
|
|
239
|
-
results.push({ packageName, success: true });
|
|
240
|
-
} else {
|
|
241
|
-
spinner.fail(`Error updating ${packageName}`);
|
|
242
|
-
Print.error(`Details: ${result.error}`);
|
|
243
|
-
results.push({ packageName, success: false, error: result.error });
|
|
244
|
-
}
|
|
245
|
-
} catch (error) {
|
|
246
|
-
spinner.fail(`Error updating ${packageName}`);
|
|
247
|
-
Print.error(`Details: ${error.message}`);
|
|
248
|
-
results.push({ packageName, success: false, error: error.message });
|
|
249
|
-
}
|
|
250
|
-
}
|
|
237
|
+
if (result.success) {
|
|
238
|
+
spinner.succeed(`${packageName} updated successfully`);
|
|
239
|
+
results.push({ packageName, success: true });
|
|
240
|
+
} else {
|
|
241
|
+
spinner.fail(`Error updating ${packageName}`);
|
|
242
|
+
Print.error(`Details: ${result.error}`);
|
|
243
|
+
results.push({ packageName, success: false, error: result.error });
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
spinner.fail(`Error updating ${packageName}`);
|
|
247
|
+
Print.error(`Details: ${error.message}`);
|
|
248
|
+
results.push({ packageName, success: false, error: error.message });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
251
|
|
|
252
252
|
return results;
|
|
253
253
|
}
|
|
@@ -256,26 +256,26 @@ class UpdateManager {
|
|
|
256
256
|
* Main method to check and prompt for updates
|
|
257
257
|
*/
|
|
258
258
|
async checkAndPromptUpdates(options = {}) {
|
|
259
|
-
const spinner = ora('Checking for updates...').start();
|
|
259
|
+
const spinner = ora('Checking for updates...').start();
|
|
260
260
|
|
|
261
261
|
try {
|
|
262
262
|
const updateInfo = await this.checkForUpdates();
|
|
263
263
|
spinner.stop();
|
|
264
264
|
|
|
265
|
-
if (!updateInfo) {
|
|
266
|
-
Print.error('Could not check for updates. Verify your internet connection.');
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
265
|
+
if (!updateInfo) {
|
|
266
|
+
Print.error('Could not check for updates. Verify your internet connection.');
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
269
|
|
|
270
|
-
if (updateInfo.allCurrent) {
|
|
271
|
-
Print.success('✅ All components are up to date!');
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
270
|
+
if (updateInfo.allCurrent) {
|
|
271
|
+
Print.success('✅ All components are up to date!');
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
274
|
|
|
275
|
-
if (!updateInfo.hasUpdates) {
|
|
276
|
-
Print.success('✅ All components are up to date!');
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
275
|
+
if (!updateInfo.hasUpdates) {
|
|
276
|
+
Print.success('✅ All components are up to date!');
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
279
|
|
|
280
280
|
// Display available updates
|
|
281
281
|
this.displayUpdates(updateInfo);
|
|
@@ -283,20 +283,20 @@ class UpdateManager {
|
|
|
283
283
|
// Get packages to update
|
|
284
284
|
const packagesToUpdate = await this.promptForUpdates(updateInfo, options);
|
|
285
285
|
|
|
286
|
-
if (!packagesToUpdate || packagesToUpdate.length === 0) {
|
|
287
|
-
Print.info('No packages selected for update.');
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
286
|
+
if (!packagesToUpdate || packagesToUpdate.length === 0) {
|
|
287
|
+
Print.info('No packages selected for update.');
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
290
|
|
|
291
291
|
// Show plan and confirm installation if not auto-confirmed
|
|
292
292
|
let plan = await this.buildUpdatePlan(packagesToUpdate);
|
|
293
293
|
console.log('');
|
|
294
|
-
Print.info('🧭 Update plan:');
|
|
295
|
-
plan.forEach(item => {
|
|
296
|
-
const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
|
|
297
|
-
console.log(` • ${item.package} → ${where}`);
|
|
298
|
-
console.log(` ${item.command}`);
|
|
299
|
-
});
|
|
294
|
+
Print.info('🧭 Update plan:');
|
|
295
|
+
plan.forEach(item => {
|
|
296
|
+
const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
|
|
297
|
+
console.log(` • ${item.package} → ${where}`);
|
|
298
|
+
console.log(` ${item.command}`);
|
|
299
|
+
});
|
|
300
300
|
console.log('');
|
|
301
301
|
|
|
302
302
|
const cliInfo = await this.detectCliInstall();
|
|
@@ -306,47 +306,47 @@ class UpdateManager {
|
|
|
306
306
|
{
|
|
307
307
|
type: 'confirm',
|
|
308
308
|
name: 'addCli',
|
|
309
|
-
message: 'Global CLI detected. Add the global CLI update to the plan?',
|
|
310
|
-
default: true
|
|
311
|
-
}
|
|
312
|
-
]);
|
|
309
|
+
message: 'Global CLI detected. Add the global CLI update to the plan?',
|
|
310
|
+
default: true
|
|
311
|
+
}
|
|
312
|
+
]);
|
|
313
313
|
if (addCli) {
|
|
314
314
|
packagesToUpdate.push('slicejs-cli');
|
|
315
315
|
plan = await this.buildUpdatePlan(packagesToUpdate);
|
|
316
316
|
console.log('');
|
|
317
|
-
Print.info('🧭 Updated plan:');
|
|
318
|
-
plan.forEach(item => {
|
|
319
|
-
const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
|
|
320
|
-
console.log(` • ${item.package} → ${where}`);
|
|
321
|
-
console.log(` ${item.command}`);
|
|
322
|
-
});
|
|
317
|
+
Print.info('🧭 Updated plan:');
|
|
318
|
+
plan.forEach(item => {
|
|
319
|
+
const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
|
|
320
|
+
console.log(` • ${item.package} → ${where}`);
|
|
321
|
+
console.log(` ${item.command}`);
|
|
322
|
+
});
|
|
323
323
|
console.log('');
|
|
324
324
|
}
|
|
325
325
|
} else {
|
|
326
|
-
Print.warning('Global CLI detected. It is recommended to update slicejs-cli globally to keep aligned with the framework.');
|
|
327
|
-
console.log(' Suggestion: npm install -g slicejs-cli@latest');
|
|
328
|
-
console.log('');
|
|
329
|
-
}
|
|
330
|
-
}
|
|
326
|
+
Print.warning('Global CLI detected. It is recommended to update slicejs-cli globally to keep aligned with the framework.');
|
|
327
|
+
console.log(' Suggestion: npm install -g slicejs-cli@latest');
|
|
328
|
+
console.log('');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
331
|
|
|
332
332
|
if (!options.yes && !options.cli && !options.framework) {
|
|
333
333
|
const { confirm } = await inquirer.prompt([
|
|
334
|
-
{
|
|
335
|
-
type: 'confirm',
|
|
336
|
-
name: 'confirm',
|
|
337
|
-
message: 'Do you want to proceed with the update according to the plan shown?',
|
|
338
|
-
default: true
|
|
339
|
-
}
|
|
340
|
-
]);
|
|
334
|
+
{
|
|
335
|
+
type: 'confirm',
|
|
336
|
+
name: 'confirm',
|
|
337
|
+
message: 'Do you want to proceed with the update according to the plan shown?',
|
|
338
|
+
default: true
|
|
339
|
+
}
|
|
340
|
+
]);
|
|
341
341
|
|
|
342
342
|
if (!confirm) {
|
|
343
|
-
Print.info('Update cancelled.');
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
343
|
+
Print.info('Update cancelled.');
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
347
|
|
|
348
348
|
console.log(''); // Line break
|
|
349
|
-
Print.info('📥 Installing updates...');
|
|
349
|
+
Print.info('📥 Installing updates...');
|
|
350
350
|
console.log('');
|
|
351
351
|
|
|
352
352
|
// Install updates
|
|
@@ -357,24 +357,24 @@ class UpdateManager {
|
|
|
357
357
|
const successCount = results.filter(r => r.success).length;
|
|
358
358
|
const failCount = results.filter(r => !r.success).length;
|
|
359
359
|
|
|
360
|
-
if (failCount === 0) {
|
|
361
|
-
Print.success(`✅ ${successCount} package(s) updated successfully!`);
|
|
362
|
-
} else {
|
|
363
|
-
Print.warning(`⚠️ ${successCount} successful, ${failCount} failed`);
|
|
364
|
-
}
|
|
360
|
+
if (failCount === 0) {
|
|
361
|
+
Print.success(`✅ ${successCount} package(s) updated successfully!`);
|
|
362
|
+
} else {
|
|
363
|
+
Print.warning(`⚠️ ${successCount} successful, ${failCount} failed`);
|
|
364
|
+
}
|
|
365
365
|
|
|
366
366
|
if (successCount > 0) {
|
|
367
367
|
console.log('');
|
|
368
|
-
Print.info('💡 It is recommended to restart the development server if it is running.');
|
|
369
|
-
}
|
|
368
|
+
Print.info('💡 It is recommended to restart the development server if it is running.');
|
|
369
|
+
}
|
|
370
370
|
|
|
371
371
|
return failCount === 0;
|
|
372
372
|
|
|
373
373
|
} catch (error) {
|
|
374
374
|
spinner.stop();
|
|
375
|
-
Print.error(`Error during update: ${error.message}`);
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
375
|
+
Print.error(`Error during update: ${error.message}`);
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slicejs-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.1",
|
|
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);
|