slicejs-cli 3.6.3 → 3.6.5
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/LICENSE +21 -21
- package/README.md +225 -226
- package/client.js +740 -744
- package/commands/Print.js +163 -163
- package/commands/Validations.js +92 -92
- package/commands/build/build.js +40 -40
- package/commands/buildProduction/buildProduction.js +577 -579
- package/commands/bundle/bundle.js +236 -234
- package/commands/createComponent/VisualComponentTemplate.js +55 -55
- package/commands/createComponent/createComponent.js +128 -128
- package/commands/deleteComponent/deleteComponent.js +81 -81
- package/commands/doctor/doctor.js +516 -440
- package/commands/getComponent/getComponent.js +695 -701
- package/commands/init/init.js +473 -467
- package/commands/listComponents/listComponents.js +172 -172
- package/commands/startServer/startServer.js +240 -261
- package/commands/startServer/watchServer.js +66 -66
- package/commands/types/types.js +583 -580
- package/commands/utils/LocalCliDelegation.js +53 -53
- package/commands/utils/PackageManager.js +148 -148
- package/commands/utils/PathHelper.js +75 -75
- package/commands/utils/VersionChecker.js +169 -169
- package/commands/utils/bundling/BundleGenerator.js +2525 -2525
- package/commands/utils/bundling/DependencyAnalyzer.js +925 -925
- package/commands/utils/loadConfig.js +31 -31
- package/commands/utils/sliceScripts.js +48 -23
- package/commands/utils/updateManager.js +471 -471
- package/package.json +71 -74
- package/post.js +0 -60
|
@@ -1,440 +1,516 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { createServer } from 'net';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import Table from 'cli-table3';
|
|
6
|
-
import Print from '../Print.js';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
*
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createServer } from 'net';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import Table from 'cli-table3';
|
|
6
|
+
import Print from '../Print.js';
|
|
7
|
+
import { getProjectRoot, getSrcPath, getApiPath, getConfigPath, getPath, getComponentsJsPath } from '../utils/PathHelper.js';
|
|
8
|
+
import { resolvePackageManager, installCommand } from '../utils/PackageManager.js';
|
|
9
|
+
import updateManager from '../utils/updateManager.js';
|
|
10
|
+
import { parseComponentsRegistry, extractStaticPropsFromSource } from '../types/types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks the Node.js version
|
|
14
|
+
*/
|
|
15
|
+
async function checkNodeVersion() {
|
|
16
|
+
const currentVersion = process.version;
|
|
17
|
+
const majorVersion = parseInt(currentVersion.slice(1).split('.')[0]);
|
|
18
|
+
const required = 20;
|
|
19
|
+
|
|
20
|
+
if (majorVersion >= required) {
|
|
21
|
+
return {
|
|
22
|
+
pass: true,
|
|
23
|
+
message: `Node.js version: ${currentVersion} (required: >= v${required})`
|
|
24
|
+
};
|
|
25
|
+
} else {
|
|
26
|
+
return {
|
|
27
|
+
pass: false,
|
|
28
|
+
message: `Node.js version: ${currentVersion} (required: >= v${required})`,
|
|
29
|
+
suggestion: `Update Node.js to v${required} or higher`
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Checks the directory structure
|
|
36
|
+
*/
|
|
37
|
+
async function checkDirectoryStructure() {
|
|
38
|
+
const srcPath = getSrcPath(import.meta.url);
|
|
39
|
+
const apiPath = getApiPath(import.meta.url);
|
|
40
|
+
|
|
41
|
+
const srcExists = await fs.pathExists(srcPath);
|
|
42
|
+
const apiExists = await fs.pathExists(apiPath);
|
|
43
|
+
|
|
44
|
+
if (srcExists && apiExists) {
|
|
45
|
+
return {
|
|
46
|
+
pass: true,
|
|
47
|
+
message: 'Project structure (src/ and api/) exists'
|
|
48
|
+
};
|
|
49
|
+
} else {
|
|
50
|
+
const missing = [];
|
|
51
|
+
if (!srcExists) missing.push('src/');
|
|
52
|
+
if (!apiExists) missing.push('api/');
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
pass: false,
|
|
56
|
+
message: `Missing directories: ${missing.join(', ')}`,
|
|
57
|
+
suggestion: 'Run "slice init" to initialize your project'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Checks sliceConfig.json
|
|
64
|
+
*/
|
|
65
|
+
async function checkConfig() {
|
|
66
|
+
const configPath = getConfigPath(import.meta.url);
|
|
67
|
+
|
|
68
|
+
if (!await fs.pathExists(configPath)) {
|
|
69
|
+
return {
|
|
70
|
+
pass: false,
|
|
71
|
+
message: 'sliceConfig.json not found',
|
|
72
|
+
suggestion: 'Run "slice init" to create configuration'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const config = await fs.readJson(configPath);
|
|
78
|
+
|
|
79
|
+
if (!config.paths || !config.paths.components) {
|
|
80
|
+
return {
|
|
81
|
+
pass: false,
|
|
82
|
+
message: 'sliceConfig.json is invalid (missing paths.components)',
|
|
83
|
+
suggestion: 'Check your configuration file'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
pass: true,
|
|
89
|
+
message: 'sliceConfig.json is valid'
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
pass: false,
|
|
94
|
+
message: `sliceConfig.json is invalid JSON: ${error.message}`,
|
|
95
|
+
suggestion: 'Fix JSON syntax errors in sliceConfig.json'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Checks port availability
|
|
102
|
+
*/
|
|
103
|
+
async function checkPort() {
|
|
104
|
+
const configPath = getConfigPath(import.meta.url);
|
|
105
|
+
let port = 3000;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
if (await fs.pathExists(configPath)) {
|
|
109
|
+
const config = await fs.readJson(configPath);
|
|
110
|
+
port = config.server?.port || 3000;
|
|
111
|
+
}
|
|
112
|
+
} catch { /* config missing or unreadable — use default port */ }
|
|
113
|
+
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
const server = createServer();
|
|
116
|
+
|
|
117
|
+
server.once('error', (err) => {
|
|
118
|
+
if (err.code === 'EADDRINUSE') {
|
|
119
|
+
resolve({
|
|
120
|
+
warn: true,
|
|
121
|
+
message: `Port ${port} is already in use`,
|
|
122
|
+
suggestion: `Stop the process using port ${port} or use: slice dev -p <other-port>`
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
resolve({
|
|
126
|
+
pass: true,
|
|
127
|
+
message: `Port ${port} is available`
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
server.once('listening', () => {
|
|
133
|
+
server.close();
|
|
134
|
+
resolve({
|
|
135
|
+
pass: true,
|
|
136
|
+
message: `Port ${port} is available`
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
server.listen(port);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Checks the package manager setup: mixed lockfiles, packageManager field
|
|
146
|
+
* consistency, and a project-local CLI install (required for local delegation).
|
|
147
|
+
* Exported for tests.
|
|
148
|
+
*/
|
|
149
|
+
export async function checkPackageManagerSetup(projectRoot = getProjectRoot(import.meta.url)) {
|
|
150
|
+
const issues = [];
|
|
151
|
+
const suggestions = [];
|
|
152
|
+
|
|
153
|
+
const LOCKFILE_PM = {
|
|
154
|
+
'package-lock.json': 'npm',
|
|
155
|
+
'pnpm-lock.yaml': 'pnpm'
|
|
156
|
+
};
|
|
157
|
+
const lockfiles = [];
|
|
158
|
+
for (const lockfile of Object.keys(LOCKFILE_PM)) {
|
|
159
|
+
if (await fs.pathExists(path.join(projectRoot, lockfile))) {
|
|
160
|
+
lockfiles.push(lockfile);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let pkg = null;
|
|
165
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
166
|
+
if (await fs.pathExists(pkgPath)) {
|
|
167
|
+
try { pkg = await fs.readJson(pkgPath); } catch { /* reported by Dependencies check */ }
|
|
168
|
+
}
|
|
169
|
+
const pmField = typeof pkg?.packageManager === 'string' ? pkg.packageManager.split('@')[0] : null;
|
|
170
|
+
const pm = resolvePackageManager(projectRoot).name;
|
|
171
|
+
|
|
172
|
+
if (lockfiles.length > 1) {
|
|
173
|
+
issues.push(`Mixed lockfiles found: ${lockfiles.join(', ')}`);
|
|
174
|
+
const keep = pmField || pm;
|
|
175
|
+
const keepLockfile = Object.entries(LOCKFILE_PM).find(([, name]) => name === keep)?.[0];
|
|
176
|
+
suggestions.push(`Keep only ${keepLockfile ?? 'the lockfile of your package manager'} and delete the rest`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (pkg && !pmField) {
|
|
180
|
+
issues.push('No "packageManager" field in package.json');
|
|
181
|
+
suggestions.push(`Pin it (e.g. "packageManager": "${pm}@<version>") so every tool resolves the same package manager`);
|
|
182
|
+
} else if (pmField && lockfiles.length === 1 && LOCKFILE_PM[lockfiles[0]] !== pmField) {
|
|
183
|
+
issues.push(`packageManager is "${pmField}" but the lockfile is ${lockfiles[0]}`);
|
|
184
|
+
suggestions.push(`Reinstall with ${pmField} (or update the packageManager field) so they agree`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const localCliPath = path.join(projectRoot, 'node_modules', 'slicejs-cli', 'package.json');
|
|
188
|
+
if (pkg && !(await fs.pathExists(localCliPath))) {
|
|
189
|
+
issues.push('slicejs-cli is not installed locally');
|
|
190
|
+
suggestions.push(`Run "${installCommand(pm, 'slicejs-cli', { dev: true })}" so the launcher delegates to a version pinned per project`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (issues.length === 0) {
|
|
194
|
+
return {
|
|
195
|
+
pass: true,
|
|
196
|
+
message: `Package manager setup is consistent (${pmField || pm})`
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
warn: true,
|
|
201
|
+
message: issues.join(' | '),
|
|
202
|
+
suggestion: suggestions.join(' | ')
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Checks dependencies in package.json
|
|
208
|
+
*/
|
|
209
|
+
async function checkDependencies() {
|
|
210
|
+
const projectRoot = getProjectRoot(import.meta.url);
|
|
211
|
+
const packagePath = getPath(import.meta.url, 'package.json');
|
|
212
|
+
|
|
213
|
+
if (!await fs.pathExists(packagePath)) {
|
|
214
|
+
return {
|
|
215
|
+
warn: true,
|
|
216
|
+
message: 'package.json not found',
|
|
217
|
+
suggestion: 'Run "npm init" to create package.json'
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const pkg = await fs.readJson(packagePath);
|
|
223
|
+
const hasFrameworkDep = pkg.dependencies?.['slicejs-web-framework'] || pkg.devDependencies?.['slicejs-web-framework'];
|
|
224
|
+
const frameworkNodePath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'package.json');
|
|
225
|
+
const hasFrameworkNode = await fs.pathExists(frameworkNodePath);
|
|
226
|
+
const hasFramework = !!(hasFrameworkDep || hasFrameworkNode);
|
|
227
|
+
|
|
228
|
+
if (hasFramework) {
|
|
229
|
+
return {
|
|
230
|
+
pass: true,
|
|
231
|
+
message: 'Required framework dependency is installed'
|
|
232
|
+
};
|
|
233
|
+
} else {
|
|
234
|
+
const missing = ['slicejs-web-framework'];
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
warn: true,
|
|
238
|
+
message: `Missing dependencies: ${missing.join(', ')}`,
|
|
239
|
+
suggestion: missing.includes('slicejs-web-framework')
|
|
240
|
+
? `Run "${installCommand(resolvePackageManager(getProjectRoot(import.meta.url)).name, 'slicejs-web-framework@latest')}" in your project`
|
|
241
|
+
: `Run "${installCommand(resolvePackageManager(getProjectRoot(import.meta.url)).name, 'slicejs-cli@latest', { dev: true })}" in your project`,
|
|
242
|
+
missing
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return {
|
|
247
|
+
pass: false,
|
|
248
|
+
message: `package.json is invalid: ${error.message}`,
|
|
249
|
+
suggestion: 'Fix JSON syntax errors in package.json'
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Checks component integrity
|
|
256
|
+
*/
|
|
257
|
+
async function checkComponents() {
|
|
258
|
+
const configPath = getConfigPath(import.meta.url);
|
|
259
|
+
const projectRoot = getProjectRoot(import.meta.url);
|
|
260
|
+
|
|
261
|
+
if (!await fs.pathExists(configPath)) {
|
|
262
|
+
return {
|
|
263
|
+
warn: true,
|
|
264
|
+
message: 'Cannot check components (no config)',
|
|
265
|
+
suggestion: 'Run "slice init" first'
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const config = await fs.readJson(configPath);
|
|
271
|
+
const componentPaths = config.paths?.components || {};
|
|
272
|
+
|
|
273
|
+
let totalComponents = 0;
|
|
274
|
+
let componentIssues = 0;
|
|
275
|
+
|
|
276
|
+
for (const [category, { path: compPath }] of Object.entries(componentPaths)) {
|
|
277
|
+
const fullPath = getSrcPath(import.meta.url, compPath);
|
|
278
|
+
|
|
279
|
+
if (await fs.pathExists(fullPath)) {
|
|
280
|
+
const items = await fs.readdir(fullPath);
|
|
281
|
+
|
|
282
|
+
for (const item of items) {
|
|
283
|
+
const itemPath = path.join(fullPath, item);
|
|
284
|
+
const stat = await fs.stat(itemPath);
|
|
285
|
+
|
|
286
|
+
if (stat.isDirectory()) {
|
|
287
|
+
totalComponents++;
|
|
288
|
+
|
|
289
|
+
// Check JS files
|
|
290
|
+
const jsFile = path.join(itemPath, `${item}.js`);
|
|
291
|
+
if (!await fs.pathExists(jsFile)) {
|
|
292
|
+
componentIssues++;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (componentIssues === 0) {
|
|
300
|
+
return {
|
|
301
|
+
pass: true,
|
|
302
|
+
message: `${totalComponents} components checked, all OK`
|
|
303
|
+
};
|
|
304
|
+
} else {
|
|
305
|
+
return {
|
|
306
|
+
warn: true,
|
|
307
|
+
message: `${componentIssues} component(s) have missing files`,
|
|
308
|
+
suggestion: 'Check your component directories'
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
} catch (error) {
|
|
312
|
+
return {
|
|
313
|
+
warn: true,
|
|
314
|
+
message: `Cannot check components: ${error.message}`,
|
|
315
|
+
suggestion: 'Verify your project structure'
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Lints component static props for common issues.
|
|
322
|
+
* Uses the same Babel parsing as slice types generate.
|
|
323
|
+
*/
|
|
324
|
+
async function checkComponentProps() {
|
|
325
|
+
const issues = [];
|
|
326
|
+
const registryPath = getComponentsJsPath(import.meta.url);
|
|
327
|
+
const configPath = getConfigPath(import.meta.url);
|
|
328
|
+
|
|
329
|
+
if (!await fs.pathExists(registryPath)) {
|
|
330
|
+
return { pass: true, message: 'No components to check' };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const content = await fs.readFile(registryPath, 'utf-8');
|
|
335
|
+
const registryMap = parseComponentsRegistry(content, registryPath);
|
|
336
|
+
const entries = Object.entries(registryMap);
|
|
337
|
+
|
|
338
|
+
if (entries.length === 0) {
|
|
339
|
+
return { pass: true, message: 'No components registered' };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Build a category-to-path map from sliceConfig.json
|
|
343
|
+
const categoryPathCache = new Map();
|
|
344
|
+
if (await fs.pathExists(configPath)) {
|
|
345
|
+
try {
|
|
346
|
+
const config = await fs.readJson(configPath);
|
|
347
|
+
if (config.paths?.components) {
|
|
348
|
+
for (const [cat, catMeta] of Object.entries(config.paths.components)) {
|
|
349
|
+
categoryPathCache.set(cat, catMeta.path?.replace(/^[/\\]+/, '') || '');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} catch {}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const VALID_TYPES = new Set(['string', 'number', 'boolean', 'object', 'array', 'function', 'any']);
|
|
356
|
+
const checked = [];
|
|
357
|
+
|
|
358
|
+
for (const [compName, meta] of entries) {
|
|
359
|
+
const category = typeof meta === 'string' ? meta : (meta.category || '');
|
|
360
|
+
const relPath = categoryPathCache.get(category);
|
|
361
|
+
if (!relPath) continue;
|
|
362
|
+
const jsPath = getSrcPath(import.meta.url, relPath, compName, `${compName}.js`);
|
|
363
|
+
|
|
364
|
+
if (!await fs.pathExists(jsPath)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const source = await fs.readFile(jsPath, 'utf-8');
|
|
369
|
+
const props = extractStaticPropsFromSource(source, jsPath);
|
|
370
|
+
if (!props) continue;
|
|
371
|
+
|
|
372
|
+
checked.push(compName);
|
|
373
|
+
|
|
374
|
+
for (const [propName, propMeta] of Object.entries(props)) {
|
|
375
|
+
if (propMeta.type === 'any') {
|
|
376
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" uses type "any" — specify a concrete type` });
|
|
377
|
+
}
|
|
378
|
+
if (propMeta.required && propMeta.type !== 'boolean') {
|
|
379
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" is required but has no default value` });
|
|
380
|
+
}
|
|
381
|
+
if (propMeta.schema && propMeta.type !== 'object') {
|
|
382
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" has "schema" but type is "${propMeta.type}" (should be "object")` });
|
|
383
|
+
}
|
|
384
|
+
if (propMeta.items && propMeta.type !== 'array') {
|
|
385
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" has "items" but type is "${propMeta.type}" (should be "array")` });
|
|
386
|
+
}
|
|
387
|
+
if (!VALID_TYPES.has(propMeta.type)) {
|
|
388
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" has unknown type "${propMeta.type}"` });
|
|
389
|
+
}
|
|
390
|
+
if (Array.isArray(propMeta.allowedValues) && propMeta.allowedValues.length > 0) {
|
|
391
|
+
const typeMismatch = propMeta.allowedValues.some(v => typeof v !== propMeta.type);
|
|
392
|
+
if (typeMismatch && propMeta.type !== 'any') {
|
|
393
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" allowedValues type mismatch (expected ${propMeta.type})` });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (issues.length === 0) {
|
|
400
|
+
return { pass: true, message: `${checked.length} components checked, props look good` };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
warn: true,
|
|
405
|
+
message: `${issues.length} prop issue(s) across ${new Set(issues.map(i => i.component)).size} component(s)`,
|
|
406
|
+
suggestion: issues.map(i => ` ${i.component}.${i.prop}: ${i.message}`).join('\n'),
|
|
407
|
+
issues
|
|
408
|
+
};
|
|
409
|
+
} catch (error) {
|
|
410
|
+
return { warn: true, message: `Cannot check props: ${error.message}` };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Main diagnostic command
|
|
416
|
+
*/
|
|
417
|
+
export {
|
|
418
|
+
checkNodeVersion,
|
|
419
|
+
checkDirectoryStructure,
|
|
420
|
+
checkConfig,
|
|
421
|
+
checkPort,
|
|
422
|
+
checkDependencies,
|
|
423
|
+
checkComponents,
|
|
424
|
+
checkComponentProps
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
export default async function runDiagnostics() {
|
|
428
|
+
Print.newLine();
|
|
429
|
+
Print.title('🔍 Running Slice.js Diagnostics...');
|
|
430
|
+
Print.newLine();
|
|
431
|
+
|
|
432
|
+
const checks = [
|
|
433
|
+
{ name: 'Node.js Version', fn: checkNodeVersion },
|
|
434
|
+
{ name: 'Project Structure', fn: checkDirectoryStructure },
|
|
435
|
+
{ name: 'Configuration', fn: checkConfig },
|
|
436
|
+
{ name: 'Port Availability', fn: checkPort },
|
|
437
|
+
{ name: 'Package Manager', fn: checkPackageManagerSetup },
|
|
438
|
+
{ name: 'Dependencies', fn: checkDependencies },
|
|
439
|
+
{ name: 'Components', fn: checkComponents },
|
|
440
|
+
{ name: 'Component Props', fn: checkComponentProps }
|
|
441
|
+
];
|
|
442
|
+
|
|
443
|
+
const results = [];
|
|
444
|
+
|
|
445
|
+
for (const check of checks) {
|
|
446
|
+
const result = await check.fn();
|
|
447
|
+
results.push({ ...result, name: check.name });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Crear tabla de resultados
|
|
451
|
+
const table = new Table({
|
|
452
|
+
head: [chalk.cyan.bold('Check'), chalk.cyan.bold('Status'), chalk.cyan.bold('Details')],
|
|
453
|
+
colWidths: [25, 10, 55],
|
|
454
|
+
style: {
|
|
455
|
+
head: [],
|
|
456
|
+
border: ['gray']
|
|
457
|
+
},
|
|
458
|
+
wordWrap: true
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
results.forEach(result => {
|
|
462
|
+
let status;
|
|
463
|
+
if (result.pass) {
|
|
464
|
+
status = chalk.green('✅ PASS');
|
|
465
|
+
} else if (result.warn) {
|
|
466
|
+
status = chalk.yellow('⚠️ WARN');
|
|
467
|
+
} else {
|
|
468
|
+
status = chalk.red('❌ FAIL');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const details = result.suggestion
|
|
472
|
+
? `${result.message}\n${chalk.gray('→ ' + result.suggestion)}`
|
|
473
|
+
: result.message;
|
|
474
|
+
|
|
475
|
+
table.push([result.name, status, details]);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
console.log(table.toString());
|
|
479
|
+
|
|
480
|
+
// Resumen
|
|
481
|
+
const issues = results.filter(r => !r.pass && !r.warn).length;
|
|
482
|
+
const warnings = results.filter(r => r.warn).length;
|
|
483
|
+
const passed = results.filter(r => r.pass).length;
|
|
484
|
+
|
|
485
|
+
Print.newLine();
|
|
486
|
+
Print.separator();
|
|
487
|
+
|
|
488
|
+
const depsResult = results.find(r => r.name === 'Dependencies');
|
|
489
|
+
if (depsResult && depsResult.warn && Array.isArray(depsResult.missing) && depsResult.missing.length > 0) {
|
|
490
|
+
const projectRoot = getProjectRoot(import.meta.url);
|
|
491
|
+
const pm = resolvePackageManager(projectRoot).name;
|
|
492
|
+
for (const pkg of depsResult.missing) {
|
|
493
|
+
Print.info(`Missing: ${pkg}. Install with: ${installCommand(pm, `${pkg}@latest`)}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (issues === 0 && warnings === 0) {
|
|
498
|
+
Print.success('All checks passed! 🎉');
|
|
499
|
+
Print.info('Your Slice.js project is correctly configured');
|
|
500
|
+
} else {
|
|
501
|
+
console.log(chalk.bold('📊 Summary:'));
|
|
502
|
+
console.log(chalk.green(` ✅ Passed: ${passed}`));
|
|
503
|
+
if (warnings > 0) console.log(chalk.yellow(` ⚠️ Warnings: ${warnings}`));
|
|
504
|
+
if (issues > 0) console.log(chalk.red(` ❌ Issues: ${issues}`));
|
|
505
|
+
|
|
506
|
+
Print.newLine();
|
|
507
|
+
|
|
508
|
+
if (issues > 0) {
|
|
509
|
+
Print.warning('Fix the issues above to ensure proper functionality');
|
|
510
|
+
} else {
|
|
511
|
+
Print.info('Warnings are non-critical but should be addressed');
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
Print.separator();
|
|
516
|
+
}
|