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.
@@ -48,30 +48,29 @@ export default async function initializeProject(projectType) {
48
48
  const srcDir = path.join(sliceBaseDir, 'src');
49
49
 
50
50
  try {
51
- // Verificar si los directorios de destino ya existen
52
- if (fs.existsSync(destinationApi)) throw new Error(`El directorio "api" ya existe: ${destinationApi}`);
53
- if (fs.existsSync(destinationSrc)) throw new Error(`El directorio "src" ya existe: ${destinationSrc}`);
54
- } catch (error) {
55
- Print.error('Error validando directorios de destino:', error.message);
56
- return;
57
- }
51
+ if (fs.existsSync(destinationApi)) throw new Error(`The "api" directory already exists: ${destinationApi}`);
52
+ if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
53
+ } catch (error) {
54
+ Print.error('Validating destination directories:', error.message);
55
+ return;
56
+ }
58
57
 
59
58
  // 1. COPIAR LA CARPETA API (mantener lógica original)
60
- const apiSpinner = ora('Copying API structure...').start();
61
- try {
62
- if (!fs.existsSync(apiDir)) throw new Error(`No se encontró la carpeta api: ${apiDir}`);
63
- await fs.copy(apiDir, destinationApi, { recursive: true });
64
- apiSpinner.succeed('API structure created successfully');
65
- } catch (error) {
66
- apiSpinner.fail('Error copying API structure');
67
- Print.error(error.message);
68
- return;
69
- }
59
+ const apiSpinner = ora('Copying API structure...').start();
60
+ try {
61
+ if (!fs.existsSync(apiDir)) throw new Error(`API folder not found: ${apiDir}`);
62
+ await fs.copy(apiDir, destinationApi, { recursive: true });
63
+ apiSpinner.succeed('API structure created successfully');
64
+ } catch (error) {
65
+ apiSpinner.fail('Error copying API structure');
66
+ Print.error(error.message);
67
+ return;
68
+ }
70
69
 
71
70
  // 2. CREAR ESTRUCTURA SRC BÁSICA (sin copiar componentes Visual)
72
- const srcSpinner = ora('Creating src structure...').start();
73
- try {
74
- if (!fs.existsSync(srcDir)) throw new Error(`No se encontró la carpeta src: ${srcDir}`);
71
+ const srcSpinner = ora('Creating src structure...').start();
72
+ try {
73
+ if (!fs.existsSync(srcDir)) throw new Error(`src folder not found: ${srcDir}`);
75
74
 
76
75
  // Copiar solo los archivos base de src, excluyendo Components/Visual
77
76
  await fs.ensureDir(destinationSrc);
@@ -95,22 +94,22 @@ export default async function initializeProject(projectType) {
95
94
  const destComponentItemPath = path.join(destItemPath, componentItem);
96
95
 
97
96
  if (componentItem !== 'Visual') {
98
- // Copiar Service y otros tipos de components
99
- await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
100
- } else {
101
- // Solo crear el directorio Visual vacío
102
- await fs.ensureDir(destComponentItemPath);
103
- }
104
- }
105
- } else {
106
- // Copiar otras carpetas normalmente
107
- await fs.copy(srcItemPath, destItemPath, { recursive: true });
108
- }
109
- } else {
110
- // Copiar archivos normalmente
111
- await fs.copy(srcItemPath, destItemPath);
112
- }
113
- }
97
+ // Copy Service and other component types
98
+ await fs.copy(componentItemPath, destComponentItemPath, { recursive: true });
99
+ } else {
100
+ // Only create empty Visual directory
101
+ await fs.ensureDir(destComponentItemPath);
102
+ }
103
+ }
104
+ } else {
105
+ // Copy other folders normally
106
+ await fs.copy(srcItemPath, destItemPath, { recursive: true });
107
+ }
108
+ } else {
109
+ // Copy files normally
110
+ await fs.copy(srcItemPath, destItemPath);
111
+ }
112
+ }
114
113
 
115
114
  srcSpinner.succeed('Source structure created successfully');
116
115
  } catch (error) {
@@ -143,15 +142,15 @@ export default async function initializeProject(projectType) {
143
142
  if (successful > 0 && failed === 0) {
144
143
  componentsSpinner.succeed(`All ${successful} Visual components installed successfully`);
145
144
  } else if (successful > 0) {
146
- componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
147
- Print.info('You can install failed components later using "slice get <component-name>"');
148
- } else {
149
- componentsSpinner.fail('Failed to install Visual components');
150
- }
151
- } else {
152
- componentsSpinner.warn('No Visual components found in registry');
153
- Print.info('You can add components later using "slice get <component-name>"');
154
- }
145
+ componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
146
+ Print.info('You can install failed components later using "slice get <component-name>"');
147
+ } else {
148
+ componentsSpinner.fail('Failed to install Visual components');
149
+ }
150
+ } else {
151
+ componentsSpinner.warn('No Visual components found in registry');
152
+ Print.info('You can add components later using "slice get <component-name>"');
153
+ }
155
154
 
