slicejs-cli 2.1.11 → 2.2.2

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 CHANGED
@@ -6,6 +6,8 @@ import createComponent from "./commands/createComponent/createComponent.js";
6
6
  import listComponents from "./commands/listComponents/listComponents.js";
7
7
  import deleteComponent from "./commands/deleteComponent/deleteComponent.js";
8
8
  import getComponent, { listComponents as listRemoteComponents, syncComponents } from "./commands/getComponent/getComponent.js";
9
+ import buildProduction, { buildCommand, serveProductionBuild } from "./commands/buildProduction/buildProduction.js";
10
+ import startServer from "./commands/startServer/startServer.js";
9
11
  import versionChecker from "./commands/utils/versionChecker.js";
10
12
  import fs from "fs";
11
13
  import path from "path";
@@ -73,6 +75,80 @@ sliceClient
73
75
  await versionChecker.showVersionInfo();
74
76
  });
75
77
 
78
+ // BUILD COMMAND
79
+ sliceClient
80
+ .command("build")
81
+ .description("Build project for production")
82
+ .option("--serve", "Start production server after build")
83
+ .option("--preview", "Start preview server after build")
84
+ .option("--analyze", "Analyze build without building")
85
+ .option("--skip-clean", "Skip cleaning previous build")
86
+ .option("-p, --port <port>", "Port for preview/serve server", 3001)
87
+ .action(async (options) => {
88
+ await runWithVersionCheck(async () => {
89
+ const success = await buildCommand({
90
+ serve: options.serve,
91
+ preview: options.preview,
92
+ analyze: options.analyze,
93
+ skipClean: options.skipClean,
94
+ port: parseInt(options.port)
95
+ });
96
+
97
+ if (!success) {
98
+ process.exit(1);
99
+ }
100
+ });
101
+ });
102
+
103
+ // START COMMAND (PRODUCTION)
104
+ sliceClient
105
+ .command("start")
106
+ .description("Start production server (requires build first)")
107
+ .option("-p, --port <port>", "Port for production server", 3000)
108
+ .option("--build", "Build for production before starting")
109
+ .action(async (options) => {
110
+ await runWithVersionCheck(async () => {
111
+ const distDir = path.join(__dirname, "../../dist");
112
+
113
+ // Verificar si existe build de producción
114
+ if (!fs.existsSync(distDir) && !options.build) {
115
+ Print.error("No production build found");
116
+ Print.info("Run 'slice build' first or use 'slice start --build'");
117
+ return false;
118
+ }
119
+
120
+ // Si se solicita build, construir primero
121
+ if (options.build) {
122
+ Print.info("Building for production...");
123
+ const buildSuccess = await buildProduction();
124
+ if (!buildSuccess) {
125
+ Print.error("Build failed, cannot start production server");
126
+ return false;
127
+ }
128
+ }
129
+
130
+ // Iniciar servidor de producción
131
+ await startServer({
132
+ mode: 'production',
133
+ port: parseInt(options.port)
134
+ });
135
+ });
136
+ });
137
+
138
+ // DEV COMMAND (DEVELOPMENT)
139
+ sliceClient
140
+ .command("dev")
141
+ .description("Start development server")
142
+ .option("-p, --port <port>", "Port for development server", 3000)
143
+ .action(async (options) => {
144
+ await runWithVersionCheck(async () => {
145
+ await startServer({
146
+ mode: 'development',
147
+ port: parseInt(options.port)
148
+ });
149
+ });
150
+ });
151
+
76
152
  // COMPONENT COMMAND GROUP - For local component management
77
153
  const componentCommand = sliceClient.command("component").alias("comp").description("Manage local project components");
78
154
 
@@ -286,18 +362,26 @@ sliceClient
286
362
  sliceClient.addHelpText('after', `
287
363
  Common Usage Examples:
288
364
  slice init - Initialize new Slice.js project
365
+ slice dev - Start development server
366
+ slice build - Build for production
367
+ slice start - Start production server
289
368
  slice get Button Card Input - Install Visual components from registry
290
369
  slice get FetchManager -s - Install Service component from registry
291
370
  slice browse - Browse all available components
292
371
  slice sync - Update local components to latest versions
293
372
  slice component create - Create new local component
294
- slice update - Check for CLI/framework updates
295
373
 
296
374
  Command Categories:
297
- • init, version, update - Project setup and maintenance
375
+ • init, dev, build, start - Project lifecycle
298
376
  • get, browse, sync - Quick registry shortcuts
299
377
  • component <cmd> - Local component management
300
378
  • registry <cmd> - Official repository operations
379
+ • version, update - Maintenance commands
380
+
381
+ Development vs Production:
382
+ • slice dev - Development server (serves from /src)
383
+ • slice build - Create optimized /dist build
384
+ • slice start - Production server (serves from /dist)
301
385
 
302
386
  More info: https://slice-js-docs.vercel.app/
303
387
  `);
@@ -307,6 +391,7 @@ if (!process.argv.slice(2).length) {
307
391
  program.outputHelp();
308
392
  Print.newLine();
309
393
  Print.info("Start with: slice init");
394
+ Print.commandExample("Development", "slice dev");
310
395
  Print.commandExample("View available components", "slice browse");
311
396
  }
312
397
 
package/commands/Print.js CHANGED
@@ -90,4 +90,28 @@ export default class Print {
90
90
  }
91
91
  Print.separator();
92
92
  }
