slicejs-cli 2.7.4 → 2.7.6
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/client.js +50 -8
- package/commands/bundle/bundle.js +226 -0
- package/commands/doctor/doctor.js +11 -11
- package/commands/getComponent/getComponent.js +74 -36
- package/commands/init/init.js +56 -57
- package/commands/startServer/startServer.js +6 -2
- package/commands/utils/PathHelper.js +10 -0
- package/commands/utils/VersionChecker.js +2 -2
- package/commands/utils/bundling/BundleGenerator.js +389 -0
- package/commands/utils/bundling/DependencyAnalyzer.js +340 -0
- package/commands/utils/updateManager.js +87 -87
- package/package.json +1 -1
- package/refactor.md +271 -0
package/client.js
CHANGED
|
@@ -18,6 +18,7 @@ import { exec } from "child_process";
|
|
|
18
18
|
import { promisify } from "util";
|
|
19
19
|
import validations from "./commands/Validations.js";
|
|
20
20
|
import Print from "./commands/Print.js";
|
|
21
|
+
import bundle, { cleanBundles, bundleInfo } from './commands/bundle/bundle.js';
|
|
21
22
|
|
|
22
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
24
|
|
|
@@ -137,12 +138,21 @@ sliceClient
|
|
|
137
138
|
.description("Start development server")
|
|
138
139
|
.option("-p, --port <port>", "Port for development server", 3000)
|
|
139
140
|
.option("-w, --watch", "Enable watch mode for file changes")
|
|
141
|
+
.option("-b, --bundled", "Generate bundles before starting server")
|
|
140
142
|
.action(async (options) => {
|
|
141
143
|
await runWithVersionCheck(async () => {
|
|
144
|
+
// Si se solicita bundles, generarlos primero
|
|
145
|
+
if (options.bundled) {
|
|
146
|
+
Print.info("Generating bundles before starting server...");
|
|
147
|
+
await bundle({ verbose: false });
|
|
148
|
+
Print.newLine();
|
|
149
|
+
}
|
|
150
|
+
|
|
142
151
|
await startServer({
|
|
143
|
-
mode: 'development',
|
|
152
|
+
mode: options.bundled ? 'bundled' : 'development',
|
|
144
153
|
port: parseInt(options.port),
|
|
145
|
-
watch: options.watch
|
|
154
|
+
watch: options.watch,
|
|
155
|
+
bundled: options.bundled
|
|
146
156
|
});
|
|
147
157
|
});
|
|
148
158
|
});
|
|
@@ -153,12 +163,21 @@ sliceClient
|
|
|
153
163
|
.description("Start development server (alias for dev)")
|
|
154
164
|
.option("-p, --port <port>", "Port for server", 3000)
|
|
155
165
|
.option("-w, --watch", "Enable watch mode for file changes")
|
|
166
|
+
.option("-b, --bundled", "Generate bundles before starting server")
|
|
156
167
|
.action(async (options) => {
|
|
157
168
|
await runWithVersionCheck(async () => {
|
|
169
|
+
// Si se solicita bundles, generarlos primero
|
|
170
|
+
if (options.bundled) {
|
|
171
|
+
Print.info("Generating bundles before starting server...");
|
|
172
|
+
await bundle({ verbose: false });
|
|
173
|
+
Print.newLine();
|
|
174
|
+
}
|
|
175
|
+
|
|
158
176
|
await startServer({
|
|
159
|
-
mode: 'development',
|
|
177
|
+
mode: options.bundled ? 'bundled' : 'development',
|
|
160
178
|
port: parseInt(options.port),
|
|
161
|
-
watch: options.watch
|
|
179
|
+
watch: options.watch,
|
|
180
|
+
bundled: options.bundled
|
|
162
181
|
});
|
|
163
182
|
});
|
|
164
183
|
});
|
|
@@ -434,13 +453,35 @@ sliceClient
|
|
|
434
453
|
subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage()
|
|
435
454
|
});
|
|
436
455
|
|
|
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
|
+
|
|
437
476
|
// Custom help - SIMPLIFICADO para development only
|
|
438
477
|
sliceClient.addHelpText('after', `
|
|
439
478
|
Common Usage Examples:
|
|
440
479
|
slice init - Initialize new Slice.js project
|
|
441
480
|
slice dev - Start development server
|
|
442
481
|
slice start - Start development server (same as dev)
|
|
443
|
-
slice
|
|
482
|
+
slice dev --bundled - Generate bundles then start server
|
|
483
|
+
slice start --bundled - Same as above (bundle -> start)
|
|
484
|
+
slice get Button Card Input - Install Visual components from registry
|
|
444
485
|
slice get FetchManager -s - Install Service component from registry
|
|
445
486
|
slice browse - Browse all available components
|
|
446
487
|
slice sync - Update local components to latest versions
|
|
@@ -456,9 +497,10 @@ Command Categories:
|
|
|
456
497
|
• version, update, doctor - Maintenance commands
|
|
457
498
|
|
|
458
499
|
Development Workflow:
|
|
459
|
-
• slice init
|
|
460
|
-
• slice dev
|
|
461
|
-
• slice start
|
|
500
|
+
• slice init - Initialize project
|
|
501
|
+
• slice dev - Start development server (serves from /src)
|
|
502
|
+
• slice start - Alternative to dev command
|
|
503
|
+
• slice dev --bundled - Start with bundles (bundle -> start)
|
|
462
504
|
|
|
463
505
|
Note: Production builds are disabled. Use development mode for all workflows.
|
|
464
506
|
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// cli/commands/bundle/bundle.js
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import DependencyAnalyzer from '../utils/bundling/DependencyAnalyzer.js';
|
|
5
|
+
import BundleGenerator from '../utils/bundling/BundleGenerator.js';
|
|
6
|
+
import Print from '..//Print.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Main bundling command
|
|
10
|
+
*/
|
|
11
|
+
export default async function bundle(options = {}) {
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
const projectRoot = process.cwd();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
Print.title('📦 Slice.js Bundle Generator');
|
|
17
|
+
Print.newLine();
|
|
18
|
+
|
|
19
|
+
// Validate that it's a Slice.js project
|
|
20
|
+
await validateProject(projectRoot);
|
|
21
|
+
|
|
22
|
+
// Phase 1: Analysis
|
|
23
|
+
Print.buildProgress('Analyzing project...');
|
|
24
|
+
const analyzer = new DependencyAnalyzer(import.meta.url);
|
|
25
|
+
const analysisData = await analyzer.analyze();
|
|
26
|
+
|
|
27
|
+
// Show report if in verbose mode
|
|
28
|
+
if (options.verbose || options.analyze) {
|
|
29
|
+
Print.newLine();
|
|
30
|
+
analyzer.generateReport(analysisData.metrics);
|
|
31
|
+
Print.newLine();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// If only analysis is requested, finish here
|
|
35
|
+
if (options.analyze) {
|
|
36
|
+
Print.success('Analysis completed');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Phase 2: Bundle generation
|
|
41
|
+
Print.buildProgress('Generating bundles...');
|
|
42
|
+
const generator = new BundleGenerator(import.meta.url, analysisData);
|
|
43
|
+
const result = await generator.generate();
|
|
44
|
+
|
|
45
|
+
// Phase 3: Save configuration
|
|
46
|
+
Print.buildProgress('Saving configuration...');
|
|
47
|
+
await generator.saveBundleConfig(result.config);
|
|
48
|
+
|
|
49
|
+
// Phase 4: Summary
|
|
50
|
+
Print.newLine();
|
|
51
|
+
printSummary(result, startTime);
|
|
52
|
+
|
|
53
|
+
// Show next step
|
|
54
|
+
Print.newLine();
|
|
55
|
+
Print.info('💡 Next step:');
|
|
56
|
+
console.log(' Update your Slice.js to use the generated bundles');
|
|
57
|
+
console.log(' Bundles will load automatically in production\n');
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
Print.error('Error generating bundles:', error.message);
|
|
61
|
+
console.error('\n📍 Complete stack trace:');
|
|
62
|
+
console.error(error.stack);
|
|
63
|
+
|
|
64
|
+
if (error.code) {
|
|
65
|
+
console.error('\n📍 Error code:', error.code);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validates that the project has the correct structure
|
|
74
|
+
*/
|
|
75
|
+
async function validateProject(projectRoot) {
|
|
76
|
+
const requiredPaths = [
|
|
77
|
+
'src/Components/components.js',
|
|
78
|
+
'src/routes.js'
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const reqPath of requiredPaths) {
|
|
82
|
+
const fullPath = path.join(projectRoot, reqPath);
|
|
83
|
+
if (!await fs.pathExists(fullPath)) {
|
|
84
|
+
throw new Error(`Required file not found: ${reqPath}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Prints summary of generated bundles
|
|
91
|
+
*/
|
|
92
|
+
function printSummary(result, startTime) {
|
|
93
|
+
const { bundles, config, files } = result;
|
|
94
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
95
|
+
|
|
96
|
+
Print.success(`Bundles generated in ${duration}s\n`);
|
|
97
|
+
|
|
98
|
+
// Critical bundle
|
|
99
|
+
if (bundles.critical.components.length > 0) {
|
|
100
|
+
Print.info('📦 Critical Bundle:');
|
|
101
|
+
console.log(` Components: ${bundles.critical.components.length}`);
|
|
102
|
+
console.log(` Size: ${(bundles.critical.size / 1024).toFixed(1)} KB`);
|
|
103
|
+
console.log(` File: ${bundles.critical.file}\n`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Route bundles
|
|
107
|
+
const routeCount = Object.keys(bundles.routes).length;
|
|
108
|
+
if (routeCount > 0) {
|
|
109
|
+
Print.info(`📦 Route Bundles (${routeCount}):`);
|
|
110
|
+
|
|
111
|
+
for (const [key, bundle] of Object.entries(bundles.routes)) {
|
|
112
|
+
console.log(` ${key}:`);
|
|
113
|
+
console.log(` Route: ${bundle.path}`);
|
|
114
|
+
console.log(` Components: ${bundle.components.length}`);
|
|
115
|
+
console.log(` Size: ${(bundle.size / 1024).toFixed(1)} KB`);
|
|
116
|
+
console.log(` File: ${bundle.file}`);
|
|
117
|
+
}
|
|
118
|
+
Print.newLine();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Global statistics
|
|
122
|
+
Print.info('📊 Statistics:');
|
|
123
|
+
console.log(` Strategy: ${config.strategy}`);
|
|
124
|
+
console.log(` Total components: ${config.stats.totalComponents}`);
|
|
125
|
+
console.log(` Shared components: ${config.stats.sharedComponents}`);
|
|
126
|
+
console.log(` Generated files: ${files.length}`);
|
|
127
|
+
|
|
128
|
+
// Calculate estimated improvement
|
|
129
|
+
const beforeRequests = config.stats.totalComponents;
|
|
130
|
+
const afterRequests = files.length;
|
|
131
|
+
const improvement = Math.round((1 - afterRequests / beforeRequests) * 100);
|
|
132
|
+
|
|
133
|
+
console.log(` Request reduction: ${improvement}% (${beforeRequests} → ${afterRequests})`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Subcommand: Clean bundles
|
|
138
|
+
*/
|
|
139
|
+
export async function cleanBundles() {
|
|
140
|
+
const projectRoot = process.cwd();
|
|
141
|
+
const srcPath = path.join(projectRoot, 'src');
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
Print.title('🧹 Cleaning bundles...');
|
|
145
|
+
|
|
146
|
+
const files = await fs.readdir(srcPath);
|
|
147
|
+
const bundleFiles = files.filter(f => f.startsWith('slice-bundle.'));
|
|
148
|
+
|
|
149
|
+
if (bundleFiles.length === 0) {
|
|
150
|
+
Print.warning('No bundles found to clean');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const file of bundleFiles) {
|
|
155
|
+
await fs.remove(path.join(srcPath, file));
|
|
156
|
+
console.log(` ✓ Deleted: ${file}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Remove config
|
|
160
|
+
const configPath = path.join(srcPath, 'bundle.config.json');
|
|
161
|
+
if (await fs.pathExists(configPath)) {
|
|
162
|
+
await fs.remove(configPath);
|
|
163
|
+
console.log(` ✓ Deleted: bundle.config.json`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
Print.newLine();
|
|
167
|
+
Print.success(`${bundleFiles.length} files deleted`);
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
Print.error('Error cleaning bundles:', error.message);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Subcommand: Bundle information
|
|
177
|
+
*/
|
|
178
|
+
export async function bundleInfo() {
|
|
179
|
+
const projectRoot = process.cwd();
|
|
180
|
+
const configPath = path.join(projectRoot, 'src/bundle.config.json');
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
if (!await fs.pathExists(configPath)) {
|
|
184
|
+
Print.warning('Bundle configuration not found');
|
|
185
|
+
Print.info('Run "slice bundle" to generate bundles');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const config = await fs.readJson(configPath);
|
|
190
|
+
|
|
191
|
+
Print.title('📦 Bundle Information');
|
|
192
|
+
Print.newLine();
|
|
193
|
+
|
|
194
|
+
Print.info('Configuration:');
|
|
195
|
+
console.log(` Version: ${config.version}`);
|
|
196
|
+
console.log(` Strategy: ${config.strategy}`);
|
|
197
|
+
console.log(` Generated: ${new Date(config.generated).toLocaleString()}`);
|
|
198
|
+
Print.newLine();
|
|
199
|
+
|
|
200
|
+
Print.info('Statistics:');
|
|
201
|
+
console.log(` Total components: ${config.stats.totalComponents}`);
|
|
202
|
+
console.log(` Total routes: ${config.stats.totalRoutes}`);
|
|
203
|
+
console.log(` Shared components: ${config.stats.sharedComponents} (${config.stats.sharedPercentage}%)`);
|
|
204
|
+
console.log(` Total size: ${(config.stats.totalSize / 1024).toFixed(1)} KB`);
|
|
205
|
+
Print.newLine();
|
|
206
|
+
|
|
207
|
+
Print.info('Bundles:');
|
|
208
|
+
|
|
209
|
+
// Critical
|
|
210
|
+
if (config.bundles.critical) {
|
|
211
|
+
console.log(` Critical: ${config.bundles.critical.components.length} components, ${(config.bundles.critical.size / 1024).toFixed(1)} KB`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Routes
|
|
215
|
+
const routeCount = Object.keys(config.bundles.routes).length;
|
|
216
|
+
console.log(` Routes: ${routeCount} bundles`);
|
|
217
|
+
|
|
218
|
+
for (const [key, bundle] of Object.entries(config.bundles.routes)) {
|
|
219
|
+
console.log(` ${key}: ${bundle.components.length} components, ${(bundle.size / 1024).toFixed(1)} KB`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
} catch (error) {
|
|
223
|
+
Print.error('Error reading information:', error.message);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -9,6 +9,7 @@ import inquirer from 'inquirer';
|
|
|
9
9
|
import { exec } from 'child_process';
|
|
10
10
|
import { promisify } from 'util';
|
|
11
11
|
import { getProjectRoot, getSrcPath, getApiPath, getConfigPath, getPath } from '../utils/PathHelper.js';
|
|
12
|
+
import updateManager from '../utils/updateManager.js';
|
|
12
13
|
|
|
13
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
|
|
@@ -148,7 +149,8 @@ async function checkPort() {
|
|
|
148
149
|
* Verifica dependencias en package.json
|
|
149
150
|
*/
|
|
150
151
|
async function checkDependencies() {
|
|
151
|
-
const
|
|
152
|
+
const projectRoot = getProjectRoot(import.meta.url);
|
|
153
|
+
const packagePath = path.join(projectRoot, 'package.json');
|
|
152
154
|
|
|
153
155
|
if (!await fs.pathExists(packagePath)) {
|
|
154
156
|
return {
|
|
@@ -160,18 +162,18 @@ async function checkDependencies() {
|
|
|
160
162
|
|
|
161
163
|
try {
|
|
162
164
|
const pkg = await fs.readJson(packagePath);
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
+
const hasFrameworkDep = pkg.dependencies?.['slicejs-web-framework'] || pkg.devDependencies?.['slicejs-web-framework'];
|
|
166
|
+
const frameworkNodePath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'package.json');
|
|
167
|
+
const hasFrameworkNode = await fs.pathExists(frameworkNodePath);
|
|
168
|
+
const hasFramework = !!(hasFrameworkDep || hasFrameworkNode);
|
|
165
169
|
|
|
166
|
-
if (
|
|
170
|
+
if (hasFramework) {
|
|
167
171
|
return {
|
|
168
172
|
pass: true,
|
|
169
|
-
message: '
|
|
173
|
+
message: 'Required framework dependency is installed'
|
|
170
174
|
};
|
|
171
175
|
} else {
|
|
172
|
-
const missing = [];
|
|
173
|
-
if (!hasCli) missing.push('slicejs-cli');
|
|
174
|
-
if (!hasFramework) missing.push('slicejs-web-framework');
|
|
176
|
+
const missing = ['slicejs-web-framework'];
|
|
175
177
|
|
|
176
178
|
return {
|
|
177
179
|
warn: true,
|
|
@@ -334,9 +336,7 @@ export default async function runDiagnostics() {
|
|
|
334
336
|
if (confirmInstall) {
|
|
335
337
|
for (const pkg of depsResult.missing) {
|
|
336
338
|
try {
|
|
337
|
-
const cmd =
|
|
338
|
-
? 'npm install -D slicejs-cli@latest'
|
|
339
|
-
: 'npm install slicejs-web-framework@latest';
|
|
339
|
+
const cmd = 'npm install slicejs-web-framework@latest';
|
|
340
340
|
Print.info(`Installing ${pkg}...`);
|
|
341
341
|
await execAsync(cmd, { cwd: projectRoot });
|
|
342
342
|
Print.success(`${pkg} installed`);
|
|
@@ -7,6 +7,7 @@ import inquirer from "inquirer";
|
|
|
7
7
|
import validations from "../Validations.js";
|
|
8
8
|
import Print from "../Print.js";
|
|
9
9
|
import { getConfigPath, getComponentsJsPath, getPath } from "../utils/PathHelper.js";
|
|
10
|
+
import ora from "ora";
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
|
|
@@ -188,32 +189,51 @@ filterOfficialComponents(allComponents) {
|
|
|
188
189
|
|
|
189
190
|
const downloadedFiles = [];
|
|
190
191
|
const failedFiles = [];
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
const total = component.files.length;
|
|
193
|
+
let done = 0;
|
|
194
|
+
const spinner = ora(`Downloading ${componentName} 0/${total}`).start();
|
|
195
|
+
const fetchWithRetry = async (url, retries = 3, baseDelay = 500) => {
|
|
196
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
197
|
+
try {
|
|
198
|
+
const response = await fetch(url);
|
|
199
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
200
|
+
return await response.text();
|
|
201
|
+
} catch (e) {
|
|
202
|
+
if (attempt === retries) throw e;
|
|
203
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
204
|
+
await new Promise(r => setTimeout(r, delay));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const worker = async (fileName) => {
|
|
209
|
+
const url = `${DOCS_REPO_BASE_URL}/${category}/${componentName}/${fileName}`;
|
|
195
210
|
const localPath = path.join(targetPath, fileName);
|
|
196
|
-
|
|
197
211
|
try {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
if (!response.ok) {
|
|
201
|
-
Print.downloadError(fileName, `HTTP ${response.status}: ${response.statusText}`);
|
|
202
|
-
failedFiles.push(fileName);
|
|
203
|
-
continue; // ✅ CONTINUAR en lugar de lanzar error
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const content = await response.text();
|
|
212
|
+
const content = await fetchWithRetry(url);
|
|
207
213
|
await fs.writeFile(localPath, content, 'utf8');
|
|
208
214
|
downloadedFiles.push(fileName);
|
|
209
|
-
|
|
210
215
|
Print.downloadSuccess(fileName);
|
|
211
216
|
} catch (error) {
|
|
212
217
|
Print.downloadError(fileName, error.message);
|
|
213
218
|
failedFiles.push(fileName);
|
|
214
|
-
|
|
219
|
+
} finally {
|
|
220
|
+
done += 1;
|
|
221
|
+
spinner.text = `Downloading ${componentName} ${done}/${total}`;
|
|
215
222
|
}
|
|
216
|
-
}
|
|
223
|
+
};
|
|
224
|
+
const runConcurrent = async (items, concurrency = 3) => {
|
|
225
|
+
let index = 0;
|
|
226
|
+
const runners = Array(Math.min(concurrency, items.length)).fill(0).map(async () => {
|
|
227
|
+
while (true) {
|
|
228
|
+
const i = index++;
|
|
229
|
+
if (i >= items.length) break;
|
|
230
|
+
await worker(items[i]);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
await Promise.all(runners);
|
|
234
|
+
};
|
|
235
|
+
await runConcurrent(component.files, 3);
|
|
236
|
+
spinner.stop();
|
|
217
237
|
|
|
218
238
|
// ✅ NUEVO: Solo lanzar error si NO se descargó el archivo principal (.js)
|
|
219
239
|
const mainFileDownloaded = downloadedFiles.some(file => file.endsWith('.js'));
|
|
@@ -311,7 +331,7 @@ filterOfficialComponents(allComponents) {
|
|
|
311
331
|
const availableComponents = this.getAvailableComponents(category);
|
|
312
332
|
|
|
313
333
|
if (!availableComponents[componentName]) {
|
|
314
|
-
throw new Error(`
|
|
334
|
+
throw new Error(`Component '${componentName}' not found in category '${category}' in the official repository`);
|
|
315
335
|
}
|
|
316
336
|
|
|
317
337
|
// ✅ MEJORADO: Detectar si validations tiene acceso a la configuración
|
|
@@ -347,7 +367,7 @@ filterOfficialComponents(allComponents) {
|
|
|
347
367
|
{
|
|
348
368
|
type: 'confirm',
|
|
349
369
|
name: 'overwrite',
|
|
350
|
-
message: `
|
|
370
|
+
message: `The component '${componentName}' already exists locally. Overwrite with the repository version?`,
|
|
351
371
|
default: false
|
|
352
372
|
}
|
|
353
373
|
]);
|
|
@@ -395,16 +415,34 @@ filterOfficialComponents(allComponents) {
|
|
|
395
415
|
async installMultipleComponents(componentNames, category = 'Visual', force = false) {
|
|
396
416
|
const results = [];
|
|
397
417
|
Print.info(`Getting ${componentNames.length} ${category} components from official repository...`);
|
|
398
|
-
|
|
399
|
-
|
|
418
|
+
const total = componentNames.length;
|
|
419
|
+
let done = 0;
|
|
420
|
+
const spinner = ora(`Installing 0/${total}`).start();
|
|
421
|
+
const worker = async (componentName) => {
|
|
400
422
|
try {
|
|
401
423
|
const result = await this.installComponent(componentName, category, force);
|
|
402
424
|
results.push({ name: componentName, success: result });
|
|
403
425
|
} catch (error) {
|
|
404
426
|
Print.componentError(componentName, 'getting', error.message);
|
|
405
427
|
results.push({ name: componentName, success: false, error: error.message });
|
|
428
|
+
} finally {
|
|
429
|
+
done += 1;
|
|
430
|
+
spinner.text = `Installing ${done}/${total}`;
|
|
406
431
|
}
|
|
407
|
-
}
|
|
432
|
+
};
|
|
433
|
+
const runConcurrent = async (items, concurrency = 3) => {
|
|
434
|
+
let index = 0;
|
|
435
|
+
const runners = Array(Math.min(concurrency, items.length)).fill(0).map(async () => {
|
|
436
|
+
while (true) {
|
|
437
|
+
const i = index++;
|
|
438
|
+
if (i >= items.length) break;
|
|
439
|
+
await worker(items[i]);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
await Promise.all(runners);
|
|
443
|
+
};
|
|
444
|
+
await runConcurrent(componentNames, 3);
|
|
445
|
+
spinner.stop();
|
|
408
446
|
|
|
409
447
|
// Summary
|
|
410
448
|
const successful = results.filter(r => r.success).length;
|
|
@@ -492,11 +530,11 @@ filterOfficialComponents(allComponents) {
|
|
|
492
530
|
|
|
493
531
|
displayAvailableComponents() {
|
|
494
532
|
if (!this.componentsRegistry) {
|
|
495
|
-
Print.error('❌
|
|
533
|
+
Print.error('❌ Could not load component registry');
|
|
496
534
|
return;
|
|
497
535
|
}
|
|
498
536
|
|
|
499
|
-
console.log('\n📚
|
|
537
|
+
console.log('\n📚 Available components in the official Slice.js repository:\n');
|
|
500
538
|
|
|
501
539
|
const visualComponents = this.getAvailableComponents('Visual');
|
|
502
540
|
const serviceComponents = this.getAvailableComponents('Service');
|
|
@@ -522,10 +560,10 @@ filterOfficialComponents(allComponents) {
|
|
|
522
560
|
Print.newLine();
|
|
523
561
|
Print.info(`Total: ${Object.keys(visualComponents).length} Visual + ${Object.keys(serviceComponents).length} Service components`);
|
|
524
562
|
|
|
525
|
-
console.log(`\n💡
|
|
526
|
-
console.log(`slice get Button Card Input #
|
|
527
|
-
console.log(`slice get FetchManager --service #
|
|
528
|
-
console.log(`slice sync #
|
|
563
|
+
console.log(`\n💡 Usage examples:`);
|
|
564
|
+
console.log(`slice get Button Card Input # Install Visual components`);
|
|
565
|
+
console.log(`slice get FetchManager --service # Install Service component`);
|
|
566
|
+
console.log(`slice sync # Sync Visual components`);
|
|
529
567
|
}
|
|
530
568
|
|
|
531
569
|
async interactiveInstall() {
|
|
@@ -533,7 +571,7 @@ filterOfficialComponents(allComponents) {
|
|
|
533
571
|
{
|
|
534
572
|
type: 'list',
|
|
535
573
|
name: 'componentType',
|
|
536
|
-
message: '
|
|
574
|
+
message: 'Select the type of component to install from the repository:',
|
|
537
575
|
choices: [
|
|
538
576
|
{ name: '🎨 Visual Components (UI)', value: 'Visual' },
|
|
539
577
|
{ name: '⚙️ Service Components (Logic)', value: 'Service' }
|
|
@@ -552,10 +590,10 @@ filterOfficialComponents(allComponents) {
|
|
|
552
590
|
{
|
|
553
591
|
type: 'list',
|
|
554
592
|
name: 'installMode',
|
|
555
|
-
message: '
|
|
593
|
+
message: 'How do you want to install Visual components?',
|
|
556
594
|
choices: [
|
|
557
|
-
{ name: '
|
|
558
|
-
{ name: '
|
|
595
|
+
{ name: 'Get one', value: 'single' },
|
|
596
|
+
{ name: 'Get multiple', value: 'multiple' }
|
|
559
597
|
]
|
|
560
598
|
}
|
|
561
599
|
]);
|
|
@@ -565,11 +603,11 @@ filterOfficialComponents(allComponents) {
|
|
|
565
603
|
{
|
|
566
604
|
type: 'checkbox',
|
|
567
605
|
name: 'selectedComponents',
|
|
568
|
-
message: '
|
|
606
|
+
message: 'Select Visual components to install from the repository:',
|
|
569
607
|
choices: componentChoices,
|
|
570
608
|
validate: (input) => {
|
|
571
609
|
if (input.length === 0) {
|
|
572
|
-
return '
|
|
610
|
+
return 'You must select at least one component';
|
|
573
611
|
}
|
|
574
612
|
return true;
|
|
575
613
|
}
|
|
@@ -582,7 +620,7 @@ filterOfficialComponents(allComponents) {
|
|
|
582
620
|
{
|
|
583
621
|
type: 'list',
|
|
584
622
|
name: 'selectedComponent',
|
|
585
|
-
message: '
|
|
623
|
+
message: 'Select a Visual component:',
|
|
586
624
|
choices: componentChoices
|
|
587
625
|
}
|
|
588
626
|
]);
|
|
@@ -594,7 +632,7 @@ filterOfficialComponents(allComponents) {
|
|
|
594
632
|
{
|
|
595
633
|
type: 'list',
|
|
596
634
|
name: 'selectedComponent',
|
|
597
|
-
message: '
|
|
635
|
+
message: 'Select a Service component:',
|
|
598
636
|
choices: componentChoices
|
|
599
637
|
}
|
|
600
638
|
]);
|