156
155
  } catch (error) {
157
156
  componentsSpinner.fail('Could not download Visual components from official repository');
@@ -223,21 +222,21 @@ export default async function initializeProject(projectType) {
223
222
  console.log(' npm run get - Install components');
224
223
  console.log(' npm run browse - Browse components');
225
224
  } catch (error) {
226
- pkgSpinner.fail('Failed to configure npm scripts');
227
- Print.error(error.message);
228
- }
225
+ pkgSpinner.fail('Failed to configure npm scripts');
226
+ Print.error(error.message);
227
+ }
229
228
 
230
- Print.success('Proyecto inicializado correctamente.');
231
- Print.newLine();
232
- Print.info('Next steps:');
233
- console.log(' slice browse - View available components');
234
- console.log(' slice get Button - Install specific components');
235
- console.log(' slice sync - Update all components to latest versions');
229
+ Print.success('Project initialized successfully.');
230
+ Print.newLine();
231
+ Print.info('Next steps:');
232
+ console.log(' slice browse - View available components');
233
+ console.log(' slice get Button - Install specific components');
234
+ console.log(' slice sync - Update all components to latest versions');
236
235
 
237
236
  } catch (error) {
238
- Print.error('Error inesperado al inicializar el proyecto:', error.message);
239
- }
240
- }
237
+ Print.error('Unexpected error initializing project:', error.message);
238
+ }
239
+ }
241
240
 