93
+
94
+ // Método para mostrar resultados de minificación
95
+ static minificationResult(filename, originalSize, minifiedSize, savingsPercent) {
96
+ const originalKB = (originalSize / 1024).toFixed(1);
97
+ const minifiedKB = (minifiedSize / 1024).toFixed(1);
98
+
99
+ console.log('\x1b[32m', ` ✅ ${filename}`, '\x1b[0m');
100
+ console.log('\x1b[90m', ` ${originalKB}KB → ${minifiedKB}KB (${savingsPercent}% saved)`, '\x1b[0m');
101
+ }
102
+
103
+ // Método para mostrar progreso de build
104
+ static buildProgress(message) {
105
+ console.log('\x1b[36m', `🔄 ${message}`, '\x1b[0m');
106
+ }
107
+
108
+ // Método para mostrar estadísticas de servidor
109
+ static serverStats(mode, port, directory) {
110
+ Print.newLine();
111
+ console.log('\x1b[35m', `🌐 Server Configuration:`, '\x1b[0m');
112
+ console.log('\x1b[90m', ` Mode: ${mode}`, '\x1b[0m');
113
+ console.log('\x1b[90m', ` Port: ${port}`, '\x1b[0m');
114
+ console.log('\x1b[90m', ` Serving: /${directory}`, '\x1b[0m');
115
+ Print.newLine();
116
+ }
93
117
  }
