pulse-js-framework 1.7.11 → 1.7.13
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/README.md +80 -8
- package/cli/docs.js +712 -0
- package/cli/doctor.js +702 -0
- package/cli/index.js +338 -65
- package/cli/scaffold.js +1037 -0
- package/cli/test.js +455 -0
- package/package.json +19 -2
- package/runtime/a11y.js +824 -1
- package/runtime/context.js +374 -0
- package/runtime/graphql.js +1356 -0
- package/runtime/index.js +6 -0
- package/runtime/logger.js +2 -1
- package/runtime/websocket.js +874 -0
- package/types/context.d.ts +171 -0
- package/types/graphql.d.ts +490 -0
- package/types/index.d.ts +15 -0
- package/types/websocket.d.ts +347 -0
package/cli/doctor.js
ADDED
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse CLI - Doctor Command
|
|
3
|
+
* Project diagnostics and health checks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
7
|
+
import { join, relative, resolve } from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import { log } from './logger.js';
|
|
10
|
+
import { findPulseFiles, parseArgs, formatBytes } from './utils/file-utils.js';
|
|
11
|
+
import { createTimer, formatDuration } from './utils/cli-ui.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Diagnostic check result
|
|
15
|
+
* @typedef {Object} CheckResult
|
|
16
|
+
* @property {string} name - Check name
|
|
17
|
+
* @property {'pass'|'warn'|'fail'|'info'} status - Check status
|
|
18
|
+
* @property {string} message - Result message
|
|
19
|
+
* @property {string} [suggestion] - Fix suggestion
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Run all diagnostic checks
|
|
24
|
+
* @param {Object} options - Check options
|
|
25
|
+
* @returns {Promise<CheckResult[]>}
|
|
26
|
+
*/
|
|
27
|
+
export async function runDiagnostics(options = {}) {
|
|
28
|
+
const { verbose = false, fix = false } = options;
|
|
29
|
+
const results = [];
|
|
30
|
+
|
|
31
|
+
// Environment checks
|
|
32
|
+
results.push(await checkNodeVersion());
|
|
33
|
+
results.push(await checkNpmVersion());
|
|
34
|
+
|
|
35
|
+
// Project structure checks
|
|
36
|
+
results.push(await checkPackageJson());
|
|
37
|
+
results.push(await checkDependencies());
|
|
38
|
+
results.push(await checkPulseFramework());
|
|
39
|
+
results.push(await checkViteConfig());
|
|
40
|
+
results.push(await checkProjectStructure());
|
|
41
|
+
|
|
42
|
+
// Code quality checks
|
|
43
|
+
results.push(await checkPulseFiles());
|
|
44
|
+
results.push(await checkGitStatus());
|
|
45
|
+
|
|
46
|
+
// Performance checks
|
|
47
|
+
if (verbose) {
|
|
48
|
+
results.push(await checkNodeModulesSize());
|
|
49
|
+
results.push(await checkBuildArtifacts());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check Node.js version
|
|
57
|
+
*/
|
|
58
|
+
async function checkNodeVersion() {
|
|
59
|
+
const name = 'Node.js Version';
|
|
60
|
+
try {
|
|
61
|
+
const version = process.version;
|
|
62
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
63
|
+
|
|
64
|
+
if (major < 18) {
|
|
65
|
+
return {
|
|
66
|
+
name,
|
|
67
|
+
status: 'fail',
|
|
68
|
+
message: `Node.js ${version} detected`,
|
|
69
|
+
suggestion: 'Pulse requires Node.js 18+. Please upgrade: https://nodejs.org'
|
|
70
|
+
};
|
|
71
|
+
} else if (major < 20) {
|
|
72
|
+
return {
|
|
73
|
+
name,
|
|
74
|
+
status: 'warn',
|
|
75
|
+
message: `Node.js ${version} (recommended: 20+)`,
|
|
76
|
+
suggestion: 'Consider upgrading to Node.js 20+ for better test coverage support'
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
name,
|
|
82
|
+
status: 'pass',
|
|
83
|
+
message: `Node.js ${version}`
|
|
84
|
+
};
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return {
|
|
87
|
+
name,
|
|
88
|
+
status: 'fail',
|
|
89
|
+
message: 'Could not detect Node.js version',
|
|
90
|
+
suggestion: 'Ensure Node.js is properly installed'
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check npm version
|
|
97
|
+
*/
|
|
98
|
+
async function checkNpmVersion() {
|
|
99
|
+
const name = 'npm Version';
|
|
100
|
+
try {
|
|
101
|
+
const version = execSync('npm --version', { encoding: 'utf-8' }).trim();
|
|
102
|
+
const major = parseInt(version.split('.')[0], 10);
|
|
103
|
+
|
|
104
|
+
if (major < 8) {
|
|
105
|
+
return {
|
|
106
|
+
name,
|
|
107
|
+
status: 'warn',
|
|
108
|
+
message: `npm ${version} (recommended: 8+)`,
|
|
109
|
+
suggestion: 'Consider upgrading npm: npm install -g npm@latest'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
name,
|
|
115
|
+
status: 'pass',
|
|
116
|
+
message: `npm ${version}`
|
|
117
|
+
};
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return {
|
|
120
|
+
name,
|
|
121
|
+
status: 'warn',
|
|
122
|
+
message: 'Could not detect npm version'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check package.json exists and is valid
|
|
129
|
+
*/
|
|
130
|
+
async function checkPackageJson() {
|
|
131
|
+
const name = 'package.json';
|
|
132
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
133
|
+
|
|
134
|
+
if (!existsSync(pkgPath)) {
|
|
135
|
+
return {
|
|
136
|
+
name,
|
|
137
|
+
status: 'fail',
|
|
138
|
+
message: 'package.json not found',
|
|
139
|
+
suggestion: 'Run "npm init" or "pulse create <name>" to create a project'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
145
|
+
|
|
146
|
+
const issues = [];
|
|
147
|
+
|
|
148
|
+
// Check for type: module
|
|
149
|
+
if (pkg.type !== 'module') {
|
|
150
|
+
issues.push('Missing "type": "module"');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check for required scripts
|
|
154
|
+
if (!pkg.scripts?.dev) {
|
|
155
|
+
issues.push('Missing "dev" script');
|
|
156
|
+
}
|
|
157
|
+
if (!pkg.scripts?.build) {
|
|
158
|
+
issues.push('Missing "build" script');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for name
|
|
162
|
+
if (!pkg.name) {
|
|
163
|
+
issues.push('Missing "name" field');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (issues.length > 0) {
|
|
167
|
+
return {
|
|
168
|
+
name,
|
|
169
|
+
status: 'warn',
|
|
170
|
+
message: `Issues found: ${issues.join(', ')}`,
|
|
171
|
+
suggestion: 'Update package.json to fix these issues'
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
name,
|
|
177
|
+
status: 'pass',
|
|
178
|
+
message: `${pkg.name}@${pkg.version || '0.0.0'}`
|
|
179
|
+
};
|
|
180
|
+
} catch (e) {
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
status: 'fail',
|
|
184
|
+
message: 'Invalid package.json: ' + e.message,
|
|
185
|
+
suggestion: 'Fix the JSON syntax error in package.json'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check dependencies are installed
|
|
192
|
+
*/
|
|
193
|
+
async function checkDependencies() {
|
|
194
|
+
const name = 'Dependencies';
|
|
195
|
+
const nodeModulesPath = join(process.cwd(), 'node_modules');
|
|
196
|
+
|
|
197
|
+
if (!existsSync(nodeModulesPath)) {
|
|
198
|
+
return {
|
|
199
|
+
name,
|
|
200
|
+
status: 'fail',
|
|
201
|
+
message: 'node_modules not found',
|
|
202
|
+
suggestion: 'Run "npm install" to install dependencies'
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check for common issues
|
|
207
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
208
|
+
if (!existsSync(pkgPath)) {
|
|
209
|
+
return {
|
|
210
|
+
name,
|
|
211
|
+
status: 'warn',
|
|
212
|
+
message: 'Cannot verify dependencies without package.json'
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
218
|
+
const allDeps = {
|
|
219
|
+
...pkg.dependencies,
|
|
220
|
+
...pkg.devDependencies
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const missing = [];
|
|
224
|
+
for (const dep of Object.keys(allDeps)) {
|
|
225
|
+
if (!existsSync(join(nodeModulesPath, dep))) {
|
|
226
|
+
missing.push(dep);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (missing.length > 0) {
|
|
231
|
+
return {
|
|
232
|
+
name,
|
|
233
|
+
status: 'warn',
|
|
234
|
+
message: `Missing: ${missing.slice(0, 3).join(', ')}${missing.length > 3 ? ` (+${missing.length - 3} more)` : ''}`,
|
|
235
|
+
suggestion: 'Run "npm install" to install missing dependencies'
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const depCount = Object.keys(allDeps).length;
|
|
240
|
+
return {
|
|
241
|
+
name,
|
|
242
|
+
status: 'pass',
|
|
243
|
+
message: `${depCount} dependencies installed`
|
|
244
|
+
};
|
|
245
|
+
} catch (e) {
|
|
246
|
+
return {
|
|
247
|
+
name,
|
|
248
|
+
status: 'warn',
|
|
249
|
+
message: 'Could not verify dependencies'
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check Pulse framework is installed
|
|
256
|
+
*/
|
|
257
|
+
async function checkPulseFramework() {
|
|
258
|
+
const name = 'Pulse Framework';
|
|
259
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
260
|
+
|
|
261
|
+
if (!existsSync(pkgPath)) {
|
|
262
|
+
return {
|
|
263
|
+
name,
|
|
264
|
+
status: 'info',
|
|
265
|
+
message: 'Not a Node.js project'
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
271
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
272
|
+
|
|
273
|
+
if (allDeps['pulse-js-framework']) {
|
|
274
|
+
const version = allDeps['pulse-js-framework'];
|
|
275
|
+
return {
|
|
276
|
+
name,
|
|
277
|
+
status: 'pass',
|
|
278
|
+
message: `pulse-js-framework ${version}`
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Check if this IS the framework
|
|
283
|
+
if (pkg.name === 'pulse-js-framework') {
|
|
284
|
+
return {
|
|
285
|
+
name,
|
|
286
|
+
status: 'pass',
|
|
287
|
+
message: `Framework source (${pkg.version})`
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
name,
|
|
293
|
+
status: 'warn',
|
|
294
|
+
message: 'Pulse framework not found in dependencies',
|
|
295
|
+
suggestion: 'Run "npm install pulse-js-framework"'
|
|
296
|
+
};
|
|
297
|
+
} catch (e) {
|
|
298
|
+
return {
|
|
299
|
+
name,
|
|
300
|
+
status: 'warn',
|
|
301
|
+
message: 'Could not check framework'
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check Vite configuration
|
|
308
|
+
*/
|
|
309
|
+
async function checkViteConfig() {
|
|
310
|
+
const name = 'Vite Config';
|
|
311
|
+
const configFiles = [
|
|
312
|
+
'vite.config.js',
|
|
313
|
+
'vite.config.ts',
|
|
314
|
+
'vite.config.mjs'
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
let configPath = null;
|
|
318
|
+
for (const file of configFiles) {
|
|
319
|
+
const path = join(process.cwd(), file);
|
|
320
|
+
if (existsSync(path)) {
|
|
321
|
+
configPath = path;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!configPath) {
|
|
327
|
+
return {
|
|
328
|
+
name,
|
|
329
|
+
status: 'info',
|
|
330
|
+
message: 'No Vite config found',
|
|
331
|
+
suggestion: 'Create vite.config.js for Vite integration'
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
337
|
+
|
|
338
|
+
// Check for Pulse plugin
|
|
339
|
+
if (!content.includes('pulse')) {
|
|
340
|
+
return {
|
|
341
|
+
name,
|
|
342
|
+
status: 'warn',
|
|
343
|
+
message: 'Vite config missing Pulse plugin',
|
|
344
|
+
suggestion: 'Add: import pulse from "pulse-js-framework/vite"'
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
name,
|
|
350
|
+
status: 'pass',
|
|
351
|
+
message: relative(process.cwd(), configPath)
|
|
352
|
+
};
|
|
353
|
+
} catch (e) {
|
|
354
|
+
return {
|
|
355
|
+
name,
|
|
356
|
+
status: 'warn',
|
|
357
|
+
message: 'Could not read Vite config'
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Check project structure
|
|
364
|
+
*/
|
|
365
|
+
async function checkProjectStructure() {
|
|
366
|
+
const name = 'Project Structure';
|
|
367
|
+
const issues = [];
|
|
368
|
+
|
|
369
|
+
// Check for src directory
|
|
370
|
+
if (!existsSync(join(process.cwd(), 'src'))) {
|
|
371
|
+
issues.push('No src/ directory');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Check for index.html
|
|
375
|
+
if (!existsSync(join(process.cwd(), 'index.html'))) {
|
|
376
|
+
issues.push('No index.html');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Check for main entry file
|
|
380
|
+
const mainFiles = ['src/main.js', 'src/main.ts', 'src/index.js', 'src/index.ts'];
|
|
381
|
+
const hasMain = mainFiles.some(f => existsSync(join(process.cwd(), f)));
|
|
382
|
+
if (!hasMain) {
|
|
383
|
+
issues.push('No main entry file in src/');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (issues.length > 0) {
|
|
387
|
+
return {
|
|
388
|
+
name,
|
|
389
|
+
status: 'warn',
|
|
390
|
+
message: issues.join(', '),
|
|
391
|
+
suggestion: 'Run "pulse create <name>" to create a proper project structure'
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
name,
|
|
397
|
+
status: 'pass',
|
|
398
|
+
message: 'Standard structure detected'
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Check .pulse files for issues
|
|
404
|
+
*/
|
|
405
|
+
async function checkPulseFiles() {
|
|
406
|
+
const name = 'Pulse Files';
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
const files = findPulseFiles(['.']);
|
|
410
|
+
|
|
411
|
+
if (files.length === 0) {
|
|
412
|
+
return {
|
|
413
|
+
name,
|
|
414
|
+
status: 'info',
|
|
415
|
+
message: 'No .pulse files found'
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Quick syntax check
|
|
420
|
+
const { parse } = await import('../compiler/index.js');
|
|
421
|
+
let errors = 0;
|
|
422
|
+
const errorFiles = [];
|
|
423
|
+
|
|
424
|
+
for (const file of files) {
|
|
425
|
+
try {
|
|
426
|
+
const source = readFileSync(file, 'utf-8');
|
|
427
|
+
parse(source);
|
|
428
|
+
} catch (e) {
|
|
429
|
+
errors++;
|
|
430
|
+
errorFiles.push(relative(process.cwd(), file));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (errors > 0) {
|
|
435
|
+
return {
|
|
436
|
+
name,
|
|
437
|
+
status: 'fail',
|
|
438
|
+
message: `${errors} file(s) with syntax errors`,
|
|
439
|
+
suggestion: `Run "pulse lint" to see details. Files: ${errorFiles.slice(0, 2).join(', ')}${errorFiles.length > 2 ? '...' : ''}`
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
name,
|
|
445
|
+
status: 'pass',
|
|
446
|
+
message: `${files.length} file(s) OK`
|
|
447
|
+
};
|
|
448
|
+
} catch (e) {
|
|
449
|
+
return {
|
|
450
|
+
name,
|
|
451
|
+
status: 'warn',
|
|
452
|
+
message: 'Could not check .pulse files: ' + e.message
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Check git status
|
|
459
|
+
*/
|
|
460
|
+
async function checkGitStatus() {
|
|
461
|
+
const name = 'Git Repository';
|
|
462
|
+
|
|
463
|
+
if (!existsSync(join(process.cwd(), '.git'))) {
|
|
464
|
+
return {
|
|
465
|
+
name,
|
|
466
|
+
status: 'info',
|
|
467
|
+
message: 'Not a git repository',
|
|
468
|
+
suggestion: 'Run "git init" to initialize version control'
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
// Check for uncommitted changes
|
|
474
|
+
const status = execSync('git status --porcelain', { encoding: 'utf-8' });
|
|
475
|
+
const changes = status.trim().split('\n').filter(l => l.trim()).length;
|
|
476
|
+
|
|
477
|
+
if (changes > 0) {
|
|
478
|
+
return {
|
|
479
|
+
name,
|
|
480
|
+
status: 'info',
|
|
481
|
+
message: `${changes} uncommitted change(s)`
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Get current branch
|
|
486
|
+
const branch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
name,
|
|
490
|
+
status: 'pass',
|
|
491
|
+
message: `On branch: ${branch}`
|
|
492
|
+
};
|
|
493
|
+
} catch (e) {
|
|
494
|
+
return {
|
|
495
|
+
name,
|
|
496
|
+
status: 'warn',
|
|
497
|
+
message: 'Could not check git status'
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Check node_modules size
|
|
504
|
+
*/
|
|
505
|
+
async function checkNodeModulesSize() {
|
|
506
|
+
const name = 'node_modules Size';
|
|
507
|
+
const nodeModulesPath = join(process.cwd(), 'node_modules');
|
|
508
|
+
|
|
509
|
+
if (!existsSync(nodeModulesPath)) {
|
|
510
|
+
return {
|
|
511
|
+
name,
|
|
512
|
+
status: 'info',
|
|
513
|
+
message: 'node_modules not found'
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
const size = getDirSize(nodeModulesPath);
|
|
519
|
+
|
|
520
|
+
if (size > 500 * 1024 * 1024) { // 500MB
|
|
521
|
+
return {
|
|
522
|
+
name,
|
|
523
|
+
status: 'warn',
|
|
524
|
+
message: `${formatBytes(size)} (large)`,
|
|
525
|
+
suggestion: 'Consider cleaning with: npm prune && npm dedupe'
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
name,
|
|
531
|
+
status: 'pass',
|
|
532
|
+
message: formatBytes(size)
|
|
533
|
+
};
|
|
534
|
+
} catch (e) {
|
|
535
|
+
return {
|
|
536
|
+
name,
|
|
537
|
+
status: 'info',
|
|
538
|
+
message: 'Could not calculate size'
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Check build artifacts
|
|
545
|
+
*/
|
|
546
|
+
async function checkBuildArtifacts() {
|
|
547
|
+
const name = 'Build Artifacts';
|
|
548
|
+
const distPath = join(process.cwd(), 'dist');
|
|
549
|
+
|
|
550
|
+
if (!existsSync(distPath)) {
|
|
551
|
+
return {
|
|
552
|
+
name,
|
|
553
|
+
status: 'info',
|
|
554
|
+
message: 'No dist/ directory',
|
|
555
|
+
suggestion: 'Run "pulse build" to create a production build'
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
const size = getDirSize(distPath);
|
|
561
|
+
const files = countFiles(distPath);
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
name,
|
|
565
|
+
status: 'pass',
|
|
566
|
+
message: `${files} file(s), ${formatBytes(size)}`
|
|
567
|
+
};
|
|
568
|
+
} catch (e) {
|
|
569
|
+
return {
|
|
570
|
+
name,
|
|
571
|
+
status: 'info',
|
|
572
|
+
message: 'Could not check build'
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Get directory size recursively
|
|
579
|
+
*/
|
|
580
|
+
function getDirSize(dir) {
|
|
581
|
+
let size = 0;
|
|
582
|
+
|
|
583
|
+
try {
|
|
584
|
+
const entries = readdirSync(dir);
|
|
585
|
+
for (const entry of entries) {
|
|
586
|
+
const path = join(dir, entry);
|
|
587
|
+
try {
|
|
588
|
+
const stat = statSync(path);
|
|
589
|
+
if (stat.isDirectory()) {
|
|
590
|
+
size += getDirSize(path);
|
|
591
|
+
} else {
|
|
592
|
+
size += stat.size;
|
|
593
|
+
}
|
|
594
|
+
} catch (e) {
|
|
595
|
+
// Skip inaccessible files
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
} catch (e) {
|
|
599
|
+
// Skip inaccessible directories
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return size;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Count files in directory
|
|
607
|
+
*/
|
|
608
|
+
function countFiles(dir) {
|
|
609
|
+
let count = 0;
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const entries = readdirSync(dir);
|
|
613
|
+
for (const entry of entries) {
|
|
614
|
+
const path = join(dir, entry);
|
|
615
|
+
try {
|
|
616
|
+
const stat = statSync(path);
|
|
617
|
+
if (stat.isDirectory()) {
|
|
618
|
+
count += countFiles(path);
|
|
619
|
+
} else {
|
|
620
|
+
count++;
|
|
621
|
+
}
|
|
622
|
+
} catch (e) {
|
|
623
|
+
// Skip
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
} catch (e) {
|
|
627
|
+
// Skip
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return count;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Format check result for display
|
|
635
|
+
*/
|
|
636
|
+
function formatCheckResult(result) {
|
|
637
|
+
const icons = {
|
|
638
|
+
pass: '\x1b[32m✓\x1b[0m', // Green checkmark
|
|
639
|
+
warn: '\x1b[33m!\x1b[0m', // Yellow exclamation
|
|
640
|
+
fail: '\x1b[31m✗\x1b[0m', // Red X
|
|
641
|
+
info: '\x1b[34mi\x1b[0m' // Blue i
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
const icon = icons[result.status] || '?';
|
|
645
|
+
const name = result.name.padEnd(20);
|
|
646
|
+
const message = result.message;
|
|
647
|
+
|
|
648
|
+
return ` ${icon} ${name} ${message}`;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Main doctor command handler
|
|
653
|
+
*/
|
|
654
|
+
export async function runDoctor(args) {
|
|
655
|
+
const { options } = parseArgs(args);
|
|
656
|
+
const verbose = options.verbose || options.v || false;
|
|
657
|
+
const json = options.json || false;
|
|
658
|
+
|
|
659
|
+
const timer = createTimer();
|
|
660
|
+
|
|
661
|
+
log.info('Pulse Doctor - Project Diagnostics\n');
|
|
662
|
+
log.info('Running checks...\n');
|
|
663
|
+
|
|
664
|
+
const results = await runDiagnostics({ verbose });
|
|
665
|
+
|
|
666
|
+
if (json) {
|
|
667
|
+
console.log(JSON.stringify(results, null, 2));
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Display results
|
|
672
|
+
for (const result of results) {
|
|
673
|
+
log.info(formatCheckResult(result));
|
|
674
|
+
if (result.suggestion && (result.status === 'warn' || result.status === 'fail')) {
|
|
675
|
+
log.info(` \x1b[90m${result.suggestion}\x1b[0m`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Summary
|
|
680
|
+
const elapsed = formatDuration(timer.elapsed());
|
|
681
|
+
const passed = results.filter(r => r.status === 'pass').length;
|
|
682
|
+
const warnings = results.filter(r => r.status === 'warn').length;
|
|
683
|
+
const failures = results.filter(r => r.status === 'fail').length;
|
|
684
|
+
|
|
685
|
+
log.info('\n' + '─'.repeat(50));
|
|
686
|
+
|
|
687
|
+
if (failures > 0) {
|
|
688
|
+
log.error(`${failures} issue(s) require attention`);
|
|
689
|
+
}
|
|
690
|
+
if (warnings > 0) {
|
|
691
|
+
log.warn(`${warnings} warning(s)`);
|
|
692
|
+
}
|
|
693
|
+
if (failures === 0 && warnings === 0) {
|
|
694
|
+
log.success(`All ${passed} checks passed`);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
log.info(`\nCompleted in ${elapsed}`);
|
|
698
|
+
|
|
699
|
+
if (failures > 0) {
|
|
700
|
+
process.exit(1);
|
|
701
|
+
}
|
|
702
|
+
}
|