slicejs-cli 2.2.4 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/buildProduction/buildProduction.js +221 -309
- package/commands/startServer/startServer.js +34 -15
- package/package.json +28 -28
- package/post.js +29 -52
|
@@ -1,233 +1,198 @@
|
|
|
1
|
-
// commands/buildProduction/buildProduction.js
|
|
1
|
+
// commands/buildProduction/buildProduction.js - CON SLICECONFIG PORT
|
|
2
2
|
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import
|
|
6
|
+
import UglifyJS from 'uglify-js';
|
|
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
|
-
*
|
|
14
|
+
* Carga la configuración desde sliceConfig.json
|
|
15
15
|
*/
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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;
|
|
49
24
|
}
|
|
50
|
-
}
|
|
25
|
+
};
|
|
51
26
|
|
|
52
27
|
/**
|
|
53
|
-
*
|
|
28
|
+
* Verifica dependencias necesarias para el build
|
|
54
29
|
*/
|
|
55
|
-
async function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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;
|
|
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;
|
|
74
37
|
}
|
|
38
|
+
|
|
39
|
+
return true;
|
|
75
40
|
}
|
|
76
41
|
|
|
77
42
|
/**
|
|
78
|
-
*
|
|
43
|
+
* Copia sliceConfig.json al directorio dist
|
|
79
44
|
*/
|
|
80
|
-
async function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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;
|
|
45
|
+
async function copySliceConfig() {
|
|
46
|
+
const srcConfig = path.join(__dirname, '../../../../src/sliceConfig.json');
|
|
47
|
+
const distConfig = path.join(__dirname, '../../../../dist/sliceConfig.json');
|
|
48
|
+
|
|
49
|
+
if (await fs.pathExists(srcConfig)) {
|
|
50
|
+
await fs.copy(srcConfig, distConfig);
|
|
51
|
+
Print.info('sliceConfig.json copied to dist');
|
|
99
52
|
}
|
|
100
53
|
}
|
|
101
54
|
|
|
102
55
|
/**
|
|
103
|
-
*
|
|
56
|
+
* Procesa un directorio completo
|
|
104
57
|
*/
|
|
105
|
-
async function
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
Print.minificationResult(filename, originalSize, minifiedSize, savings);
|
|
58
|
+
async function processDirectory(srcPath, distPath, baseSrcPath) {
|
|
59
|
+
const items = await fs.readdir(srcPath);
|
|
60
|
+
|
|
61
|
+
for (const item of items) {
|
|
62
|
+
const srcItemPath = path.join(srcPath, item);
|
|
63
|
+
const distItemPath = path.join(distPath, item);
|
|
64
|
+
const stat = await fs.stat(srcItemPath);
|
|
114
65
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
66
|
+
if (stat.isDirectory()) {
|
|
67
|
+
await fs.ensureDir(distItemPath);
|
|
68
|
+
await processDirectory(srcItemPath, distItemPath, baseSrcPath);
|
|
69
|
+
} else {
|
|
70
|
+
await processFile(srcItemPath, distItemPath);
|
|
71
|
+
}
|
|
119
72
|
}
|
|
120
73
|
}
|
|
121
74
|
|
|
122
75
|
/**
|
|
123
|
-
* Procesa un archivo
|
|
76
|
+
* Procesa un archivo individual
|
|
124
77
|
*/
|
|
125
|
-
async function processFile(
|
|
78
|
+
async function processFile(srcFilePath, distFilePath) {
|
|
79
|
+
const ext = path.extname(srcFilePath).toLowerCase();
|
|
80
|
+
|
|
126
81
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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;
|
|
82
|
+
if (ext === '.js') {
|
|
83
|
+
await minifyJavaScript(srcFilePath, distFilePath);
|
|
84
|
+
} else if (ext === '.css') {
|
|
85
|
+
await minifyCSS(srcFilePath, distFilePath);
|
|
86
|
+
} else if (ext === '.html') {
|
|
87
|
+
await minifyHTML(srcFilePath, distFilePath);
|
|
88
|
+
} else {
|
|
89
|
+
// Copiar archivos que no necesitan minificación
|
|
90
|
+
await fs.copy(srcFilePath, distFilePath);
|
|
145
91
|
}
|
|
146
|
-
|
|
147
|
-
await fs.writeFile(destPath, processedContent, 'utf8');
|
|
148
|
-
|
|
149
92
|
} catch (error) {
|
|
150
|
-
Print.error(`
|
|
151
|
-
|
|
93
|
+
Print.error(`Processing ${path.basename(srcFilePath)}: ${error.message}`);
|
|
94
|
+
// Copiar archivo original si falla la minificación
|
|
95
|
+
await fs.copy(srcFilePath, distFilePath);
|
|
152
96
|
}
|
|
153
97
|
}
|
|
154
98
|
|
|
155
99
|
/**
|
|
156
|
-
*
|
|
100
|
+
* Minifica archivos JavaScript
|
|
157
101
|
*/
|
|
158
|
-
async function
|
|
159
|
-
const
|
|
102
|
+
async function minifyJavaScript(srcPath, distPath) {
|
|
103
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
104
|
+
const originalSize = Buffer.byteLength(content, 'utf8');
|
|
160
105
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
}
|
|
106
|
+
const result = UglifyJS.minify(content, {
|
|
107
|
+
compress: {
|
|
108
|
+
drop_console: false,
|
|
109
|
+
drop_debugger: true,
|
|
110
|
+
pure_funcs: ['console.log']
|
|
111
|
+
},
|
|
112
|
+
mangle: {
|
|
113
|
+
reserved: ['slice', 'Slice']
|
|
114
|
+
},
|
|
115
|
+
output: {
|
|
116
|
+
comments: false
|
|
181
117
|
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (result.error) {
|
|
121
|
+
throw new Error(`UglifyJS error: ${result.error}`);
|
|
182
122
|
}
|
|
123
|
+
|
|
124
|
+
await fs.writeFile(distPath, result.code, 'utf8');
|
|
125
|
+
|
|
126
|
+
const minifiedSize = Buffer.byteLength(result.code, 'utf8');
|
|
127
|
+
const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
|
|
128
|
+
|
|
129
|
+
Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
|
|
183
130
|
}
|
|
184
131
|
|
|
185
132
|
/**
|
|
186
|
-
*
|
|
133
|
+
* Minifica archivos CSS
|
|
187
134
|
*/
|
|
188
|
-
async function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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;
|
|
135
|
+
async function minifyCSS(srcPath, distPath) {
|
|
136
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
137
|
+
const originalSize = Buffer.byteLength(content, 'utf8');
|
|
138
|
+
|
|
139
|
+
const cleanCSS = new CleanCSS({
|
|
140
|
+
level: 2,
|
|
141
|
+
returnPromise: false
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const result = cleanCSS.minify(content);
|
|
145
|
+
|
|
146
|
+
if (result.errors.length > 0) {
|
|
147
|
+
throw new Error(`CleanCSS errors: ${result.errors.join(', ')}`);
|
|
211
148
|
}
|
|
149
|
+
|
|
150
|
+
await fs.writeFile(distPath, result.styles, 'utf8');
|
|
151
|
+
|
|
152
|
+
const minifiedSize = Buffer.byteLength(result.styles, 'utf8');
|
|
153
|
+
const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
|
|
154
|
+
|
|
155
|
+
Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
|
|
212
156
|
}
|
|
213
157
|
|
|
214
158
|
/**
|
|
215
|
-
*
|
|
159
|
+
* Minifica archivos HTML
|
|
216
160
|
*/
|
|
217
|
-
async function
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
161
|
+
async function minifyHTML(srcPath, distPath) {
|
|
162
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
163
|
+
const originalSize = Buffer.byteLength(content, 'utf8');
|
|
164
|
+
|
|
165
|
+
const minified = await minify(content, {
|
|
166
|
+
collapseWhitespace: true,
|
|
167
|
+
removeComments: true,
|
|
168
|
+
removeRedundantAttributes: true,
|
|
169
|
+
removeScriptTypeAttributes: true,
|
|
170
|
+
removeStyleLinkTypeAttributes: true,
|
|
171
|
+
useShortDoctype: true,
|
|
172
|
+
minifyCSS: true,
|
|
173
|
+
minifyJS: true
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
await fs.writeFile(distPath, minified, 'utf8');
|
|
177
|
+
|
|
178
|
+
const minifiedSize = Buffer.byteLength(minified, 'utf8');
|
|
179
|
+
const savings = Math.round(((originalSize - minifiedSize) / originalSize) * 100);
|
|
180
|
+
|
|
181
|
+
Print.minificationResult(path.basename(srcPath), originalSize, minifiedSize, savings);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Crea un bundle optimizado del archivo principal
|
|
186
|
+
*/
|
|
187
|
+
async function createOptimizedBundle() {
|
|
188
|
+
Print.buildProgress('Creating optimized bundle...');
|
|
189
|
+
|
|
190
|
+
const mainJSPath = path.join(__dirname, '../../../../dist/App/index.js');
|
|
191
|
+
|
|
192
|
+
if (await fs.pathExists(mainJSPath)) {
|
|
193
|
+
Print.success('Main bundle optimized');
|
|
194
|
+
} else {
|
|
195
|
+
Print.warning('No main JavaScript file found for bundling');
|
|
231
196
|
}
|
|
232
197
|
}
|
|
233
198
|
|
|
@@ -235,56 +200,76 @@ async function copySliceConfig() {
|
|
|
235
200
|
* Genera estadísticas del build
|
|
236
201
|
*/
|
|
237
202
|
async function generateBuildStats(srcDir, distDir) {
|
|
238
|
-
|
|
239
|
-
|
|
203
|
+
Print.buildProgress('Generating build statistics...');
|
|
204
|
+
|
|
205
|
+
const getDirectorySize = async (dirPath) => {
|
|
206
|
+
let totalSize = 0;
|
|
207
|
+
const items = await fs.readdir(dirPath);
|
|
240
208
|
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
const
|
|
209
|
+
for (const item of items) {
|
|
210
|
+
const itemPath = path.join(dirPath, item);
|
|
211
|
+
const stat = await fs.stat(itemPath);
|
|
244
212
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
} else {
|
|
250
|
-
const stats = await fs.stat(filePath);
|
|
251
|
-
totalSize += stats.size;
|
|
252
|
-
}
|
|
213
|
+
if (stat.isDirectory()) {
|
|
214
|
+
totalSize += await getDirectorySize(itemPath);
|
|
215
|
+
} else {
|
|
216
|
+
totalSize += stat.size;
|
|
253
217
|
}
|
|
254
|
-
|
|
255
|
-
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return totalSize;
|
|
221
|
+
};
|
|
256
222
|
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
const
|
|
223
|
+
try {
|
|
224
|
+
const srcSize = await getDirectorySize(srcDir);
|
|
225
|
+
const distSize = await getDirectorySize(distDir);
|
|
226
|
+
const savings = Math.round(((srcSize - distSize) / srcSize) * 100);
|
|
260
227
|
|
|
261
228
|
Print.newLine();
|
|
262
|
-
Print.
|
|
263
|
-
console.log(
|
|
264
|
-
console.log(
|
|
265
|
-
console.log(
|
|
229
|
+
Print.info(`📊 Build Statistics:`);
|
|
230
|
+
console.log(` Source: ${(srcSize / 1024).toFixed(1)} KB`);
|
|
231
|
+
console.log(` Built: ${(distSize / 1024).toFixed(1)} KB`);
|
|
232
|
+
console.log(` Saved: ${savings}% smaller`);
|
|
266
233
|
|
|
267
234
|
} catch (error) {
|
|
268
|
-
Print.warning(
|
|
235
|
+
Print.warning('Could not generate build statistics');
|
|
269
236
|
}
|
|
270
237
|
}
|
|
271
238
|
|
|
272
239
|
/**
|
|
273
|
-
*
|
|
240
|
+
* Analiza el build sin construir
|
|
241
|
+
*/
|
|
242
|
+
async function analyzeBuild() {
|
|
243
|
+
const distDir = path.join(__dirname, '../../../../dist');
|
|
244
|
+
|
|
245
|
+
if (!await fs.pathExists(distDir)) {
|
|
246
|
+
Print.error('No build found to analyze. Run "slice build" first.');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
Print.info('Analyzing production build...');
|
|
251
|
+
await generateBuildStats(
|
|
252
|
+
path.join(__dirname, '../../../../src'),
|
|
253
|
+
distDir
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Función principal de build
|
|
274
259
|
*/
|
|
275
260
|
export default async function buildProduction(options = {}) {
|
|
276
261
|
const startTime = Date.now();
|
|
277
262
|
|
|
278
263
|
try {
|
|
279
|
-
Print.title('
|
|
264
|
+
Print.title('🔨 Building Slice.js project for production...');
|
|
280
265
|
Print.newLine();
|
|
281
266
|
|
|
282
|
-
// Verificar que existe src
|
|
283
267
|
const srcDir = path.join(__dirname, '../../../../src');
|
|
284
268
|
const distDir = path.join(__dirname, '../../../../dist');
|
|
285
269
|
|
|
270
|
+
// Verificar que existe el directorio src
|
|
286
271
|
if (!await fs.pathExists(srcDir)) {
|
|
287
|
-
throw new Error('
|
|
272
|
+
throw new Error('Source directory not found. Run "slice init" first.');
|
|
288
273
|
}
|
|
289
274
|
|
|
290
275
|
// 1. Limpiar directorio dist
|
|
@@ -320,10 +305,9 @@ export default async function buildProduction(options = {}) {
|
|
|
320
305
|
Print.info('Your optimized project is ready in the /dist directory');
|
|
321
306
|
Print.newLine();
|
|
322
307
|
Print.info('Next steps:');
|
|
323
|
-
console.log(' •
|
|
324
|
-
console.log(' • Update your Express server to serve from /dist instead of /src');
|
|
308
|
+
console.log(' • Use "npm run slice:start" to test the production build');
|
|
325
309
|
console.log(' • Deploy both /api and /dist directories to your hosting provider');
|
|
326
|
-
console.log(' • Use "slice build --serve" to preview the production build
|
|
310
|
+
console.log(' • Use "slice build --serve" to preview the production build');
|
|
327
311
|
|
|
328
312
|
return true;
|
|
329
313
|
|
|
@@ -334,52 +318,64 @@ export default async function buildProduction(options = {}) {
|
|
|
334
318
|
}
|
|
335
319
|
|
|
336
320
|
/**
|
|
337
|
-
* Servidor de
|
|
338
|
-
* Usa Express como el servidor principal pero sirviendo desde /dist
|
|
321
|
+
* Servidor de preview para testing del build de producción
|
|
339
322
|
*/
|
|
340
|
-
export async function serveProductionBuild(port
|
|
323
|
+
export async function serveProductionBuild(port) {
|
|
341
324
|
try {
|
|
325
|
+
const config = loadConfig();
|
|
326
|
+
const defaultPort = config?.server?.port || 3001;
|
|
327
|
+
const finalPort = port || defaultPort;
|
|
328
|
+
|
|
342
329
|
const distDir = path.join(__dirname, '../../../../dist');
|
|
343
330
|
|
|
344
331
|
if (!await fs.pathExists(distDir)) {
|
|
345
332
|
throw new Error('No production build found. Run "slice build" first.');
|
|
346
333
|
}
|
|
347
334
|
|
|
348
|
-
Print.info(`Starting production
|
|
335
|
+
Print.info(`Starting production preview server on port ${finalPort}...`);
|
|
349
336
|
|
|
350
|
-
// Implementar servidor estático simple
|
|
337
|
+
// Implementar servidor estático simple
|
|
351
338
|
const express = await import('express');
|
|
352
339
|
const app = express.default();
|
|
353
340
|
|
|
354
|
-
// Servir archivos estáticos desde dist
|
|
341
|
+
// Servir archivos estáticos desde dist
|
|
355
342
|
app.use(express.default.static(distDir));
|
|
356
343
|
|
|
357
344
|
// SPA fallback - servir index.html para rutas no encontradas
|
|
358
345
|
app.get('*', (req, res) => {
|
|
359
|
-
const indexPath = path.join(distDir, 'index.html');
|
|
346
|
+
const indexPath = path.join(distDir, 'App/index.html');
|
|
347
|
+
const fallbackPath = path.join(distDir, 'index.html');
|
|
348
|
+
|
|
349
|
+
// Intentar primero App/index.html, luego index.html
|
|
360
350
|
if (fs.existsSync(indexPath)) {
|
|
361
351
|
res.sendFile(indexPath);
|
|
352
|
+
} else if (fs.existsSync(fallbackPath)) {
|
|
353
|
+
res.sendFile(fallbackPath);
|
|
362
354
|
} else {
|
|
363
|
-
res.status(404).send('Production build not found');
|
|
355
|
+
res.status(404).send('Production build index.html not found');
|
|
364
356
|
}
|
|
365
357
|
});
|
|
366
358
|
|
|
367
|
-
app.listen(
|
|
368
|
-
Print.success(`Production
|
|
359
|
+
app.listen(finalPort, () => {
|
|
360
|
+
Print.success(`Production preview server running at http://localhost:${finalPort}`);
|
|
369
361
|
Print.info('Press Ctrl+C to stop the server');
|
|
370
|
-
Print.info('This server
|
|
362
|
+
Print.info('This server previews your production build from /dist');
|
|
363
|
+
Print.warning('This is a preview server - use "npm run slice:start" for the full production server');
|
|
371
364
|
});
|
|
372
365
|
|
|
373
366
|
} catch (error) {
|
|
374
|
-
Print.error(`Error starting production server: ${error.message}`);
|
|
367
|
+
Print.error(`Error starting production preview server: ${error.message}`);
|
|
375
368
|
throw error;
|
|
376
369
|
}
|
|
377
370
|
}
|
|
378
371
|
|
|
379
372
|
/**
|
|
380
|
-
* Comando build con opciones
|
|
373
|
+
* Comando build con opciones
|
|
381
374
|
*/
|
|
382
375
|
export async function buildCommand(options = {}) {
|
|
376
|
+
const config = loadConfig();
|
|
377
|
+
const defaultPort = config?.server?.port || 3001;
|
|
378
|
+
|
|
383
379
|
// Verificar dependencias necesarias
|
|
384
380
|
if (!await checkBuildDependencies()) {
|
|
385
381
|
return false;
|
|
@@ -387,7 +383,7 @@ export async function buildCommand(options = {}) {
|
|
|
387
383
|
|
|
388
384
|
if (options.serve) {
|
|
389
385
|
// Solo servir build existente
|
|
390
|
-
await serveProductionBuild(options.port);
|
|
386
|
+
await serveProductionBuild(options.port || defaultPort);
|
|
391
387
|
return true;
|
|
392
388
|
}
|
|
393
389
|
|
|
@@ -400,96 +396,12 @@ export async function buildCommand(options = {}) {
|
|
|
400
396
|
// Build completo
|
|
401
397
|
const success = await buildProduction(options);
|
|
402
398
|
|
|
403
|
-
// Solo mostrar mensaje informativo, no ejecutar servidor automáticamente
|
|
404
399
|
if (success && options.preview) {
|
|
405
400
|
Print.newLine();
|
|
406
401
|
Print.info('✨ Build completed successfully!');
|
|
407
|
-
Print.info(
|
|
408
|
-
|
|
402
|
+
Print.info(`Starting preview server on port ${options.port || defaultPort}...`);
|
|
403
|
+
await serveProductionBuild(options.port || defaultPort);
|
|
409
404
|
}
|
|
410
405
|
|
|
411
406
|
return success;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Verifica que las dependencias de build estén instaladas en el CLI
|
|
417
|
-
*/
|
|
418
|
-
async function checkBuildDependencies() {
|
|
419
|
-
try {
|
|
420
|
-
Print.info('Checking build dependencies...');
|
|
421
|
-
|
|
422
|
-
// Verificar dependencias en el CLI en lugar del proyecto
|
|
423
|
-
const cliPackageJsonPath = path.join(__dirname, '../../package.json');
|
|
424
|
-
|
|
425
|
-
if (!await fs.pathExists(cliPackageJsonPath)) {
|
|
426
|
-
throw new Error('CLI package.json not found');
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const cliPackageJson = await fs.readJson(cliPackageJsonPath);
|
|
430
|
-
const deps = { ...cliPackageJson.dependencies, ...cliPackageJson.devDependencies };
|
|
431
|
-
|
|
432
|
-
const requiredDeps = ['terser', 'clean-css', 'html-minifier-terser'];
|
|
433
|
-
const missing = requiredDeps.filter(dep => !deps[dep]);
|
|
434
|
-
|
|
435
|
-
if (missing.length > 0) {
|
|
436
|
-
Print.error('Missing build dependencies in CLI:');
|
|
437
|
-
missing.forEach(dep => console.log(` • ${dep}`));
|
|
438
|
-
Print.newLine();
|
|
439
|
-
Print.info('Please update slicejs-cli to the latest version:');
|
|
440
|
-
console.log('npm install -g slicejs-cli@latest');
|
|
441
|
-
return false;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
Print.success('All build dependencies are available in CLI');
|
|
445
|
-
return true;
|
|
446
|
-
|
|
447
|
-
} catch (error) {
|
|
448
|
-
Print.error(`Error checking dependencies: ${error.message}`);
|
|
449
|
-
return false;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Analiza el tamaño y composición del build
|
|
455
|
-
*/
|
|
456
|
-
async function analyzeBuild() {
|
|
457
|
-
try {
|
|
458
|
-
const distDir = path.join(__dirname, '../../../../dist');
|
|
459
|
-
|
|
460
|
-
if (!await fs.pathExists(distDir)) {
|
|
461
|
-
throw new Error('No production build found. Run "slice build" first.');
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
Print.title('📊 Build Analysis');
|
|
465
|
-
Print.newLine();
|
|
466
|
-
|
|
467
|
-
const analyzeDirectory = async (dir, prefix = '') => {
|
|
468
|
-
const items = await fs.readdir(dir);
|
|
469
|
-
let totalSize = 0;
|
|
470
|
-
|
|
471
|
-
for (const item of items) {
|
|
472
|
-
const itemPath = path.join(dir, item);
|
|
473
|
-
const stat = await fs.stat(itemPath);
|
|
474
|
-
|
|
475
|
-
if (stat.isDirectory()) {
|
|
476
|
-
const dirSize = await analyzeDirectory(itemPath, `${prefix}${item}/`);
|
|
477
|
-
totalSize += dirSize;
|
|
478
|
-
} else {
|
|
479
|
-
const size = (stat.size / 1024).toFixed(1);
|
|
480
|
-
console.log(`📄 ${prefix}${item}: ${size} KB`);
|
|
481
|
-
totalSize += stat.size;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return totalSize;
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
const totalSize = await analyzeDirectory(distDir);
|
|
489
|
-
Print.newLine();
|
|
490
|
-
Print.info(`Total build size: ${(totalSize / 1024).toFixed(1)} KB`);
|
|
491
|
-
|
|
492
|
-
} catch (error) {
|
|
493
|
-
Print.error(`Error analyzing build: ${error.message}`);
|
|
494
|
-
}
|
|
495
407
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// commands/startServer/startServer.js
|
|
1
|
+
// commands/startServer/startServer.js - CON ARGUMENTOS
|
|
2
2
|
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import path from 'path';
|
|
@@ -8,6 +8,20 @@ import Print from '../Print.js';
|
|
|
8
8
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Carga la configuración desde sliceConfig.json
|
|
13
|
+
*/
|
|
14
|
+
const loadConfig = () => {
|
|
15
|
+
try {
|
|
16
|
+
const configPath = path.join(__dirname, '../../../../src/sliceConfig.json');
|
|
17
|
+
const rawData = fs.readFileSync(configPath, 'utf-8');
|
|
18
|
+
return JSON.parse(rawData);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
Print.error(`Loading configuration: ${error.message}`);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
11
25
|
/**
|
|
12
26
|
* Verifica si existe un build de producción
|
|
13
27
|
*/
|
|
@@ -27,7 +41,7 @@ async function checkDevelopmentStructure() {
|
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
/**
|
|
30
|
-
* Inicia el servidor Node.js
|
|
44
|
+
* Inicia el servidor Node.js con argumentos
|
|
31
45
|
*/
|
|
32
46
|
function startNodeServer(port, mode) {
|
|
33
47
|
return new Promise((resolve, reject) => {
|
|
@@ -35,13 +49,20 @@ function startNodeServer(port, mode) {
|
|
|
35
49
|
|
|
36
50
|
Print.info(`Starting ${mode} server on port ${port}...`);
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
// Construir argumentos basados en el modo
|
|
53
|
+
const args = [apiIndexPath];
|
|
54
|
+
if (mode === 'production') {
|
|
55
|
+
args.push('--production');
|
|
56
|
+
} else {
|
|
57
|
+
args.push('--development');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const serverProcess = spawn('node', args, {
|
|
39
61
|
stdio: 'inherit',
|
|
40
62
|
env: {
|
|
41
63
|
...process.env,
|
|
42
|
-
PORT: port
|
|
43
|
-
|
|
44
|
-
SLICE_CLI_MODE: 'true' // Flag para que api/index.js sepa que viene del CLI
|
|
64
|
+
PORT: port
|
|
65
|
+
// Ya no necesitamos NODE_ENV ni SLICE_CLI_MODE
|
|
45
66
|
}
|
|
46
67
|
});
|
|
47
68
|
|
|
@@ -57,26 +78,24 @@ function startNodeServer(port, mode) {
|
|
|
57
78
|
process.exit(0);
|
|
58
79
|
});
|
|
59
80
|
|
|
60
|
-
// Manejar cierre del proceso
|
|
61
81
|
process.on('SIGTERM', () => {
|
|
62
82
|
serverProcess.kill('SIGTERM');
|
|
63
83
|
});
|
|
64
84
|
|
|
65
|
-
// El servidor se considera iniciado exitosamente después de un breve delay
|
|
66
85
|
setTimeout(() => {
|
|
67
|
-
Print.success(`${mode === 'production' ? 'Production' : 'Development'} server running at http://localhost:${port}`);
|
|
68
|
-
Print.info(`Serving files from /${mode === 'production' ? 'dist' : 'src'} directory`);
|
|
69
|
-
Print.info('Press Ctrl+C to stop the server');
|
|
70
86
|
resolve(serverProcess);
|
|
71
|
-
},
|
|
87
|
+
}, 500);
|
|
72
88
|
});
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
/**
|
|
76
|
-
* Función principal para iniciar servidor
|
|
92
|
+
* Función principal para iniciar servidor
|
|
77
93
|
*/
|
|
78
94
|
export default async function startServer(options = {}) {
|
|
79
|
-
const
|
|
95
|
+
const config = loadConfig();
|
|
96
|
+
const defaultPort = config?.server?.port || 3000;
|
|
97
|
+
|
|
98
|
+
const { mode = 'development', port = defaultPort } = options;
|
|
80
99
|
|
|
81
100
|
try {
|
|
82
101
|
Print.title(`🚀 Starting Slice.js ${mode} server...`);
|
|
@@ -97,7 +116,7 @@ export default async function startServer(options = {}) {
|
|
|
97
116
|
Print.info('Development mode: serving files from /src with hot reload');
|
|
98
117
|
}
|
|
99
118
|
|
|
100
|
-
// Iniciar el servidor
|
|
119
|
+
// Iniciar el servidor con argumentos
|
|
101
120
|
await startNodeServer(port, mode);
|
|
102
121
|
|
|
103
122
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "slicejs-cli",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"description": "Command client for developing web applications with Slice.js framework",
|
|
5
|
-
"main": "client.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
-
"postinstall": "node post.js"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"framework",
|
|
12
|
-
"web",
|
|
13
|
-
"client",
|
|
14
|
-
"cli"
|
|
15
|
-
],
|
|
16
|
-
"author": "vkneider",
|
|
17
|
-
"type": "module",
|
|
18
|
-
"license": "ISC",
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"clean-css": "^5.3.3",
|
|
21
|
-
"commander": "^12.0.0",
|
|
22
|
-
"fs-extra": "^11.2.0",
|
|
23
|
-
"html-minifier-terser": "^7.2.0",
|
|
24
|
-
"inquirer": "^12.4.2",
|
|
25
|
-
"slicejs-web-framework": "latest",
|
|
26
|
-
"terser": "^5.43.1"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "slicejs-cli",
|
|
3
|
+
"version": "2.2.6",
|
|
4
|
+
"description": "Command client for developing web applications with Slice.js framework",
|
|
5
|
+
"main": "client.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"postinstall": "node post.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"framework",
|
|
12
|
+
"web",
|
|
13
|
+
"client",
|
|
14
|
+
"cli"
|
|
15
|
+
],
|
|
16
|
+
"author": "vkneider",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"clean-css": "^5.3.3",
|
|
21
|
+
"commander": "^12.0.0",
|
|
22
|
+
"fs-extra": "^11.2.0",
|
|
23
|
+
"html-minifier-terser": "^7.2.0",
|
|
24
|
+
"inquirer": "^12.4.2",
|
|
25
|
+
"slicejs-web-framework": "latest",
|
|
26
|
+
"terser": "^5.43.1"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/post.js
CHANGED
|
@@ -24,8 +24,8 @@ 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:dev'] = 'node
|
|
28
|
-
projectPackageJson.scripts['slice:start'] = 'node
|
|
27
|
+
projectPackageJson.scripts['slice:dev'] = 'node api/index.js --development';
|
|
28
|
+
projectPackageJson.scripts['slice:start'] = 'node api/index.js --production';
|
|
29
29
|
projectPackageJson.scripts['slice:build'] = 'node node_modules/slicejs-cli/client.js build';
|
|
30
30
|
projectPackageJson.scripts['slice:version'] = 'node node_modules/slicejs-cli/client.js version';
|
|
31
31
|
projectPackageJson.scripts['slice:update'] = 'node node_modules/slicejs-cli/client.js update';
|
|
@@ -50,9 +50,9 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
|
|
|
50
50
|
projectPackageJson.scripts['slice:build-preview'] = 'node node_modules/slicejs-cli/client.js build --preview';
|
|
51
51
|
projectPackageJson.scripts['slice:build-analyze'] = 'node node_modules/slicejs-cli/client.js build --analyze';
|
|
52
52
|
|
|
53
|
-
// Legacy/compatibility commands
|
|
54
|
-
projectPackageJson.scripts['run'] = 'node api/index.js';
|
|
55
|
-
projectPackageJson.scripts['development'] = 'node
|
|
53
|
+
// Legacy/compatibility commands - ACTUALIZADOS
|
|
54
|
+
projectPackageJson.scripts['run'] = 'node api/index.js --development';
|
|
55
|
+
projectPackageJson.scripts['development'] = 'node api/index.js --development';
|
|
56
56
|
|
|
57
57
|
// Module configuration
|
|
58
58
|
projectPackageJson.type = 'module';
|
|
@@ -91,6 +91,10 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
|
|
|
91
91
|
console.log(' 3. npm run slice:build - Build for production');
|
|
92
92
|
console.log(' 4. npm run slice:start - Test production build');
|
|
93
93
|
console.log('\n💡 Tip: Use "slice:sync" to keep your components updated');
|
|
94
|
+
console.log('\n🔧 New argument-based system:');
|
|
95
|
+
console.log(' • slice:dev → node api/index.js --development');
|
|
96
|
+
console.log(' • slice:start → node api/index.js --production');
|
|
97
|
+
console.log(' • Arguments take precedence over environment variables');
|
|
94
98
|
})
|
|
95
99
|
.catch(err => {
|
|
96
100
|
if (err.code === 'ENOENT') {
|
|
@@ -101,83 +105,56 @@ fs.promises.access(projectPackageJsonPath, fs.constants.F_OK)
|
|
|
101
105
|
description: 'Slice.js project',
|
|
102
106
|
main: 'api/index.js',
|
|
103
107
|
scripts: {
|
|
104
|
-
// Main workflow commands
|
|
108
|
+
// Main workflow commands - UPDATED with arguments
|
|
105
109
|
'slice:init': 'node node_modules/slicejs-cli/client.js init',
|
|
106
|
-
'slice:dev': 'node
|
|
107
|
-
'slice:start': 'node
|
|
110
|
+
'slice:dev': 'node api/index.js --development',
|
|
111
|
+
'slice:start': 'node api/index.js --production',
|
|
108
112
|
'slice:build': 'node node_modules/slicejs-cli/client.js build',
|
|
109
113
|
'slice:version': 'node node_modules/slicejs-cli/client.js version',
|
|
110
114
|
'slice:update': 'node node_modules/slicejs-cli/client.js update',
|
|
111
115
|
|
|
112
|
-
// Local
|
|
116
|
+
// Local component commands
|
|
113
117
|
'slice:create': 'node node_modules/slicejs-cli/client.js component create',
|
|
114
118
|
'slice:list': 'node node_modules/slicejs-cli/client.js component list',
|
|
115
119
|
'slice:delete': 'node node_modules/slicejs-cli/client.js component delete',
|
|
116
120
|
|
|
117
|
-
//
|
|
121
|
+
// Repository commands
|
|
118
122
|
'slice:get': 'node node_modules/slicejs-cli/client.js get',
|
|
119
123
|
'slice:browse': 'node node_modules/slicejs-cli/client.js browse',
|
|
120
124
|
'slice:sync': 'node node_modules/slicejs-cli/client.js sync',
|
|
121
125
|
|
|
122
|
-
// Detailed registry
|
|
123
|
-
'slice:registry-get': 'node node_modules/slicejs-cli/client.js registry get',
|
|
124
|
-
'slice:registry-list': 'node node_modules/slicejs-cli/client.js registry list',
|
|
125
|
-
'slice:registry-sync': 'node node_modules/slicejs-cli/client.js registry sync',
|
|
126
|
-
|
|
127
126
|
// Build utilities
|
|
128
127
|
'slice:build-serve': 'node node_modules/slicejs-cli/client.js build --serve',
|
|
129
128
|
'slice:build-preview': 'node node_modules/slicejs-cli/client.js build --preview',
|
|
130
129
|
'slice:build-analyze': 'node node_modules/slicejs-cli/client.js build --analyze',
|
|
131
130
|
|
|
132
|
-
// Legacy
|
|
133
|
-
'run': 'node api/index.js',
|
|
134
|
-
'development': 'node
|
|
131
|
+
// Legacy commands - UPDATED
|
|
132
|
+
'run': 'node api/index.js --development',
|
|
133
|
+
'development': 'node api/index.js --development'
|
|
135
134
|
},
|
|
136
|
-
keywords: ['slicejs', 'web-framework', 'components'],
|
|
137
|
-
author: '',
|
|
138
|
-
license: 'ISC',
|
|
139
135
|
type: 'module',
|
|
140
136
|
engines: {
|
|
141
137
|
"node": ">=20.0.0"
|
|
142
138
|
}
|
|
143
139
|
};
|
|
144
140
|
|
|
145
|
-
// Save the new package.json
|
|
146
141
|
return fs.promises.writeFile(projectPackageJsonPath, JSON.stringify(defaultPackageJson, null, 2), 'utf8');
|
|
147
142
|
} else {
|
|
148
|
-
console.error('Error:', err);
|
|
149
143
|
throw err;
|
|
150
144
|
}
|
|
151
145
|
})
|
|
152
146
|
.then(() => {
|
|
153
|
-
console.log('✅
|
|
154
|
-
console.log('\n
|
|
155
|
-
console.log(' npm run slice:
|
|
156
|
-
console.log(' npm run slice:
|
|
157
|
-
console.log('
|
|
158
|
-
console.log('
|
|
159
|
-
console.log('
|
|
160
|
-
console.log('
|
|
161
|
-
console.log('
|
|
162
|
-
console.log(' npm run slice:sync - Update local components to latest versions');
|
|
163
|
-
console.log('\n⚙️ Local component management:');
|
|
164
|
-
console.log(' npm run slice:create - Create local component');
|
|
165
|
-
console.log(' npm run slice:list - List local components');
|
|
166
|
-
console.log(' npm run slice:delete - Delete local component');
|
|
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:');
|
|
172
|
-
console.log(' npm run slice:version - View version information');
|
|
173
|
-
console.log(' npm run slice:update - Check for available updates');
|
|
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');
|
|
147
|
+
console.log('✅ SliceJS CLI commands configured successfully');
|
|
148
|
+
console.log('\n🎯 Updated workflow:');
|
|
149
|
+
console.log(' npm run slice:dev → node api/index.js --development (serves /src)');
|
|
150
|
+
console.log(' npm run slice:start → node api/index.js --production (serves /dist)');
|
|
151
|
+
console.log('\n🔧 Benefits:');
|
|
152
|
+
console.log(' • Clear argument-based mode detection');
|
|
153
|
+
console.log(' • No confusion between src/dist directories');
|
|
154
|
+
console.log(' • Maintains backward compatibility with NODE_ENV');
|
|
155
|
+
console.log(' • More reliable and predictable behavior');
|
|
180
156
|
})
|
|
181
157
|
.catch(err => {
|
|
182
|
-
console.error('Error
|
|
183
|
-
|
|
158
|
+
console.error('❌ Error setting up package.json:', err.message);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|