@@ -0,0 +1,494 @@
1
+ // commands/buildProduction/buildProduction.js
2
+
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { minify as terserMinify } from 'terser';
7
+ import CleanCSS from 'clean-css';
8
+ import htmlMinifier from 'html-minifier-terser';
9
+ import Print from '../Print.js';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ /**
14
+ * Opciones de minificación para diferentes tipos de archivos
15
+ */
16
+ const getMinificationOptions = () => ({
17
+ js: {
18
+ compress: {
19
+ dead_code: true,
20
+ drop_console: true, // Remover console.log en producción
21
+ drop_debugger: true,
22
+ pure_funcs: ['console.log', 'console.info', 'console.warn'],
23
+ passes: 2
24
+ },
25
+ mangle: {
26
+ toplevel: true,
27
+ reserved: ['Slice', 'Controller', 'StylesManager'] // Preservar clases principales
28
+ },
29
+ output: {
30
+ comments: false,
31
+ beautify: false
32
+ },
33
+ toplevel: true
34
+ },
35
+ css: {
36
+ level: 2, // Optimización agresiva
37
+ returnPromise: false
38
+ },
39
+ html: {
40
+ collapseWhitespace: true,
41
+ removeComments: true,
42
+ removeRedundantAttributes: true,
43
+ removeEmptyAttributes: true,
44
+ minifyCSS: true,
45
+ minifyJS: true,
46
+ useShortDoctype: true,
47
+ removeAttributeQuotes: true,
48
+ removeOptionalTags: true
49
+ }
50
+ });
51
+
52
+ /**
53
+ * Minifica un archivo JavaScript
54
+ */
55
+ async function minifyJavaScript(content, filename) {
56
+ try {
57
+ const options = getMinificationOptions().js;
58
+ const result = await terserMinify(content, options);
59
+
60
+ if (result.error) {
61
+ throw result.error;
62
+ }
63
+
64
+ const originalSize = Buffer.byteLength(content, 'utf8');
65
+ const minifiedSize = Buffer.byteLength(result.code, 'utf8');
66
+ const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
67
+
68
+ Print.minificationResult(filename, originalSize, minifiedSize, savings);
69
+
70
+ return result.code;
71
+ } catch (error) {
72
+ Print.error(`Error minifying ${filename}: ${error.message}`);
73
+ throw error;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Minifica un archivo CSS
79
+ */
80
+ async function minifyCSS(content, filename) {
81
+ try {
82
+ const cleanCSS = new CleanCSS(getMinificationOptions().css);
83
+ const result = cleanCSS.minify(content);
84
+
85
+ if (result.errors && result.errors.length > 0) {
86
+ throw new Error(result.errors.join(', '));
87
+ }
88
+
89
+ const originalSize = Buffer.byteLength(content, 'utf8');
90
+ const minifiedSize = Buffer.byteLength(result.styles, 'utf8');
91
+ const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
92
+
93
+ Print.minificationResult(filename, originalSize, minifiedSize, savings);
94
+
95
+ return result.styles;
96
+ } catch (error) {
97
+ Print.error(`Error minifying ${filename}: ${error.message}`);
98
+ throw error;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Minifica un archivo HTML
104
+ */
105
+ async function minifyHTML(content, filename) {
106
+ try {
107
+ const result = await htmlMinifier.minify(content, getMinificationOptions().html);
108
+
109
+ const originalSize = Buffer.byteLength(content, 'utf8');
110
+ const minifiedSize = Buffer.byteLength(result, 'utf8');
111
+ const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
112
+
113
+ Print.minificationResult(filename, originalSize, minifiedSize, savings);
114
+
115
+ return result;
116
+ } catch (error) {
117
+ Print.error(`Error minifying ${filename}: ${error.message}`);
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Procesa un archivo según su extensión
124
+ */
125
+ async function processFile(srcPath, destPath, relativePath) {
126
+ try {
127
+ const content = await fs.readFile(srcPath, 'utf8');
128
+ const ext = path.extname(srcPath).toLowerCase();
129
+ let processedContent = content;
130
+
131
+ switch (ext) {
132
+ case '.js':
133
+ processedContent = await minifyJavaScript(content, relativePath);
134
+ break;
135
+ case '.css':
136
+ processedContent = await minifyCSS(content, relativePath);
137
+ break;
138
+ case '.html':
139
+ processedContent = await minifyHTML(content, relativePath);
140
+ break;
141
+ default:
142
+ // Para otros archivos (JSON, etc.), solo copiar
143
+ await fs.copy(srcPath, destPath);
144
+ return;
145
+ }
146
+
147
+ await fs.writeFile(destPath, processedContent, 'utf8');
148
+
149
+ } catch (error) {
150
+ Print.error(`Error processing ${relativePath}: ${error.message}`);
151
+ throw error;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Copia y procesa recursivamente todos los archivos de src a dist
157
+ */
158
+ async function processDirectory(srcDir, distDir, baseSrcDir) {
159
+ const items = await fs.readdir(srcDir);
160
+
161
+ for (const item of items) {
162
+ const srcPath = path.join(srcDir, item);
163
+ const destPath = path.join(distDir, item);
164
+ const relativePath = path.relative(baseSrcDir, srcPath);
165
+
166
+ const stat = await fs.stat(srcPath);
167
+
168
+ if (stat.isDirectory()) {
169
+ await fs.ensureDir(destPath);
170
+ await processDirectory(srcPath, destPath, baseSrcDir);
171
+ } else {
172
+ const ext = path.extname(srcPath).toLowerCase();
173
+
174
+ // Procesar archivos que pueden ser minificados
175
+ if (['.js', '.css', '.html'].includes(ext)) {
176
+ await processFile(srcPath, destPath, relativePath);
177
+ } else {
178
+ // Copiar otros archivos sin modificar
179
+ await fs.copy(srcPath, destPath);
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Crea un bundle optimizado del archivo principal Slice.js
187
+ */
188
+ async function createOptimizedBundle() {
189
+ try {
190
+ Print.info('Creating optimized Slice.js bundle...');
191
+
192
+ const slicePath = path.join(__dirname, '../../../../src/Slice/Slice.js');
193
+ const distSlicePath = path.join(__dirname, '../../../../dist/Slice/Slice.js');
194
+
195
+ if (!await fs.pathExists(slicePath)) {
196
+ Print.warning('Slice.js main file not found, skipping bundle optimization');
197
+ return;
198
+ }
199
+
200
+ const content = await fs.readFile(slicePath, 'utf8');
201
+ const minifiedContent = await minifyJavaScript(content, 'Slice/Slice.js');
202
+
203
+ await fs.ensureDir(path.dirname(distSlicePath));
204
+ await fs.writeFile(distSlicePath, minifiedContent, 'utf8');
205
+
206
+ Print.success('Optimized Slice.js bundle created');
207
+
208
+ } catch (error) {
209
+ Print.error(`Error creating optimized bundle: ${error.message}`);
210
+ throw error;
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Copia sliceConfig.json sin modificaciones
216
+ */
217
+ async function copySliceConfig() {
218
+ try {
219
+ const srcConfigPath = path.join(__dirname, '../../../../src/sliceConfig.json');
220
+ const distConfigPath = path.join(__dirname, '../../../../dist/sliceConfig.json');
221
+
222
+ if (await fs.pathExists(srcConfigPath)) {
223
+ await fs.copy(srcConfigPath, distConfigPath);
224
+ Print.success('sliceConfig.json copied to dist');
225
+ } else {
226
+ Print.warning('sliceConfig.json not found in src, skipping copy');
227
+ }
228
+ } catch (error) {
229
+ Print.error(`Error copying sliceConfig.json: ${error.message}`);
230
+ throw error;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Genera estadísticas del build
236
+ */
237
+ async function generateBuildStats(srcDir, distDir) {
238
+ try {
239
+ Print.info('Generating build statistics...');
240
+
241
+ const calculateDirSize = async (dir) => {
242
+ let totalSize = 0;
243
+ const files = await fs.readdir(dir, { withFileTypes: true });
244
+
245
+ for (const file of files) {
246
+ const filePath = path.join(dir, file.name);
247
+ if (file.isDirectory()) {
248
+ totalSize += await calculateDirSize(filePath);
249
+ } else {
250
+ const stats = await fs.stat(filePath);
251
+ totalSize += stats.size;
252
+ }
253
+ }
254
+ return totalSize;
255
+ };
256
+
257
+ const srcSize = await calculateDirSize(srcDir);
258
+ const distSize = await calculateDirSize(distDir);
259
+ const savings = ((srcSize - distSize) / srcSize * 100).toFixed(1);
260
+
261
+ Print.newLine();
262
+ Print.title('📊 Build Statistics');
263
+ console.log(`📁 Source size: ${(srcSize / 1024).toFixed(1)} KB`);
264
+ console.log(`📦 Production size: ${(distSize / 1024).toFixed(1)} KB`);
265
+ console.log(`💾 Size reduction: ${savings}% saved`);
266
+
267
+ } catch (error) {
268
+ Print.warning(`Could not generate build statistics: ${error.message}`);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Función principal de build para producción
274
+ */
275
+ export default async function buildProduction(options = {}) {
276
+ const startTime = Date.now();
277
+
278
+ try {
279
+ Print.title('🚀 Building Slice.js project for production...');
280
+ Print.newLine();
281
+
282
+ // Verificar que existe src
283
+ const srcDir = path.join(__dirname, '../../../../src');
284
+ const distDir = path.join(__dirname, '../../../../dist');
285
+
286
+ if (!await fs.pathExists(srcDir)) {
287
+ throw new Error('src directory not found. Run "slice init" first.');
288
+ }
289
+
290
+ // 1. Limpiar directorio dist
291
+ if (await fs.pathExists(distDir)) {
292
+ if (!options.skipClean) {
293
+ Print.info('Cleaning previous build...');
294
+ await fs.remove(distDir);
295
+ Print.success('Previous build cleaned');
296
+ }
297
+ }
298
+
299
+ await fs.ensureDir(distDir);
300
+
301
+ // 2. Copiar sliceConfig.json sin modificaciones
302
+ await copySliceConfig();
303
+
304
+ // 3. Procesar todos los archivos de src
305
+ Print.info('Processing and minifying source files...');
306
+ await processDirectory(srcDir, distDir, srcDir);
307
+ Print.success('All source files processed and optimized');
308
+
309
+ // 4. Crear bundle optimizado del archivo principal
310
+ await createOptimizedBundle();
311
+
312
+ // 5. Generar estadísticas
313
+ await generateBuildStats(srcDir, distDir);
314
+
315
+ // 6. Tiempo total
316
+ const buildTime = ((Date.now() - startTime) / 1000).toFixed(1);
317
+
318
+ Print.newLine();
319
+ Print.success(`✨ Production build completed in ${buildTime}s`);
320
+ Print.info('Your optimized project is ready in the /dist directory');
321
+ Print.newLine();
322
+ Print.info('Next steps:');
323
+ console.log(' • The same /api folder serves both development and production');
324
+ console.log(' • Update your Express server to serve from /dist instead of /src');
325
+ console.log(' • Deploy both /api and /dist directories to your hosting provider');
326
+ console.log(' • Use "slice build --serve" to preview the production build locally');
327
+
328
+ return true;
329
+
330
+ } catch (error) {
331
+ Print.error(`Build failed: ${error.message}`);
332
+ return false;
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Servidor de desarrollo para testing del build de producción
338
+ * Usa Express como el servidor principal pero sirviendo desde /dist
339
+ */
340
+ export async function serveProductionBuild(port = 3001) {
341
+ try {
342
+ const distDir = path.join(__dirname, '../../../../dist');
343
+
344
+ if (!await fs.pathExists(distDir)) {
345
+ throw new Error('No production build found. Run "slice build" first.');
346
+ }
347
+
348
+ Print.info(`Starting production build server on port ${port}...`);
349
+
350
+ // Implementar servidor estático simple que simula el comportamiento de la API
351
+ const express = await import('express');
352
+ const app = express.default();
353
+
354
+ // Servir archivos estáticos desde dist (equivalente a lo que hace la API con src)
355
+ app.use(express.default.static(distDir));
356
+
357
+ // SPA fallback - servir index.html para rutas no encontradas
358
+ app.get('*', (req, res) => {
359
+ const indexPath = path.join(distDir, 'index.html');
360
+ if (fs.existsSync(indexPath)) {
361
+ res.sendFile(indexPath);
362
+ } else {
363
+ res.status(404).send('Production build not found');
364
+ }
365
+ });
366
+
367
+ app.listen(port, () => {
368
+ Print.success(`Production build server running at http://localhost:${port}`);
369
+ Print.info('Press Ctrl+C to stop the server');
370
+ Print.info('This server simulates production environment using /dist files');
371
+ });
372
+
373
+ } catch (error) {
374
+ Print.error(`Error starting production server: ${error.message}`);
375
+ throw error;
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Comando build con opciones
381
+ */
382
+ export async function buildCommand(options = {}) {
383
+ // Verificar dependencias necesarias
384
+ if (!await checkBuildDependencies()) {
385
+ return false;
386
+ }
387
+
388
+ if (options.serve) {
389
+ // Solo servir build existente
390
+ await serveProductionBuild(options.port);
391
+ return true;
392
+ }
393
+
394
+ if (options.analyze) {
395
+ // Analizar build sin construir
396
+ await analyzeBuild();
397
+ return true;
398
+ }
399
+
400
+ // Build completo
401
+ const success = await buildProduction(options);
402
+
403
+ if (success && options.preview) {
404
+ Print.newLine();
405
+ Print.info('Starting preview server...');
406
+ setTimeout(() => {
407
+ serveProductionBuild(options.port);
408
+ }, 1000);
409
+ }
410
+
411
+ return success;
412
+ }
413
+
414
+ /**
415
+ * Verifica que las dependencias de build estén instaladas en el CLI
416
+ */
417
+ async function checkBuildDependencies() {
418
+ try {
419
+ Print.info('Checking build dependencies...');
420
+
421
+ // Verificar dependencias en el CLI en lugar del proyecto
422
+ const cliPackageJsonPath = path.join(__dirname, '../../package.json');
423
+
424
+ if (!await fs.pathExists(cliPackageJsonPath)) {
425
+ throw new Error('CLI package.json not found');
426
+ }
427
+
428
+ const cliPackageJson = await fs.readJson(cliPackageJsonPath);
429
+ const deps = { ...cliPackageJson.dependencies, ...cliPackageJson.devDependencies };
430
+
431
+ const requiredDeps = ['terser', 'clean-css', 'html-minifier-terser'];
432
+ const missing = requiredDeps.filter(dep => !deps[dep]);
433
+
434
+ if (missing.length > 0) {
435
+ Print.error('Missing build dependencies in CLI:');
436
+ missing.forEach(dep => console.log(` • ${dep}`));
437
+ Print.newLine();
438
+ Print.info('Please update slicejs-cli to the latest version:');
439
+ console.log('npm install -g slicejs-cli@latest');
440
+ return false;
441
+ }
442
+
443
+ Print.success('All build dependencies are available in CLI');
444
+ return true;
445
+
446
+ } catch (error) {
447
+ Print.error(`Error checking dependencies: ${error.message}`);
448
+ return false;
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Analiza el tamaño y composición del build
454
+ */
455
+ async function analyzeBuild() {
456
+ try {
457
+ const distDir = path.join(__dirname, '../../../../dist');
458
+
459
+ if (!await fs.pathExists(distDir)) {
460
+ throw new Error('No production build found. Run "slice build" first.');
461
+ }
462
+
463
+ Print.title('📊 Build Analysis');
464
+ Print.newLine();
465
+
466
+ const analyzeDirectory = async (dir, prefix = '') => {
467
+ const items = await fs.readdir(dir);
468
+ let totalSize = 0;
469
+
470
+ for (const item of items) {
471
+ const itemPath = path.join(dir, item);
472
+ const stat = await fs.stat(itemPath);
473
+
474
+ if (stat.isDirectory()) {
475
+ const dirSize = await analyzeDirectory(itemPath, `${prefix}${item}/`);
476
+ totalSize += dirSize;
477
+ } else {
478
+ const size = (stat.size / 1024).toFixed(1);
479
+ console.log(`📄 ${prefix}${item}: ${size} KB`);
480
+ totalSize += stat.size;
481
+ }
482
+ }
483
+
484
+ return totalSize;
485
+ };
486
+
487
+ const totalSize = await analyzeDirectory(distDir);
488
+ Print.newLine();
489
+ Print.info(`Total build size: ${(totalSize / 1024).toFixed(1)} KB`);
490
+
491
+ } catch (error) {
492
+ Print.error(`Error analyzing build: ${error.message}`);
493
+ }
494
+ }
@@ -667,4 +667,4 @@ async function syncComponents(options = {}) {
667
667
  }
668
668
 
669
669
  export default getComponents;
670
- export { listComponents, syncComponents };
670
+ export { listComponents, syncComponents, ComponentRegistry };
@@ -2,28 +2,21 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import Print from '../Print.js';
5
+
5
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
7
 
8
+ // Importar la clase ComponentRegistry del getComponent
9
+ import { ComponentRegistry } from '../getComponent/getComponent.js';
10
+
7
11
  export default async function initializeProject(projectType) {
8
12
  try {
9
- // Directorio de origen en el paquete
13
+ // Directorio de origen para API (mantener copia local)
10
14
  let sliceBaseDir = path.join(__dirname, '../../../slicejs-web-framework');
11
15
  let apiDir = path.join(sliceBaseDir, 'api');
12
16
  let srcDir = path.join(sliceBaseDir, 'src');
13
-
14
17
  let destinationApi = path.join(__dirname, '../../../../api');
15
18
  let destinationSrc = path.join(__dirname, '../../../../src');
16
19
 
17
- try {
18
- // Verificar si los directorios de origen existen
19
- if (!fs.existsSync(sliceBaseDir)) throw new Error(`No se encontró el directorio base: ${sliceBaseDir}`);
20
- if (!fs.existsSync(apiDir)) throw new Error(`No se encontró la carpeta api: ${apiDir}`);
21
- if (!fs.existsSync(srcDir)) throw new Error(`No se encontró la carpeta src: ${srcDir}`);
22
- } catch (error) {
23
- Print.error('Error validando directorios de origen:', error.message);
24
- return;
25
- }
26
-
27
20
  try {
28
21
  // Verificar si los directorios de destino ya existen
29
22
  if (fs.existsSync(destinationApi)) throw new Error(`El directorio "api" ya existe: ${destinationApi}`);
@@ -33,8 +26,9 @@ export default async function initializeProject(projectType) {
33
26
  return;
34
27
  }
35
28
 
29
+ // 1. COPIAR LA CARPETA API (mantener lógica original)
36
30
  try {
37
- // Copiar las carpetas
31
+ if (!fs.existsSync(apiDir)) throw new Error(`No se encontró la carpeta api: ${apiDir}`);
38
32
  await fs.copy(apiDir, destinationApi, { recursive: true });
39
33
  Print.success('Carpeta "api" copiada correctamente.');
40
34
  } catch (error) {
@@ -42,16 +36,119 @@ export default async function initializeProject(projectType) {
42
36
  return;
43
37
  }
44
38
 
39
+ // 2. CREAR ESTRUCTURA SRC BÁSICA (sin copiar componentes Visual)
45
40
  try {
46
- await fs.copy(srcDir, destinationSrc, { recursive: true });
47
- Print.success('Carpeta "src" copiada correctamente.');
41
+ if (!fs.existsSync(srcDir)) throw new Error(`No se encontró la carpeta src: ${srcDir}`);
42
+
43
+ // Copiar solo los archivos base de src, excluyendo Components/Visual
44
+ await fs.ensureDir(destinationSrc);
45
+
46
+ // Copiar archivos y carpetas de src excepto Components/Visual
47
+ const srcItems = await fs.readdir(srcDir);
48
+
49
+ for (const item of srcItems) {
50
+ const srcItemPath = path.join(srcDir, item);
51
+ const destItemPath = path.join(destinationSrc, item);
52
+ const stat = await fs.stat(srcItemPath);
53
+
54
+ if (stat.isDirectory()) {
55
+ if (item === 'Components') {
56
+ // Crear estructura de Components pero sin copiar Visual
57
+ await fs.ensureDir(destItemPath);
58
+
59
+ const componentItems = await fs.readdir(srcItemPath);
60
+ for (const componentItem of componentItems) {
61
+ const componentItemPath = path.join(srcItemPath, componentItem);
62
+ const destComponentItemPath = path.join(destItemPath, componentItem);
63
+
64
+ if (componentItem !== 'Visual') {
65
+ // Copiar Service y otros tipos de components
66
+ await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
67
+ } else {
68
+ // Solo crear el directorio Visual vacío
69
+ await fs.ensureDir(destComponentItemPath);
70
+ }
71
+ }
72
+ } else {
73
+ // Copiar otras carpetas normalmente
74
+ await fs.copy(srcItemPath, destItemPath, { recursive: true });
75
+ }
76
+ } else {
77
+ // Copiar archivos normalmente
78
+ await fs.copy(srcItemPath, destItemPath);
79
+ }
80
+ }
81
+
82
+ Print.success('Estructura "src" creada correctamente.');
48
83
  } catch (error) {
49
- Print.error('Error copiando la carpeta "src":', error.message);
84
+ Print.error('Error creando la estructura "src":', error.message);
50
85
  return;
51
86
  }
52
87
 
88
+ // 3. DESCARGAR TODOS LOS COMPONENTES VISUAL DESDE EL REPOSITORIO OFICIAL
89
+ try {
90
+ Print.info('Downloading all Visual components from official repository...');
91
+
92
+ const registry = new ComponentRegistry();
93
+ await registry.loadRegistry();
94
+
95
+ // Obtener TODOS los componentes Visual disponibles
96
+ const allVisualComponents = await getAllVisualComponents(registry);
97
+
98
+ if (allVisualComponents.length > 0) {
99
+ Print.info(`Installing ${allVisualComponents.length} Visual components...`);
100
+
101
+ const results = await registry.installMultipleComponents(
102
+ allVisualComponents,
103
+ 'Visual',
104
+ true // force = true para instalación inicial
105
+ );
106
+
107
+ const successful = results.filter(r => r.success).length;
108
+ const failed = results.filter(r => !r.success).length;
109
+
110
+ if (successful > 0) {
111
+ Print.success(`${successful} Visual components installed from official repository`);
112
+ }
113
+
114
+ if (failed > 0) {
115
+ Print.warning(`${failed} Visual components could not be installed`);
116
+ Print.info('You can install them later using "slice get <component-name>"');
117
+ }
118
+ } else {
119
+ Print.warning('No Visual components found in registry');
120
+ Print.info('You can add components later using "slice get <component-name>"');
121
+ }
122
+
123
+ } catch (error) {
124
+ Print.warning('Could not download Visual components from official repository');
125
+ Print.error(`Repository error: ${error.message}`);
126
+ Print.info('Project initialized without Visual components');
127
+ Print.info('You can add them later using "slice get <component-name>"');
128
+ }
129
+
53
130
  Print.success('Proyecto inicializado correctamente.');
131
+ Print.newLine();
132
+ Print.info('Next steps:');
133
+ console.log(' slice browse - View available components');
134
+ console.log(' slice get Button - Install specific components');
135
+ console.log(' slice sync - Update all components to latest versions');
136
+
54
137
  } catch (error) {
55
138
  Print.error('Error inesperado al inicializar el proyecto:', error.message);
56
139
  }
57
140
  }
141
+
142
+ /**
143
+ * Obtiene TODOS los componentes Visual disponibles en el registry
144
+ * @param {ComponentRegistry} registry - Instancia del registry cargado
145
+ * @returns {Array} - Array con todos los nombres de componentes Visual
146
+ */
147
+ async function getAllVisualComponents(registry) {
148
+ const availableComponents = registry.getAvailableComponents('Visual');
149
+ const allVisualComponents = Object.keys(availableComponents);
150
+
151
+ Print.info(`Found ${allVisualComponents.length} Visual components in official repository`);
152
+
153
+ return allVisualComponents;
154
+ }
@@ -0,0 +1,198 @@
1
+ // commands/startServer/startServer.js
2
+
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { spawn } from 'child_process';
7
+ import Print from '../Print.js';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+
11
+ /**
12
+ * Verifica si existe un build de producción
13
+ */
14
+ async function checkProductionBuild() {
15
+ const distDir = path.join(__dirname, '../../../../dist');
16
+ return await fs.pathExists(distDir);
17
+ }
18
+
19
+ /**
20
+ * Verifica si existe la estructura de desarrollo
21
+ */
22
+ async function checkDevelopmentStructure() {
23
+ const srcDir = path.join(__dirname, '../../../../src');
24
+ const apiDir = path.join(__dirname, '../../../../api');
25
+
26
+ return (await fs.pathExists(srcDir)) && (await fs.pathExists(apiDir));
27
+ }
28
+
29
+ /**
30
+ * Modifica temporalmente el servidor Express para modo producción
31
+ */
32
+ async function createProductionIndexFile() {
33
+ try {
34
+ const apiDir = path.join(__dirname, '../../../../api');
35
+ const originalIndexPath = path.join(apiDir, 'index.js');
36
+ const backupIndexPath = path.join(apiDir, 'index.dev.js');
37
+ const prodIndexPath = path.join(apiDir, 'index.prod.js');
38
+
39
+ // Crear backup del index original si no existe
40
+ if (!await fs.pathExists(backupIndexPath)) {
41
+ await fs.copy(originalIndexPath, backupIndexPath);
42
+ }
43
+
44
+ // Leer el contenido original
45
+ const originalContent = await fs.readFile(originalIndexPath, 'utf8');
46
+
47
+ // Modificar para servir desde /dist en lugar de /src
48
+ const productionContent = originalContent.replace(
49
+ /express\.static\(['"`]src['"`]\)/g,
50
+ "express.static('dist')"
51
+ ).replace(
52
+ /express\.static\(path\.join\(__dirname,\s*['"`]\.\.\/src['"`]\)\)/g,
53
+ "express.static(path.join(__dirname, '../dist'))"
54
+ );
55
+
56
+ // Escribir archivo temporal de producción
57
+ await fs.writeFile(prodIndexPath, productionContent, 'utf8');
58
+
59
+ // Reemplazar index.js con versión de producción
60
+ await fs.copy(prodIndexPath, originalIndexPath);
61
+
62
+ Print.success('Express server configured for production mode');
63
+
64
+ return true;
65
+ } catch (error) {
66
+ Print.error(`Error configuring production server: ${error.message}`);
67
+ return false;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Restaura el servidor Express al modo desarrollo
73
+ */
74
+ async function restoreDevelopmentIndexFile() {
75
+ try {
76
+ const apiDir = path.join(__dirname, '../../../../api');
77
+ const originalIndexPath = path.join(apiDir, 'index.js');
78
+ const backupIndexPath = path.join(apiDir, 'index.dev.js');
79
+
80
+ if (await fs.pathExists(backupIndexPath)) {
81
+ await fs.copy(backupIndexPath, originalIndexPath);
82
+ Print.success('Express server restored to development mode');
83
+ }
84
+
85
+ return true;
86
+ } catch (error) {
87
+ Print.error(`Error restoring development server: ${error.message}`);
88
+ return false;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Inicia el servidor Node.js
94
+ */
95
+ function startNodeServer(port, mode) {
96
+ return new Promise((resolve, reject) => {
97
+ const apiIndexPath = path.join(__dirname, '../../../../api/index.js');
98
+
99
+ Print.info(`Starting ${mode} server on port ${port}...`);
100
+
101
+ const serverProcess = spawn('node', [apiIndexPath], {
102
+ stdio: 'inherit',
103
+ env: {
104
+ ...process.env,
105
+ PORT: port,
106
+ NODE_ENV: mode === 'production' ? 'production' : 'development'
107
+ }
108
+ });
109
+
110
+ serverProcess.on('error', (error) => {
111
+ Print.error(`Failed to start server: ${error.message}`);
112
+ reject(error);
113
+ });
114
+
115
+ // Manejar Ctrl+C para limpiar archivos temporales
116
+ process.on('SIGINT', async () => {
117
+ Print.info('Shutting down server...');
118
+
119
+ if (mode === 'production') {
120
+ await restoreDevelopmentIndexFile();
121
+ }
122
+
123
+ serverProcess.kill('SIGINT');
124
+ process.exit(0);
125
+ });
126
+
127
+ // Manejar cierre del proceso
128
+ process.on('SIGTERM', async () => {
129
+ if (mode === 'production') {
130
+ await restoreDevelopmentIndexFile();
131
+ }
132
+
133
+ serverProcess.kill('SIGTERM');
134
+ });
135
+
136
+ // El servidor se considera iniciado exitosamente después de un breve delay
137
+ setTimeout(() => {
138
+ Print.success(`${mode === 'production' ? 'Production' : 'Development'} server running at http://localhost:${port}`);
139
+ Print.info(`Serving files from /${mode === 'production' ? 'dist' : 'src'} directory`);
140
+ Print.info('Press Ctrl+C to stop the server');
141
+ resolve(serverProcess);
142
+ }, 1000);
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Función principal para iniciar servidor
148
+ */
149
+ export default async function startServer(options = {}) {
150
+ const { mode = 'development', port = 3000 } = options;
151
+
152
+ try {
153
+ Print.title(`🚀 Starting Slice.js ${mode} server...`);
154
+ Print.newLine();
155
+
156
+ // Verificar estructura del proyecto
157
+ if (!await checkDevelopmentStructure()) {
158
+ throw new Error('Project structure not found. Run "slice init" first.');
159
+ }
160
+
161
+ if (mode === 'production') {
162
+ // Modo producción: verificar build y configurar servidor
163
+ if (!await checkProductionBuild()) {
164
+ throw new Error('No production build found. Run "slice build" first.');
165
+ }
166
+
167
+ // Configurar Express para modo producción
168
+ const configSuccess = await createProductionIndexFile();
169
+ if (!configSuccess) {
170
+ throw new Error('Failed to configure production server');
171
+ }
172
+
173
+ Print.info('Production mode: serving optimized files from /dist');
174
+ } else {
175
+ // Modo desarrollo: asegurar que está en modo desarrollo
176
+ await restoreDevelopmentIndexFile();
177
+ Print.info('Development mode: serving files from /src with hot reload');
178
+ }
179
+
180
+ // Iniciar el servidor
181
+ await startNodeServer(port, mode);
182
+
183
+ } catch (error) {
184
+ Print.error(`Failed to start server: ${error.message}`);
185
+
186
+ // Limpiar en caso de error
187
+ if (mode === 'production') {
188
+ await restoreDevelopmentIndexFile();
189
+ }
190
+
191
+ throw error;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Funciones de utilidad exportadas
197
+ */
198
+ export { checkProductionBuild, checkDevelopmentStructure };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-cli",
3
- "version": "2.1.11",
3
+ "version": "2.2.2",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "scripts": {
@@ -17,9 +17,12 @@
17
17
  "type": "module",
18
18
  "license": "ISC",
19
19
  "dependencies": {
20
+ "clean-css": "^5.3.3",
20
21
  "commander": "^12.0.0",
21
22
  "fs-extra": "^11.2.0",
23
+ "html-minifier-terser": "^7.2.0",
22
24
  "inquirer": "^12.4.2",
23
- "slicejs-web-framework": "latest"
25
+ "slicejs-web-framework": "latest",
26
+ "terser": "^5.43.1"
24
27
  }
25
28
  }
package/post.js CHANGED
@@ -24,7 +24,9 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
24
24
 
25
25
  // Main project commands
26
26
  projectPackageJson.scripts['slice:init'] = 'node node_modules/slicejs-cli/client.js init';
27
- projectPackageJson.scripts['slice:start'] = 'node api/index.js';
27
+ projectPackageJson.scripts['slice:dev'] = 'node node_modules/slicejs-cli/client.js dev';
28
+ projectPackageJson.scripts['slice:start'] = 'node node_modules/slicejs-cli/client.js start';
29
+ projectPackageJson.scripts['slice:build'] = 'node node_modules/slicejs-cli/client.js build';
28
30
  projectPackageJson.scripts['slice:version'] = 'node node_modules/slicejs-cli/client.js version';
29
31
  projectPackageJson.scripts['slice:update'] = 'node node_modules/slicejs-cli/client.js update';
30
32
 
@@ -32,7 +34,7 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
32
34
  projectPackageJson.scripts['slice:create'] = 'node node_modules/slicejs-cli/client.js component create';
33
35
  projectPackageJson.scripts['slice:list'] = 'node node_modules/slicejs-cli/client.js component list';
34
36
  projectPackageJson.scripts['slice:delete'] = 'node node_modules/slicejs-cli/client.js component delete';
35
-
37
+
36
38
  // Main repository commands (most used shortcuts)
37
39
  projectPackageJson.scripts['slice:get'] = 'node node_modules/slicejs-cli/client.js get';
38
40
  projectPackageJson.scripts['slice:browse'] = 'node node_modules/slicejs-cli/client.js browse';
@@ -43,9 +45,14 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
43
45
  projectPackageJson.scripts['slice:registry-list'] = 'node node_modules/slicejs-cli/client.js registry list';
44
46
  projectPackageJson.scripts['slice:registry-sync'] = 'node node_modules/slicejs-cli/client.js registry sync';
45
47
 
48
+ // Build-related commands
49
+ projectPackageJson.scripts['slice:build-serve'] = 'node node_modules/slicejs-cli/client.js build --serve';
50
+ projectPackageJson.scripts['slice:build-preview'] = 'node node_modules/slicejs-cli/client.js build --preview';
51
+ projectPackageJson.scripts['slice:build-analyze'] = 'node node_modules/slicejs-cli/client.js build --analyze';
52
+
46
53
  // Legacy/compatibility commands
47
54
  projectPackageJson.scripts['run'] = 'node api/index.js';
48
- projectPackageJson.scripts['development'] = 'node api/index.js';
55
+ projectPackageJson.scripts['development'] = 'node node_modules/slicejs-cli/client.js dev';
49
56
 
50
57
  // Module configuration
51
58
  projectPackageJson.type = 'module';
@@ -58,21 +65,32 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
58
65
  })
59
66
  .then(() => {
60
67
  console.log('✅ SliceJS CLI commands added to package.json');
61
- console.log('\n🚀 Main commands:');
68
+ console.log('\n🚀 Main workflow commands:');
62
69
  console.log(' npm run slice:init - Initialize Slice.js project');
70
+ console.log(' npm run slice:dev - Start development server (serves from /src)');
71
+ console.log(' npm run slice:build - Build for production (creates /dist)');
72
+ console.log(' npm run slice:start - Start production server (serves from /dist)');
73
+ console.log('\n📦 Component management:');
63
74
  console.log(' npm run slice:get Button - Get components from official repository');
64
75
  console.log(' npm run slice:browse - View all available components');
65
76
  console.log(' npm run slice:sync - Update local components to latest versions');
66
- console.log(' npm run slice:start - Start development server');
67
77
  console.log('\n⚙️ Local component management:');
68
78
  console.log(' npm run slice:create - Create local component');
69
79
  console.log(' npm run slice:list - List local components');
70
80
  console.log(' npm run slice:delete - Delete local component');
71
- console.log('\n🔧 Utilities:');
81
+ console.log('\n🔧 Build utilities:');
82
+ console.log(' npm run slice:build-serve - Build and serve immediately');
83
+ console.log(' npm run slice:build-preview- Build and preview');
84
+ console.log(' npm run slice:build-analyze- Analyze build size');
85
+ console.log('\n🔧 Other utilities:');
72
86
  console.log(' npm run slice:version - View version information');
73
87
  console.log(' npm run slice:update - Check for available updates');
74
- console.log('\n🎯 To get started: npm run slice:init');
75
- console.log('💡 Tip: Use "slice:sync" to keep your components updated');
88
+ console.log('\n🎯 Development workflow:');
89
+ console.log(' 1. npm run slice:init - Initialize project');
90
+ console.log(' 2. npm run slice:dev - Develop with hot reload');
91
+ console.log(' 3. npm run slice:build - Build for production');
92
+ console.log(' 4. npm run slice:start - Test production build');
93
+ console.log('\n💡 Tip: Use "slice:sync" to keep your components updated');
76
94
  })
77
95
  .catch(err => {
78
96
  if (err.code === 'ENOENT') {
@@ -83,9 +101,11 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
83
101
  description: 'Slice.js project',
84
102
  main: 'api/index.js',
85
103
  scripts: {
86
- // Main commands
104
+ // Main workflow commands
87
105
  'slice:init': 'node node_modules/slicejs-cli/client.js init',
88
- 'slice:start': 'node api/index.js',
106
+ 'slice:dev': 'node node_modules/slicejs-cli/client.js dev',
107
+ 'slice:start': 'node node_modules/slicejs-cli/client.js start',
108
+ 'slice:build': 'node node_modules/slicejs-cli/client.js build',
89
109
  'slice:version': 'node node_modules/slicejs-cli/client.js version',
90
110
  'slice:update': 'node node_modules/slicejs-cli/client.js update',
91
111
 
@@ -104,8 +124,14 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
104
124
  'slice:registry-list': 'node node_modules/slicejs-cli/client.js registry list',
105
125
  'slice:registry-sync': 'node node_modules/slicejs-cli/client.js registry sync',
106
126
 
127
+ // Build utilities
128
+ 'slice:build-serve': 'node node_modules/slicejs-cli/client.js build --serve',
129
+ 'slice:build-preview': 'node node_modules/slicejs-cli/client.js build --preview',
130
+ 'slice:build-analyze': 'node node_modules/slicejs-cli/client.js build --analyze',
131
+
107
132
  // Legacy
108
- 'run': 'node api/index.js'
133
+ 'run': 'node api/index.js',
134
+ 'development': 'node node_modules/slicejs-cli/client.js dev'
109
135
  },
110
136
  keywords: ['slicejs', 'web-framework', 'components'],
111
137
  author: '',
@@ -125,21 +151,32 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
125
151
  })
126
152
  .then(() => {
127
153
  console.log('✅ Created package.json with SliceJS CLI commands.');
128
- console.log('\n🚀 Main commands:');
154
+ console.log('\n🚀 Main workflow commands:');
129
155
  console.log(' npm run slice:init - Initialize Slice.js project');
156
+ console.log(' npm run slice:dev - Start development server (serves from /src)');
157
+ console.log(' npm run slice:build - Build for production (creates /dist)');
158
+ console.log(' npm run slice:start - Start production server (serves from /dist)');
159
+ console.log('\n📦 Component management:');
130
160
  console.log(' npm run slice:get Button - Get components from official repository');
131
161
  console.log(' npm run slice:browse - View all available components');
132
162
  console.log(' npm run slice:sync - Update local components to latest versions');
133
- console.log(' npm run slice:start - Start development server');
134
163
  console.log('\n⚙️ Local component management:');
135
164
  console.log(' npm run slice:create - Create local component');
136
165
  console.log(' npm run slice:list - List local components');
137
166
  console.log(' npm run slice:delete - Delete local component');
138
- console.log('\n🔧 Utilities:');
167
+ console.log('\n🔧 Build utilities:');
168
+ console.log(' npm run slice:build-serve - Build and serve immediately');
169
+ console.log(' npm run slice:build-preview- Build and preview');
170
+ console.log(' npm run slice:build-analyze- Analyze build size');
171
+ console.log('\n🔧 Other utilities:');
139
172
  console.log(' npm run slice:version - View version information');
140
173
  console.log(' npm run slice:update - Check for available updates');
141
- console.log('\n🎯 To get started: npm run slice:init');
142
- console.log('💡 Tip: Use "slice:sync" to keep your components updated');
174
+ console.log('\n🎯 Development workflow:');
175
+ console.log(' 1. npm run slice:init - Initialize project');
176
+ console.log(' 2. npm run slice:dev - Develop with hot reload');
177
+ console.log(' 3. npm run slice:build - Build for production');
178
+ console.log(' 4. npm run slice:start - Test production build');
179
+ console.log('\n💡 Tip: Use "slice:sync" to keep your components updated');
143
180
  })
144
181
  .catch(err => {
145
182
  console.error('Error creating package.json:', err);