slicejs-cli 2.2.5 → 2.2.7

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.
@@ -1,274 +1,347 @@
1
- // commands/buildProduction/buildProduction.js
1
+ // commands/buildProduction/buildProduction.js - VERSIÓN LIMPIA
2
2
 
3
3
  import fs from 'fs-extra';
4
4
  import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { minify as terserMinify } from 'terser';
7
+ import { minify } from 'html-minifier-terser';
7
8
  import CleanCSS from 'clean-css';
8
- import htmlMinifier from 'html-minifier-terser';
9
9
  import Print from '../Print.js';
10
10
 
11
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
12
 
13
13
  /**
14
- * Opciones de minificación para diferentes tipos de archivos - CORREGIDAS
14
+ * Carga la configuración desde sliceConfig.json
15
15
  */
16
- const getMinificationOptions = () => ({
17
- js: {
18
- compress: {
19
- dead_code: true,
20
- drop_console: false, // NO remover console.log para evitar problemas
21
- drop_debugger: true,
22
- pure_funcs: [], // NO remover funciones específicas
23
- passes: 1, // Reducir pasadas para ser menos agresivo
24
- keep_classnames: true, // IMPORTANTE: Preservar nombres de clases
25
- keep_fnames: true // IMPORTANTE: Preservar nombres de funciones
26
- },
27
- mangle: {
28
- toplevel: false, // NO hacer mangle a nivel superior
29
- keep_classnames: true, // Preservar nombres de clases
30
- keep_fnames: true, // Preservar nombres de funciones
31
- reserved: [
32
- // Framework core
33
- 'Slice', 'Controller', 'StylesManager', 'ThemeManager', 'Logger',
34
- // Métodos importantes
35
- 'slice', 'build', 'init', 'attachTemplate', 'getComponent',
36
- // Eventos y propiedades de componentes
37
- 'constructor', 'connectedCallback', 'disconnectedCallback',
38
- 'attributeChangedCallback', 'adoptedCallback',
39
- // Variables comunes en componentes
40
- 'componentName', 'props', 'options', 'value', 'disabled',
41
- // HTML Elements y DOM
42
- 'HTMLElement', 'customElements', 'define', 'querySelector',
43
- 'querySelectorAll', 'addEventListener', 'removeEventListener',
44
- // Métodos de componentes Slice.js
45
- 'setComponentProps', 'componentCategories', 'templates',
46
- 'activeComponents', 'classes', 'requestedStyles'
47
- ]
48
- },
49
- output: {
50
- comments: false,
51
- beautify: false,
52
- keep_quoted_props: true // Preservar propiedades entre comillas
53
- },
54
- toplevel: false // NO optimizar a nivel superior
55
- },
56
- css: {
57
- level: 1, // Optimización moderada en lugar de agresiva
58
- returnPromise: false
59
- },
60
- html: {
61
- collapseWhitespace: true,
62
- removeComments: true,
63
- removeRedundantAttributes: true,
64
- removeEmptyAttributes: true,
65
- minifyCSS: false, // NO minificar CSS inline para evitar problemas
66
- minifyJS: false, // NO minificar JS inline para evitar problemas
67
- useShortDoctype: true,
68
- removeAttributeQuotes: false, // Mantener comillas en atributos
69
- removeOptionalTags: false // Mantener tags opcionales
16
+ const loadConfig = () => {
17
+ try {
18
+ const configPath = path.join(__dirname, '../../../../src/sliceConfig.json');
19
+ const rawData = fs.readFileSync(configPath, 'utf-8');
20
+ return JSON.parse(rawData);
21
+ } catch (error) {
22
+ Print.error(`Loading configuration: ${error.message}`);
23
+ return null;
70
24
  }
71
- });
25
+ };
72
26
 
73
27
  /**
74
- * Minifica un archivo JavaScript de forma segura
28
+ * Verifica dependencias necesarias para el build
75
29
  */
76
- async function minifyJavaScript(content, filename) {
30
+ async function checkBuildDependencies() {
31
+ const srcDir = path.join(__dirname, '../../../../src');
32
+
33
+ if (!await fs.pathExists(srcDir)) {
34
+ Print.error('Source directory (/src) not found');
35
+ Print.info('Run "slice init" to initialize your project');
36
+ return false;
37
+ }
38
+
77
39
  try {
78
- // Para archivos de componentes, ser menos agresivo
79
- const isComponentFile = filename.includes('/Components/') || filename.includes('\\Components\\');
80
-
81
- let options = getMinificationOptions().js;
82
-
83
- if (isComponentFile) {
84
- // Configuración especial para archivos de componentes
85
- options = {
86
- ...options,
87
- compress: {
88
- ...options.compress,
89
- passes: 1,
90
- keep_classnames: true,
91
- keep_fnames: true
92
- },
93
- mangle: false // NO hacer mangle en archivos de componentes
94
- };
95
- }
96
-
97
- const result = await terserMinify(content, options);
98
-
99
- if (result.error) {
100
- throw result.error;
101
- }
102
-
103
- const originalSize = Buffer.byteLength(content, 'utf8');
104
- const minifiedSize = Buffer.byteLength(result.code, 'utf8');
105
- const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
106
-
107
- Print.minificationResult(filename, originalSize, minifiedSize, savings);
108
-
109
- return result.code;
40
+ await import('terser');
41
+ await import('clean-css');
42
+ await import('html-minifier-terser');
43
+ Print.success('Build dependencies available');
44
+ return true;
110
45
  } catch (error) {
111
- Print.error(`Error minifying ${filename}: ${error.message}`);
112
- // En caso de error, devolver contenido original
113
- Print.warning(`Using original content for ${filename}`);
114
- return content;
46
+ Print.warning('Some build dependencies missing - using fallback copy mode');
47
+ return true;
115
48
  }
116
49
  }
117
50
 
118
51
  /**
119
- * Minifica un archivo CSS
52
+ * Verifica que existan los archivos críticos para Slice.js
120
53
  */
121
- async function minifyCSS(content, filename) {
122
- try {
123
- const cleanCSS = new CleanCSS(getMinificationOptions().css);
124
- const result = cleanCSS.minify(content);
125
-
126
- if (result.errors && result.errors.length > 0) {
127
- throw new Error(result.errors.join(', '));
54
+ async function verifySliceFiles(srcDir) {
55
+ Print.info('Verifying Slice.js critical files...');
56
+
57
+ const criticalFiles = [
58
+ 'sliceConfig.json',
59
+ 'Components/components.js',
60
+ 'App/index.js'
61
+ ];
62
+
63
+ for (const file of criticalFiles) {
64
+ const filePath = path.join(srcDir, file);
65
+ if (!await fs.pathExists(filePath)) {
66
+ throw new Error(`Critical Slice.js file missing: ${file}`);
128
67
  }
129
-
130
- const originalSize = Buffer.byteLength(content, 'utf8');
131
- const minifiedSize = Buffer.byteLength(result.styles, 'utf8');
132
- const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
133
-
134
- Print.minificationResult(filename, originalSize, minifiedSize, savings);
135
-
136
- return result.styles;
137
- } catch (error) {
138
- Print.error(`Error minifying ${filename}: ${error.message}`);
139
- throw error;
140
68
  }
69
+
70
+ Print.success('All critical Slice.js files verified');
141
71
  }
142
72
 
143
73
  /**
144
- * Minifica un archivo HTML
74
+ * Verifica la integridad del build para Slice.js
145
75
  */
146
- async function minifyHTML(content, filename) {
147
- try {
148
- const result = await htmlMinifier.minify(content, getMinificationOptions().html);
149
-
150
- const originalSize = Buffer.byteLength(content, 'utf8');
151
- const minifiedSize = Buffer.byteLength(result, 'utf8');
152
- const savings = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
153
-
154
- Print.minificationResult(filename, originalSize, minifiedSize, savings);
76
+ async function verifyBuildIntegrity(distDir) {
77
+ Print.info('Verifying build integrity for Slice.js...');
78
+
79
+ const criticalBuiltFiles = [
80
+ 'sliceConfig.json',
81
+ 'Components/components.js',
82
+ 'App/index.js'
83
+ ];
84
+
85
+ for (const file of criticalBuiltFiles) {
86
+ const filePath = path.join(distDir, file);
87
+ if (!await fs.pathExists(filePath)) {
88
+ throw new Error(`Critical built file missing: ${file}`);
89
+ }
155
90
 
156
- return result;
157
- } catch (error) {
158
- Print.error(`Error minifying ${filename}: ${error.message}`);
159
- throw error;
91
+ if (file === 'Components/components.js') {
92
+ const content = await fs.readFile(filePath, 'utf8');
93
+ if (!content.includes('const components') || !content.includes('export default')) {
94
+ throw new Error('components.js structure corrupted during build');
95
+ }
96
+ }
160
97
  }
98
+
99
+ Print.success('Build integrity verified - all Slice.js components preserved');
161
100
  }
162
101
 
163
102
  /**
164
- * Procesa un archivo según su extensión
103
+ * Copia sliceConfig.json al directorio dist
165
104
  */
166
- async function processFile(srcPath, destPath, relativePath) {
167
- try {
168
- const content = await fs.readFile(srcPath, 'utf8');
169
- const ext = path.extname(srcPath).toLowerCase();
170
- let processedContent = content;
171
-
172
- switch (ext) {
173
- case '.js':
174
- processedContent = await minifyJavaScript(content, relativePath);
175
- break;
176
- case '.css':
177
- processedContent = await minifyCSS(content, relativePath);
178
- break;
179
- case '.html':
180
- processedContent = await minifyHTML(content, relativePath);
181
- break;
182
- default:
183
- // Para otros archivos (JSON, etc.), solo copiar
184
- await fs.copy(srcPath, destPath);
185
- return;
186
- }
187
-
188
- await fs.writeFile(destPath, processedContent, 'utf8');
189
-
190
- } catch (error) {
191
- Print.error(`Error processing ${relativePath}: ${error.message}`);
192
- throw error;
105
+ async function copySliceConfig() {
106
+ const srcConfig = path.join(__dirname, '../../../../src/sliceConfig.json');
107
+ const distConfig = path.join(__dirname, '../../../../dist/sliceConfig.json');
108
+
109
+ if (await fs.pathExists(srcConfig)) {
110
+ await fs.copy(srcConfig, distConfig);
111
+ Print.info('sliceConfig.json copied to dist');
193
112
  }
194
113
  }
195
114
 
196
115
  /**
197
- * Copia y procesa recursivamente todos los archivos de src a dist
116
+ * Procesa un directorio completo
198
117
  */
199
- async function processDirectory(srcDir, distDir, baseSrcDir) {
200
- const items = await fs.readdir(srcDir);
118
+ async function processDirectory(srcPath, distPath, baseSrcPath) {
119
+ const items = await fs.readdir(srcPath);
201
120
 
202
121
  for (const item of items) {
203
- const srcPath = path.join(srcDir, item);
204
- const destPath = path.join(distDir, item);
205
- const relativePath = path.relative(baseSrcDir, srcPath);
206
-
207
- const stat = await fs.stat(srcPath);
122
+ const srcItemPath = path.join(srcPath, item);
123
+ const distItemPath = path.join(distPath, item);
124
+ const stat = await fs.stat(srcItemPath);
208
125
 
209
126
  if (stat.isDirectory()) {
210
- await fs.ensureDir(destPath);
211
- await processDirectory(srcPath, destPath, baseSrcDir);
127
+ await fs.ensureDir(distItemPath);
128
+ await processDirectory(srcItemPath, distItemPath, baseSrcPath);
212
129
  } else {
213
- const ext = path.extname(srcPath).toLowerCase();
214
-
215
- // Procesar archivos que pueden ser minificados
216
- if (['.js', '.css', '.html'].includes(ext)) {
217
- await processFile(srcPath, destPath, relativePath);
218
- } else {
219
- // Copiar otros archivos sin modificar
220
- await fs.copy(srcPath, destPath);
221
- }
130
+ await processFile(srcItemPath, distItemPath);
222
131
  }
223
132
  }
224
133
  }
225
134
 
226
135
  /**
227
- * Crea un bundle optimizado del archivo principal Slice.js
136
+ * Procesa un archivo individual
228
137
  */
229
- async function createOptimizedBundle() {
138
+ async function processFile(srcFilePath, distFilePath) {
139
+ const ext = path.extname(srcFilePath).toLowerCase();
140
+ const fileName = path.basename(srcFilePath);
141
+
230
142
  try {
231
- Print.info('Creating optimized Slice.js bundle...');
232
-
233
- const slicePath = path.join(__dirname, '../../../../src/Slice/Slice.js');
234
- const distSlicePath = path.join(__dirname, '../../../../dist/Slice/Slice.js');
235
-
236
- if (!await fs.pathExists(slicePath)) {
237
- Print.warning('Slice.js main file not found, skipping bundle optimization');
238
- return;
143
+ if (fileName === 'components.js') {
144
+ await processComponentsFile(srcFilePath, distFilePath);
145
+ } else if (ext === '.js') {
146
+ await minifyJavaScript(srcFilePath, distFilePath);
147
+ } else if (ext === '.css') {
148
+ await minifyCSS(srcFilePath, distFilePath);
149
+ } else if (ext === '.html') {
150
+ await minifyHTML(srcFilePath, distFilePath);
151
+ } else if (fileName === 'sliceConfig.json') {
152
+ await fs.copy(srcFilePath, distFilePath);
153
+ Print.info(`📄 Preserved: ${fileName} (configuration file)`);
154
+ } else {
155
+ await fs.copy(srcFilePath, distFilePath);
156
+ const stat = await fs.stat(srcFilePath);
157
+ const sizeKB = (stat.size / 1024).toFixed(1);
158
+ Print.info(`📄 Copied: ${fileName} (${sizeKB} KB)`);
239
159
  }
240
-
241
- const content = await fs.readFile(slicePath, 'utf8');
242
- const minifiedContent = await minifyJavaScript(content, 'Slice/Slice.js');
243
-
244
- await fs.ensureDir(path.dirname(distSlicePath));
245
- await fs.writeFile(distSlicePath, minifiedContent, 'utf8');
246
-
247
- Print.success('Optimized Slice.js bundle created');
248
-
249
160
  } catch (error) {
250
- Print.error(`Error creating optimized bundle: ${error.message}`);
251
- throw error;
161
+ Print.error(`Processing ${fileName}: ${error.message}`);
162
+ await fs.copy(srcFilePath, distFilePath);
252
163
  }
253
164
  }
254
165
 
255
166
  /**
256
- * Copia sliceConfig.json sin modificaciones
167
+ * Procesa el archivo components.js de forma especial
257
168
  */
258
- async function copySliceConfig() {
259
- try {
260
- const srcConfigPath = path.join(__dirname, '../../../../src/sliceConfig.json');
261
- const distConfigPath = path.join(__dirname, '../../../../dist/sliceConfig.json');
262
-
263
- if (await fs.pathExists(srcConfigPath)) {
264
- await fs.copy(srcConfigPath, distConfigPath);
265
- Print.success('sliceConfig.json copied to dist');
266
- } else {
267
- Print.warning('sliceConfig.json not found in src, skipping copy');
169
+ async function processComponentsFile(srcPath, distPath) {
170
+ const content = await fs.readFile(srcPath, 'utf8');
171
+ const originalSize = Buffer.byteLength(content, 'utf8');
172
+
173
+ const result = await terserMinify(content, {
174
+ compress: false,
175
+ mangle: false,
176
+ format: {
177
+ comments: false,
178
+ beautify: false,
179
+ indent_level: 0
268
180
  }
269
- } catch (error) {
270
- Print.error(`Error copying sliceConfig.json: ${error.message}`);
271
- throw error;
181
+ });
182
+
183
+ if (result.error) {
184
+ throw new Error(`Terser error in components.js: ${result.error}`);
185
+ }
186
+
187
+ await fs.writeFile(distPath, result.code, 'utf8');
188
+
189
+ const minifiedSize = Buffer.byteLength(result.code, 'utf8');
190
+ const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
191
+
192
+ Print.minificationResult(`${path.basename(srcPath)} (preserved structure)`, originalSize, minifiedSize, savings);
193
+ }
194
+
195
+ /**
196
+ * Minifica archivos JavaScript preservando la arquitectura de Slice.js
197
+ */
198
+ async function minifyJavaScript(srcPath, distPath) {
199
+ const content = await fs.readFile(srcPath, 'utf8');
200
+ const originalSize = Buffer.byteLength(content, 'utf8');
201
+
202
+ const result = await terserMinify(content, {
203
+ compress: {
204
+ drop_console: false,
205
+ drop_debugger: true,
206
+ pure_funcs: [],
207
+ passes: 1,
208
+ unused: false,
209
+ side_effects: false,
210
+ reduce_vars: false,
211
+ collapse_vars: false
212
+ },
213
+ mangle: {
214
+ reserved: [
215
+ // Core Slice
216
+ 'slice', 'Slice', 'SliceJS', 'window', 'document',
217
+ // Clases principales
218
+ 'Controller', 'StylesManager', 'Router', 'Logger', 'Debugger',
219
+ // Métodos de Slice
220
+ 'getClass', 'isProduction', 'getComponent', 'build', 'setTheme', 'attachTemplate',
221
+ // Controller
222
+ 'componentCategories', 'templates', 'classes', 'requestedStyles', 'activeComponents',
223
+ 'registerComponent', 'registerComponentsRecursively', 'loadTemplateToComponent',
224
+ 'fetchText', 'setComponentProps', 'verifyComponentIds', 'destroyComponent',
225
+ // StylesManager
226
+ 'componentStyles', 'themeManager', 'init', 'appendComponentStyles', 'registerComponentStyles',
227
+ // Router
228
+ 'routes', 'pathToRouteMap', 'activeRoute', 'navigate', 'matchRoute', 'handleRoute',
229
+ 'onRouteChange', 'loadInitialRoute', 'renderRoutesComponentsInPage',
230
+ // Propiedades de componentes
231
+ 'sliceId', 'sliceType', 'sliceConfig', 'debuggerProps', 'parentComponent',
232
+ 'value', 'customColor', 'icon', 'layout', 'view', 'items', 'columns', 'rows',
233
+ 'onClickCallback', 'props',
234
+ // Custom Elements
235
+ 'customElements', 'define', 'HTMLElement',
236
+ // DOM APIs críticas
237
+ 'addEventListener', 'removeEventListener', 'querySelector', 'querySelectorAll',
238
+ 'appendChild', 'removeChild', 'innerHTML', 'textContent', 'style', 'classList',
239
+ // Lifecycle
240
+ 'beforeMount', 'afterMount', 'beforeDestroy', 'afterDestroy',
241
+ 'mount', 'unmount', 'destroy', 'update', 'start', 'stop',
242
+ // Browser APIs
243
+ 'fetch', 'setTimeout', 'clearTimeout', 'localStorage', 'history', 'pushState',
244
+ // Exports/Imports
245
+ 'default', 'export', 'import', 'from', 'await', 'async',
246
+ // Nombres de componentes
247
+ 'Button', 'Grid', 'Layout', 'HomePage', 'NotFound', 'Loading', 'TreeView', 'Link',
248
+ 'FetchManager', 'Translator'
249
+ ],
250
+ properties: {
251
+ regex: /^(slice|_|\$|on[A-Z]|get|set|has|is)/
252
+ }
253
+ },
254
+ format: {
255
+ comments: false,
256
+ beautify: false
257
+ },
258
+ keep_fnames: true,
259
+ keep_classnames: true
260
+ });
261
+
262
+ if (result.error) {
263
+ throw new Error(`Terser error: ${result.error}`);
264
+ }
265
+
266
+ await fs.writeFile(distPath, result.code, 'utf8');
267
+
268
+ const minifiedSize = Buffer.byteLength(result.code, 'utf8');
269
+ const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
270
+
271
+ Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
272
+ }
273
+
274
+ /**
275
+ * Minifica archivos CSS
276
+ */
277
+ async function minifyCSS(srcPath, distPath) {
278
+ const content = await fs.readFile(srcPath, 'utf8');
279
+ const originalSize = Buffer.byteLength(content, 'utf8');
280
+
281
+ const cleanCSS = new CleanCSS({
282
+ level: 2,
283
+ returnPromise: false
284
+ });
285
+
286
+ const result = cleanCSS.minify(content);
287
+
288
+ if (result.errors.length > 0) {
289
+ throw new Error(`CleanCSS errors: ${result.errors.join(', ')}`);
290
+ }
291
+
292
+ await fs.writeFile(distPath, result.styles, 'utf8');
293
+
294
+ const minifiedSize = Buffer.byteLength(result.styles, 'utf8');
295
+ const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
296
+
297
+ Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
298
+ }
299
+
300
+ /**
301
+ * Minifica archivos HTML
302
+ */
303
+ async function minifyHTML(srcPath, distPath) {
304
+ const content = await fs.readFile(srcPath, 'utf8');
305
+ const originalSize = Buffer.byteLength(content, 'utf8');
306
+
307
+ const minified = await minify(content, {
308
+ collapseWhitespace: true,
309
+ removeComments: true,
310
+ removeRedundantAttributes: true,
311
+ removeScriptTypeAttributes: true,
312
+ removeStyleLinkTypeAttributes: true,
313
+ useShortDoctype: true,
314
+ minifyCSS: true,
315
+ minifyJS: {
316
+ mangle: {
317
+ reserved: ['slice', 'Slice', 'SliceJS', 'sliceId', 'sliceConfig']
318
+ }
319
+ },
320
+ ignoreCustomFragments: [
321
+ /slice-[\w-]+="[^"]*"/g
322
+ ]
323
+ });
324
+
325
+ await fs.writeFile(distPath, minified, 'utf8');
326
+
327
+ const minifiedSize = Buffer.byteLength(minified, 'utf8');
328
+ const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
329
+
330
+ Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
331
+ }
332
+
333
+ /**
334
+ * Crea un bundle optimizado del archivo principal
335
+ */
336
+ async function createOptimizedBundle() {
337
+ Print.buildProgress('Creating optimized bundle...');
338
+
339
+ const mainJSPath = path.join(__dirname, '../../../../dist/App/index.js');
340
+
341
+ if (await fs.pathExists(mainJSPath)) {
342
+ Print.success('Main bundle optimized');
343
+ } else {
344
+ Print.warning('No main JavaScript file found for bundling');
272
345
  }
273
346
  }
274
347
 
@@ -276,59 +349,80 @@ async function copySliceConfig() {
276
349
  * Genera estadísticas del build
277
350
  */
278
351
  async function generateBuildStats(srcDir, distDir) {
279
- try {
280
- Print.info('Generating build statistics...');
352
+ Print.buildProgress('Generating build statistics...');
353
+
354
+ const getDirectorySize = async (dirPath) => {
355
+ let totalSize = 0;
356
+ const items = await fs.readdir(dirPath);
281
357
 
282
- const calculateDirSize = async (dir) => {
283
- let totalSize = 0;
284
- const files = await fs.readdir(dir, { withFileTypes: true });
358
+ for (const item of items) {
359
+ const itemPath = path.join(dirPath, item);
360
+ const stat = await fs.stat(itemPath);
285
361
 
286
- for (const file of files) {
287
- const filePath = path.join(dir, file.name);
288
- if (file.isDirectory()) {
289
- totalSize += await calculateDirSize(filePath);
290
- } else {
291
- const stats = await fs.stat(filePath);
292
- totalSize += stats.size;
293
- }
362
+ if (stat.isDirectory()) {
363
+ totalSize += await getDirectorySize(itemPath);
364
+ } else {
365
+ totalSize += stat.size;
294
366
  }
295
- return totalSize;
296
- };
367
+ }
368
+
369
+ return totalSize;
370
+ };
297
371
 
298
- const srcSize = await calculateDirSize(srcDir);
299
- const distSize = await calculateDirSize(distDir);
300
- const savings = ((srcSize - distSize) / srcSize * 100).toFixed(1);
372
+ try {
373
+ const srcSize = await getDirectorySize(srcDir);
374
+ const distSize = await getDirectorySize(distDir);
375
+ const savings = Math.round(((srcSize - distSize) / srcSize) * 100);
301
376
 
302
377
  Print.newLine();
303
- Print.title('📊 Build Statistics');
304
- console.log(`📁 Source size: ${(srcSize / 1024).toFixed(1)} KB`);
305
- console.log(`📦 Production size: ${(distSize / 1024).toFixed(1)} KB`);
306
- console.log(`💾 Size reduction: ${savings}% saved`);
378
+ Print.info(`📊 Build Statistics:`);
379
+ console.log(` Source: ${(srcSize / 1024).toFixed(1)} KB`);
380
+ console.log(` Built: ${(distSize / 1024).toFixed(1)} KB`);
381
+ console.log(` Saved: ${savings}% smaller`);
307
382
 
308
383
  } catch (error) {
309
- Print.warning(`Could not generate build statistics: ${error.message}`);
384
+ Print.warning('Could not generate build statistics');
310
385
  }
311
386
  }
312
387
 
313
388
  /**
314
- * Función principal de build para producción
389
+ * Analiza el build sin construir
390
+ */
391
+ async function analyzeBuild() {
392
+ const distDir = path.join(__dirname, '../../../../dist');
393
+
394
+ if (!await fs.pathExists(distDir)) {
395
+ Print.error('No build found to analyze. Run "slice build" first.');
396
+ return;
397
+ }
398
+
399
+ Print.info('Analyzing production build...');
400
+ await generateBuildStats(
401
+ path.join(__dirname, '../../../../src'),
402
+ distDir
403
+ );
404
+ }
405
+
406
+ /**
407
+ * FUNCIÓN PRINCIPAL DE BUILD
315
408
  */
316
409
  export default async function buildProduction(options = {}) {
317
410
  const startTime = Date.now();
318
411
 
319
412
  try {
320
- Print.title('🚀 Building Slice.js project for production...');
413
+ Print.title('🔨 Building Slice.js project for production...');
321
414
  Print.newLine();
322
415
 
323
- // Verificar que existe src
324
416
  const srcDir = path.join(__dirname, '../../../../src');
325
417
  const distDir = path.join(__dirname, '../../../../dist');
326
418
 
327
419
  if (!await fs.pathExists(srcDir)) {
328
- throw new Error('src directory not found. Run "slice init" first.');
420
+ throw new Error('Source directory not found. Run "slice init" first.');
329
421
  }
330
422
 
331
- // 1. Limpiar directorio dist
423
+ await verifySliceFiles(srcDir);
424
+
425
+ // Limpiar directorio dist
332
426
  if (await fs.pathExists(distDir)) {
333
427
  if (!options.skipClean) {
334
428
  Print.info('Cleaning previous build...');
@@ -338,33 +432,26 @@ export default async function buildProduction(options = {}) {
338
432
  }
339
433
 
340
434
  await fs.ensureDir(distDir);
341
-
342
- // 2. Copiar sliceConfig.json sin modificaciones
343
435
  await copySliceConfig();
344
436
 
345
- // 3. Procesar todos los archivos de src
346
- Print.info('Processing and minifying source files...');
437
+ // Procesar archivos
438
+ Print.info('Processing and optimizing source files for Slice.js...');
347
439
  await processDirectory(srcDir, distDir, srcDir);
348
440
  Print.success('All source files processed and optimized');
349
441
 
350
- // 4. Crear bundle optimizado del archivo principal
442
+ await verifyBuildIntegrity(distDir);
351
443
  await createOptimizedBundle();
352
-
353
- // 5. Generar estadísticas
354
444
  await generateBuildStats(srcDir, distDir);
355
445
 
356
- // 6. Tiempo total
357
446
  const buildTime = ((Date.now() - startTime) / 1000).toFixed(1);
358
447
 
359
448
  Print.newLine();
360
- Print.success(`✨ Production build completed in ${buildTime}s`);
449
+ Print.success(`✨ Slice.js production build completed in ${buildTime}s`);
361
450
  Print.info('Your optimized project is ready in the /dist directory');
362
451
  Print.newLine();
363
452
  Print.info('Next steps:');
364
- console.log(' • The same /api folder serves both development and production');
365
- console.log(' • Update your Express server to serve from /dist instead of /src');
366
- console.log(' • Deploy both /api and /dist directories to your hosting provider');
367
- console.log(' • Use "slice build --serve" to preview the production build locally');
453
+ console.log(' • Use "npm run slice:start" to test the production build');
454
+ console.log(' • All Slice.js components and architecture preserved');
368
455
 
369
456
  return true;
370
457
 
@@ -375,162 +462,82 @@ export default async function buildProduction(options = {}) {
375
462
  }
376
463
 
377
464
  /**
378
- * Servidor de desarrollo para testing del build de producción
379
- * Usa Express como el servidor principal pero sirviendo desde /dist
465
+ * Servidor de preview para testing del build de producción
380
466
  */
381
- export async function serveProductionBuild(port = 3001) {
467
+ export async function serveProductionBuild(port) {
382
468
  try {
469
+ const config = loadConfig();
470
+ const defaultPort = config?.server?.port || 3001;
471
+ const finalPort = port || defaultPort;
472
+
383
473
  const distDir = path.join(__dirname, '../../../../dist');
384
474
 
385
475
  if (!await fs.pathExists(distDir)) {
386
476
  throw new Error('No production build found. Run "slice build" first.');
387
477
  }
388
478
 
389
- Print.info(`Starting production build server on port ${port}...`);
479
+ Print.info(`Starting production preview server on port ${finalPort}...`);
390
480
 
391
- // Implementar servidor estático simple que simula el comportamiento de la API
392
481
  const express = await import('express');
393
482
  const app = express.default();
394
483
 
395
- // Servir archivos estáticos desde dist (equivalente a lo que hace la API con src)
396
484
  app.use(express.default.static(distDir));
397
485
 
398
- // SPA fallback - servir index.html para rutas no encontradas
399
486
  app.get('*', (req, res) => {
400
- const indexPath = path.join(distDir, 'index.html');
487
+ const indexPath = path.join(distDir, 'App/index.html');
488
+ const fallbackPath = path.join(distDir, 'index.html');
489
+
401
490
  if (fs.existsSync(indexPath)) {
402
491
  res.sendFile(indexPath);
492
+ } else if (fs.existsSync(fallbackPath)) {
493
+ res.sendFile(fallbackPath);
403
494
  } else {
404
- res.status(404).send('Production build not found');
495
+ res.status(404).send('Production build index.html not found');
405
496
  }
406
497
  });
407
498
 
408
- app.listen(port, () => {
409
- Print.success(`Production build server running at http://localhost:${port}`);
499
+ app.listen(finalPort, () => {
500
+ Print.success(`Production preview server running at http://localhost:${finalPort}`);
410
501
  Print.info('Press Ctrl+C to stop the server');
411
- Print.info('This server simulates production environment using /dist files');
502
+ Print.info('This server previews your production build from /dist');
503
+ Print.warning('This is a preview server - use "npm run slice:start" for the full production server');
412
504
  });
413
505
 
414
506
  } catch (error) {
415
- Print.error(`Error starting production server: ${error.message}`);
507
+ Print.error(`Error starting production preview server: ${error.message}`);
416
508
  throw error;
417
509
  }
418
510
  }
419
511
 
420
512
  /**
421
- * Comando build con opciones - CORREGIDO
513
+ * Comando build con opciones
422
514
  */
423
515
  export async function buildCommand(options = {}) {
424
- // Verificar dependencias necesarias
516
+ const config = loadConfig();
517
+ const defaultPort = config?.server?.port || 3001;
518
+
425
519
  if (!await checkBuildDependencies()) {
426
520
  return false;
427
521
  }
428
522
 
429
523
  if (options.serve) {
430
- // Solo servir build existente
431
- await serveProductionBuild(options.port);
524
+ await serveProductionBuild(options.port || defaultPort);
432
525
  return true;
433
526
  }
434
527
 
435
528
  if (options.analyze) {
436
- // Analizar build sin construir
437
529
  await analyzeBuild();
438
530
  return true;
439
531
  }
440
532
 
441
- // Build completo
442
533
  const success = await buildProduction(options);
443
534
 
444
- // Solo mostrar mensaje informativo, no ejecutar servidor automáticamente
445
535
  if (success && options.preview) {
446
536
  Print.newLine();
447
537
  Print.info('✨ Build completed successfully!');
448
- Print.info('💡 Use "slice build --serve" to preview the production build');
449
- Print.info('💡 Or "slice start" to start production server');
538
+ Print.info(`Starting preview server on port ${options.port || defaultPort}...`);
539
+ await serveProductionBuild(options.port || defaultPort);
450
540
  }
451
541
 
452
542
  return success;
453
- }
454
-
455
-
456
- /**
457
- * Verifica que las dependencias de build estén instaladas en el CLI
458
- */
459
- async function checkBuildDependencies() {
460
- try {
461
- Print.info('Checking build dependencies...');
462
-
463
- // Verificar dependencias en el CLI en lugar del proyecto
464
- const cliPackageJsonPath = path.join(__dirname, '../../package.json');
465
-
466
- if (!await fs.pathExists(cliPackageJsonPath)) {
467
- throw new Error('CLI package.json not found');
468
- }
469
-
470
- const cliPackageJson = await fs.readJson(cliPackageJsonPath);
471
- const deps = { ...cliPackageJson.dependencies, ...cliPackageJson.devDependencies };
472
-
473
- const requiredDeps = ['terser', 'clean-css', 'html-minifier-terser'];
474
- const missing = requiredDeps.filter(dep => !deps[dep]);
475
-
476
- if (missing.length > 0) {
477
- Print.error('Missing build dependencies in CLI:');
478
- missing.forEach(dep => console.log(` • ${dep}`));
479
- Print.newLine();
480
- Print.info('Please update slicejs-cli to the latest version:');
481
- console.log('npm install -g slicejs-cli@latest');
482
- return false;
483
- }
484
-
485
- Print.success('All build dependencies are available in CLI');
486
- return true;
487
-
488
- } catch (error) {
489
- Print.error(`Error checking dependencies: ${error.message}`);
490
- return false;
491
- }
492
- }
493
-
494
- /**
495
- * Analiza el tamaño y composición del build
496
- */
497
- async function analyzeBuild() {
498
- try {
499
- const distDir = path.join(__dirname, '../../../../dist');
500
-
501
- if (!await fs.pathExists(distDir)) {
502
- throw new Error('No production build found. Run "slice build" first.');
503
- }
504
-
505
- Print.title('📊 Build Analysis');
506
- Print.newLine();
507
-
508
- const analyzeDirectory = async (dir, prefix = '') => {
509
- const items = await fs.readdir(dir);
510
- let totalSize = 0;
511
-
512
- for (const item of items) {
513
- const itemPath = path.join(dir, item);
514
- const stat = await fs.stat(itemPath);
515
-
516
- if (stat.isDirectory()) {
517
- const dirSize = await analyzeDirectory(itemPath, `${prefix}${item}/`);
518
- totalSize += dirSize;
519
- } else {
520
- const size = (stat.size / 1024).toFixed(1);
521
- console.log(`📄 ${prefix}${item}: ${size} KB`);
522
- totalSize += stat.size;
523
- }
524
- }
525
-
526
- return totalSize;
527
- };
528
-
529
- const totalSize = await analyzeDirectory(distDir);
530
- Print.newLine();
531
- Print.info(`Total build size: ${(totalSize / 1024).toFixed(1)} KB`);
532
-
533
- } catch (error) {
534
- Print.error(`Error analyzing build: ${error.message}`);
535
- }
536
543
  }