242
241
  /**
243
242
  * Obtiene TODOS los componentes Visual disponibles en el registry
@@ -86,6 +86,8 @@ function startNodeServer(port, mode) {
86
86
  const args = [apiIndexPath];
87
87
  if (mode === 'production') {
88
88
  args.push('--production');
89
+ } else if (mode === 'bundled') {
90
+ args.push('--bundled');
89
91
  } else {
90
92
  args.push('--development');
91
93
  }
@@ -171,7 +173,7 @@ export default async function startServer(options = {}) {
171
173
  const config = loadConfig();
172
174
  const defaultPort = config?.server?.port || 3000;
173
175
 
174
- const { mode = 'development', port = defaultPort, watch = false } = options;
176
+ const { mode = 'development', port = defaultPort, watch = false, bundled = false } = options;
175
177
 
176
178
  try {
177
179
  Print.title(`🚀 Starting Slice.js ${mode} server...`);
@@ -190,7 +192,7 @@ export default async function startServer(options = {}) {
190
192
  throw new Error(
191
193
  `Port ${port} is already in use. Please:\n` +
192
194
  ` 1. Stop the process using port ${port}, or\n` +
193
- ` 2. Use a different port: slice ${mode === 'development' ? 'dev' : 'start'} -p <port>`
195
+ ` 2. Use a different port: slice ${mode === 'development' ? 'dev' : mode === 'bundled' ? 'start --bundled' : 'start'} -p <port>`
194
196
  );
195
197
  }
196
198
 
@@ -203,6 +205,8 @@ export default async function startServer(options = {}) {
203
205
  throw new Error('No production build found. Run "slice build" first.');
204
206
  }
205
207
  Print.info('Production mode: serving optimized files from /dist');
208
+ } else if (mode === 'bundled') {
209
+ Print.info('Bundled mode: serving with generated bundles for optimized loading');
206
210
  } else {
207
211
  Print.info('Development mode: serving files from /src with hot reload');
208
212
  }
@@ -14,6 +14,16 @@ function candidates(moduleUrl) {
14
14
  }
15
15
 
16
16
  function resolveProjectRoot(moduleUrl) {
17
+ const initCwd = process.env.INIT_CWD
18
+ if (initCwd && fs.pathExistsSync(initCwd)) {
19
+ return initCwd
20
+ }
21
+
22
+ const cwd = process.cwd()
23
+ if (cwd && fs.pathExistsSync(cwd)) {
24
+ return cwd
25
+ }
26
+
17
27
  const dirs = candidates(moduleUrl)
18
28
  for (const root of dirs) {
19
29
  const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
@@ -111,7 +111,7 @@ class VersionChecker {
111
111
 
112
112
  if (!silent && (cliStatus === 'outdated' || frameworkStatus === 'outdated')) {
113
113
  console.log(''); // Line break
114
- Print.warning('📦 Actualizaciones Disponibles:');
114
+ Print.warning('📦 Available Updates:');
115
115
 
116
116
  if (cliStatus === 'outdated') {
117
117
  console.log(` 🔧 CLI: ${current.cli} → ${latest.cli}`);
@@ -142,7 +142,7 @@ class VersionChecker {
142
142
  const current = await this.getCurrentVersions();
143
143
  const latest = await this.getLatestVersions();
144
144
 
145
- console.log('\n📋 Información de Versiones:');
145
+ console.log('\n📋 Version Information:');
146
146
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
147
147
 
148
148
  if (current?.cli) {
@@ -0,0 +1,389 @@
1
+ // cli/utils/bundling/BundleGenerator.js
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import crypto from 'crypto';
5
+ import { getSrcPath, getComponentsJsPath } from '../PathHelper.js';
6
+
7
+ export default class BundleGenerator {
8
+ constructor(moduleUrl, analysisData) {
9
+ this.moduleUrl = moduleUrl;
10
+ this.analysisData = analysisData;
11
+ this.srcPath = getSrcPath(moduleUrl);
12
+ this.componentsPath = path.dirname(getComponentsJsPath(moduleUrl));
13
+
14
+ // Configuration
15
+ this.config = {
16
+ maxCriticalSize: 50 * 1024, // 50KB
17
+ maxCriticalComponents: 15,
18
+ minSharedUsage: 3, // Minimum routes to be considered "shared"
19
+ strategy: 'hybrid' // 'global', 'hybrid', 'per-route'
20
+ };
21
+
22
+ this.bundles = {
23
+ critical: {
24
+ components: [],
25
+ size: 0,
26
+ file: 'slice-bundle.critical.js'
27
+ },
28
+ routes: {}
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Generates all bundles
34
+ */
35
+ async generate() {
36
+ console.log('🔨 Generating bundles...');
37
+
38
+ // 1. Determine optimal strategy
39
+ this.determineStrategy();
40
+
41
+ // 2. Identify critical components
42
+ this.identifyCriticalComponents();
43
+
44
+ // 3. Assign components to routes
45
+ this.assignRouteComponents();
46
+
47
+ // 4. Generate bundle files
48
+ const files = await this.generateBundleFiles();
49
+
50
+ // 5. Generate configuration
51
+ const config = this.generateBundleConfig();
52
+
53
+ console.log('✅ Bundles generated successfully');
54
+
55
+ return {
56
+ bundles: this.bundles,
57
+ config,
58
+ files
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Determines the optimal bundling strategy
64
+ */
65
+ determineStrategy() {
66
+ const { metrics } = this.analysisData;
67
+ const { totalComponents, sharedPercentage } = metrics;
68
+
69
+ // Strategy based on size and usage pattern
70
+ if (totalComponents < 20 || sharedPercentage > 60) {
71
+ this.config.strategy = 'global';
72
+ console.log('📦 Strategy: Global Bundle (small project or highly shared)');
73
+ } else if (totalComponents < 60) {
74
+ this.config.strategy = 'hybrid';
75
+ console.log('📦 Strategy: Hybrid (critical + per route)');
76
+ } else {
77
+ this.config.strategy = 'per-route';
78
+ console.log('📦 Strategy: Per Route (large project)');
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Identifies critical components for the initial bundle
84
+ */
85
+ identifyCriticalComponents() {
86
+ const { components } = this.analysisData;
87
+
88
+ // Filter critical candidates
89
+ const candidates = components
90
+ .filter(comp => {
91
+ // Shared components (used in 3+ routes)
92
+ const isShared = comp.routes.size >= this.config.minSharedUsage;
93
+
94
+ // Structural components (Navbar, Footer, etc.)
95
+ const isStructural = comp.categoryType === 'Structural' ||
96
+ ['Navbar', 'Footer', 'Layout'].includes(comp.name);
97
+
98
+ // Small and highly used components
99
+ const isSmallAndUseful = comp.size < 5000 && comp.routes.size >= 2;
100
+
101
+ return isShared || isStructural || isSmallAndUseful;
102
+ })
103
+ .sort((a, b) => {
104
+ // Prioritize by: (usage * 10) - size
105
+ const priorityA = (a.routes.size * 10) - (a.size / 1000);
106
+ const priorityB = (b.routes.size * 10) - (b.size / 1000);
107
+ return priorityB - priorityA;
108
+ });
109
+
110
+ // Fill critical bundle up to limit
111
+ for (const comp of candidates) {
112
+ const wouldExceedSize = this.bundles.critical.size + comp.size > this.config.maxCriticalSize;
113
+ const wouldExceedCount = this.bundles.critical.components.length >= this.config.maxCriticalComponents;
114
+
115
+ if (wouldExceedSize || wouldExceedCount) break;
116
+
117
+ this.bundles.critical.components.push(comp);
118
+ this.bundles.critical.size += comp.size;
119
+ }
120
+
121
+ console.log(`✓ Critical bundle: ${this.bundles.critical.components.length} components, ${(this.bundles.critical.size / 1024).toFixed(1)} KB`);
122
+ }
123
+
124
+ /**
125
+ * Assigns remaining components to route bundles
126
+ */
127
+ assignRouteComponents() {
128
+ const criticalNames = new Set(this.bundles.critical.components.map(c => c.name));
129
+
130
+ for (const [routePath, route] of this.analysisData.routes) {
131
+ // Get all route dependencies
132
+ const routeComponents = this.getRouteComponents(route.component);
133
+
134
+ // Filter those already in critical
135
+ const uniqueComponents = routeComponents.filter(comp =>
136
+ !criticalNames.has(comp.name)
137
+ );
138
+
139
+ if (uniqueComponents.length === 0) continue;
140
+
141
+ const routeKey = this.routeToFileName(routePath);
142
+ const totalSize = uniqueComponents.reduce((sum, c) => sum + c.size, 0);
143
+
144
+ this.bundles.routes[routeKey] = {
145
+ path: routePath,
146
+ components: uniqueComponents,
147
+ size: totalSize,
148
+ file: `slice-bundle.${routeKey}.js`
149
+ };
150
+
151
+ console.log(`✓ Bundle ${routeKey}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB`);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Gets all components needed for a route
157
+ */
158
+ getRouteComponents(componentName) {
159
+ const result = [];
160
+ const visited = new Set();
161
+
162
+ const traverse = (name) => {
163
+ if (visited.has(name)) return;
164
+ visited.add(name);
165
+
166
+ const component = this.analysisData.components.find(c => c.name === name);
167
+ if (!component) return;
168
+
169
+ result.push(component);
170
+
171
+ // Add dependencies recursively
172
+ for (const dep of component.dependencies) {
173
+ traverse(dep);
174
+ }
175
+ };
176
+
177
+ traverse(componentName);
178
+ return result;
179
+ }
180
+
181
+ /**
182
+ * Generates the physical bundle files
183
+ */
184
+ async generateBundleFiles() {
185
+ const files = [];
186
+
187
+ // 1. Critical bundle
188
+ if (this.bundles.critical.components.length > 0) {
189
+ const criticalFile = await this.createBundleFile(
190
+ this.bundles.critical.components,
191
+ 'critical',
192
+ null
193
+ );
194
+ files.push(criticalFile);
195
+ }
196
+
197
+ // 2. Route bundles
198
+ for (const [routeKey, bundle] of Object.entries(this.bundles.routes)) {
199
+ const routeFile = await this.createBundleFile(
200
+ bundle.components,
201
+ 'route',
202
+ bundle.path
203
+ );
204
+ files.push(routeFile);
205
+ }
206
+
207
+ return files;
208
+ }
209
+
210
+ /**
211
+ * Creates a bundle file
212
+ */
213
+ async createBundleFile(components, type, routePath) {
214
+ const routeKey = routePath ? this.routeToFileName(routePath) : 'critical';
215
+ const fileName = `slice-bundle.${routeKey}.js`;
216
+ const filePath = path.join(this.srcPath, fileName);
217
+
218
+ const bundleContent = await this.generateBundleContent(
219
+ components,
220
+ type,
221
+ routePath
222
+ );
223
+
224
+ await fs.writeFile(filePath, bundleContent, 'utf-8');
225
+
226
+ const hash = crypto.createHash('md5').update(bundleContent).digest('hex').substring(0, 12);
227
+
228
+ return {
229
+ name: routeKey,
230
+ file: fileName,
231
+ path: filePath,
232
+ size: Buffer.byteLength(bundleContent, 'utf-8'),
233
+ hash,
234
+ componentCount: components.length
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Generates the content of a bundle
240
+ */
241
+ async generateBundleContent(components, type, routePath) {
242
+ const componentsData = {};
243
+
244
+ for (const comp of components) {
245
+ const jsContent = await fs.readFile(
246
+ path.join(comp.path, `${comp.name}.js`),
247
+ 'utf-8'
248
+ );
249
+
250
+ let htmlContent = null;
251
+ let cssContent = null;
252
+
253
+ const htmlPath = path.join(comp.path, `${comp.name}.html`);
254
+ const cssPath = path.join(comp.path, `${comp.name}.css`);
255
+
256
+ if (await fs.pathExists(htmlPath)) {
257
+ htmlContent = await fs.readFile(htmlPath, 'utf-8');
258
+ }
259
+
260
+ if (await fs.pathExists(cssPath)) {
261
+ cssContent = await fs.readFile(cssPath, 'utf-8');
262
+ }
263
+
264
+ componentsData[comp.name] = {
265
+ name: comp.name,
266
+ category: comp.category,
267
+ categoryType: comp.categoryType,
268
+ js: this.cleanJavaScript(jsContent),
269
+ html: htmlContent,
270
+ css: cssContent,
271
+ size: comp.size,
272
+ dependencies: Array.from(comp.dependencies)
273
+ };
274
+ }
275
+
276
+ const metadata = {
277
+ version: '2.0.0',
278
+ type,
279
+ route: routePath,
280
+ generated: new Date().toISOString(),
281
+ totalSize: components.reduce((sum, c) => sum + c.size, 0),
282
+ componentCount: components.length,
283
+ strategy: this.config.strategy
284
+ };
285
+
286
+ return this.formatBundleFile(componentsData, metadata);
287
+ }
288
+
289
+ /**
290
+ * Cleans JavaScript code by removing imports/exports
291
+ */
292
+ cleanJavaScript(code) {
293
+ // Remove export default
294
+ code = code.replace(/export\s+default\s+/g, '');
295
+
296
+ // Remove imports (components will already be available)
297
+ code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
298
+
299
+ return code;
300
+ }
301
+
302
+ /**
303
+ * Formats the bundle file
304
+ */
305
+ formatBundleFile(componentsData, metadata) {
306
+ return `/**
307
+ * Slice.js Bundle
308
+ * Type: ${metadata.type}
309
+ * Generated: ${metadata.generated}
310
+ * Strategy: ${metadata.strategy}
311
+ * Components: ${metadata.componentCount}
312
+ * Total Size: ${(metadata.totalSize / 1024).toFixed(1)} KB
313
+ */
314
+
315
+ export const SLICE_BUNDLE = {
316
+ metadata: ${JSON.stringify(metadata, null, 2)},
317
+
318
+ components: ${JSON.stringify(componentsData, null, 2)}
319
+ };
320
+
321
+ // Auto-registration of components
322
+ if (window.slice && window.slice.controller) {
323
+ slice.controller.registerBundle(SLICE_BUNDLE);
324
+ }
325
+ `;
326
+ }
327
+
328
+ /**
329
+ * Generates the bundle configuration
330
+ */
331
+ generateBundleConfig() {
332
+ const config = {
333
+ version: '2.0.0',
334
+ strategy: this.config.strategy,
335
+ generated: new Date().toISOString(),
336
+
337
+ stats: {
338
+ totalComponents: this.analysisData.metrics.totalComponents,
339
+ totalRoutes: this.analysisData.metrics.totalRoutes,
340
+ sharedComponents: this.bundles.critical.components.length,
341
+ sharedPercentage: this.analysisData.metrics.sharedPercentage,
342
+ totalSize: this.analysisData.metrics.totalSize,
343
+ criticalSize: this.bundles.critical.size
344
+ },
345
+
346
+ bundles: {
347
+ critical: {
348
+ file: this.bundles.critical.file,
349
+ size: this.bundles.critical.size,
350
+ components: this.bundles.critical.components.map(c => c.name)
351
+ },
352
+ routes: {}
353
+ }
354
+ };
355
+
356
+ for (const [key, bundle] of Object.entries(this.bundles.routes)) {
357
+ config.bundles.routes[key] = {
358
+ path: bundle.path,
359
+ file: bundle.file,
360
+ size: bundle.size,
361
+ components: bundle.components.map(c => c.name),
362
+ dependencies: ['critical']
363
+ };
364
+ }
365
+
366
+ return config;
367
+ }
368
+
369
+ /**
370
+ * Converts a route to filename
371
+ */
372
+ routeToFileName(routePath) {
373
+ if (routePath === '/') return 'home';
374
+ return routePath
375
+ .replace(/^\//, '')
376
+ .replace(/\//g, '-')
377
+ .replace(/[^a-zA-Z0-9-]/g, '')
378
+ .toLowerCase();
379
+ }
380
+
381
+ /**
382
+ * Saves the configuration to file
383
+ */
384
+ async saveBundleConfig(config) {
385
+ const configPath = path.join(this.srcPath, 'bundle.config.json');
386
+ await fs.writeJson(configPath, config, { spaces: 2 });
387
+ console.log(`✓ Configuration saved to ${configPath}`);
388
+ }
389
+ }