budexp 0.1.0
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 -0
- package/README.md +516 -0
- package/bin/budexp.js +162 -0
- package/package.json +63 -0
- package/src/commands/build.js +548 -0
- package/src/commands/check.js +130 -0
- package/src/commands/clean.js +92 -0
- package/src/commands/dev.js +186 -0
- package/src/utils/cleaner.js +377 -0
- package/src/utils/eas.js +198 -0
- package/src/utils/expo-doctor.js +1097 -0
- package/src/utils/logger.js +25 -0
|
@@ -0,0 +1,1097 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const logger = require('./logger');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Run expo-doctor and generate HTML report
|
|
8
|
+
*/
|
|
9
|
+
async function runExpoDoctor(options = {}) {
|
|
10
|
+
const ora = require('ora');
|
|
11
|
+
|
|
12
|
+
const spinner = ora({
|
|
13
|
+
text: 'Running budexp check...',
|
|
14
|
+
color: 'cyan',
|
|
15
|
+
spinner: 'dots',
|
|
16
|
+
}).start();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Run expo-doctor and capture output
|
|
20
|
+
const output = execFileSync('npx', ['expo-doctor'], {
|
|
21
|
+
encoding: 'utf8',
|
|
22
|
+
stdio: 'pipe',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
spinner.text = 'Generating HTML report...';
|
|
26
|
+
|
|
27
|
+
// Generate HTML report
|
|
28
|
+
const htmlReport = generateHTMLReport(output);
|
|
29
|
+
|
|
30
|
+
// Save report to file
|
|
31
|
+
const reportPath = path.join(process.cwd(), 'expo-doctor-report.html');
|
|
32
|
+
fs.writeFileSync(reportPath, htmlReport);
|
|
33
|
+
|
|
34
|
+
spinner.succeed(`HTML report generated: ${reportPath}`);
|
|
35
|
+
|
|
36
|
+
// Prompt user to open report
|
|
37
|
+
await promptToOpenReport(reportPath, options);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
output,
|
|
41
|
+
reportPath,
|
|
42
|
+
hasIssues:
|
|
43
|
+
output.includes('✖') || output.includes('⚠') || output.toLowerCase().includes('error'),
|
|
44
|
+
};
|
|
45
|
+
} catch (e) {
|
|
46
|
+
spinner.text = 'Issues detected, generating report...';
|
|
47
|
+
|
|
48
|
+
const errorOutput = e.stdout || e.message;
|
|
49
|
+
const htmlReport = generateHTMLReport(errorOutput);
|
|
50
|
+
const reportPath = path.join(process.cwd(), 'expo-doctor-report.html');
|
|
51
|
+
fs.writeFileSync(reportPath, htmlReport);
|
|
52
|
+
|
|
53
|
+
spinner.warn('expo-doctor found issues. Check the report for details.');
|
|
54
|
+
|
|
55
|
+
// Prompt user to open report
|
|
56
|
+
await promptToOpenReport(reportPath, options);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
output: errorOutput,
|
|
60
|
+
reportPath,
|
|
61
|
+
hasIssues: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Prompt user to open the report
|
|
68
|
+
*/
|
|
69
|
+
async function promptToOpenReport(reportPath, options = {}) {
|
|
70
|
+
const readline = require('readline');
|
|
71
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
72
|
+
|
|
73
|
+
if (options.openReport === false) {
|
|
74
|
+
console.log('');
|
|
75
|
+
logger.info('Report saved. Open it later at:');
|
|
76
|
+
logger.info(reportPath);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!isInteractive) {
|
|
81
|
+
console.log('');
|
|
82
|
+
logger.info('Non-interactive shell detected. Skipping report open prompt.');
|
|
83
|
+
logger.info('Report saved at:');
|
|
84
|
+
logger.info(reportPath);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const rl = readline.createInterface({
|
|
89
|
+
input: process.stdin,
|
|
90
|
+
output: process.stdout,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
console.log('');
|
|
95
|
+
rl.question('Would you like to open the report? (y/n): ', (answer) => {
|
|
96
|
+
rl.close();
|
|
97
|
+
|
|
98
|
+
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
99
|
+
openReport(reportPath);
|
|
100
|
+
} else {
|
|
101
|
+
console.log('');
|
|
102
|
+
logger.info('Report saved. You can open it later at:');
|
|
103
|
+
logger.info(reportPath);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Open report in default browser
|
|
113
|
+
*/
|
|
114
|
+
function openReport(reportPath) {
|
|
115
|
+
try {
|
|
116
|
+
const platform = process.platform;
|
|
117
|
+
let command;
|
|
118
|
+
|
|
119
|
+
// Determine the command based on the platform
|
|
120
|
+
if (platform === 'darwin') {
|
|
121
|
+
// macOS
|
|
122
|
+
command = ['open', [reportPath]];
|
|
123
|
+
} else if (platform === 'win32') {
|
|
124
|
+
// Windows
|
|
125
|
+
command = ['cmd', ['/c', 'start', '', reportPath]];
|
|
126
|
+
} else {
|
|
127
|
+
// Linux and others
|
|
128
|
+
command = ['xdg-open', [reportPath]];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
execFileSync(command[0], command[1], { stdio: 'ignore' });
|
|
132
|
+
console.log('');
|
|
133
|
+
logger.success('Opening report in your default browser...');
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.log('');
|
|
136
|
+
logger.error('Failed to open report automatically');
|
|
137
|
+
logger.info('Please open it manually at:');
|
|
138
|
+
logger.info(reportPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Generate enhanced HTML report from expo-doctor output
|
|
144
|
+
*/
|
|
145
|
+
function generateHTMLReport(output) {
|
|
146
|
+
const timestamp = new Date().toLocaleString();
|
|
147
|
+
const summary = getIssuesSummary(output);
|
|
148
|
+
|
|
149
|
+
// Escape HTML
|
|
150
|
+
const escapedOutput = output.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
151
|
+
|
|
152
|
+
// Split into lines for better rendering
|
|
153
|
+
const lines = escapedOutput.split('\n');
|
|
154
|
+
const renderedLines = lines.map((line, index) => {
|
|
155
|
+
// Remove ANSI codes
|
|
156
|
+
let cleanLine = line.replace(/\x1b\[[0-9;]*m/g, '');
|
|
157
|
+
|
|
158
|
+
// Determine line type and styling
|
|
159
|
+
let className = 'line-normal';
|
|
160
|
+
let icon = '';
|
|
161
|
+
|
|
162
|
+
if (cleanLine.includes('✖') || cleanLine.toLowerCase().includes('error')) {
|
|
163
|
+
className = 'line-error';
|
|
164
|
+
icon = '❌';
|
|
165
|
+
} else if (cleanLine.includes('⚠') || cleanLine.toLowerCase().includes('warning')) {
|
|
166
|
+
className = 'line-warning';
|
|
167
|
+
icon = '⚠️';
|
|
168
|
+
} else if (cleanLine.includes('✔') || cleanLine.toLowerCase().includes('success')) {
|
|
169
|
+
className = 'line-success';
|
|
170
|
+
icon = '✅';
|
|
171
|
+
} else if (cleanLine.includes('ℹ') || cleanLine.toLowerCase().includes('info')) {
|
|
172
|
+
className = 'line-info';
|
|
173
|
+
icon = 'ℹ️';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
number: index + 1,
|
|
178
|
+
text: cleanLine,
|
|
179
|
+
className,
|
|
180
|
+
icon,
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Generate grouped issues HTML (including suggestions)
|
|
185
|
+
const groupedIssuesHTML = generateGroupedIssues(summary, output);
|
|
186
|
+
|
|
187
|
+
return `<!DOCTYPE html>
|
|
188
|
+
<html lang="en">
|
|
189
|
+
<head>
|
|
190
|
+
<meta charset="UTF-8">
|
|
191
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
192
|
+
<title>Budexp Report</title>
|
|
193
|
+
<style>
|
|
194
|
+
* {
|
|
195
|
+
margin: 0;
|
|
196
|
+
padding: 0;
|
|
197
|
+
box-sizing: border-box;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
body {
|
|
201
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
|
202
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
203
|
+
min-height: 100vh;
|
|
204
|
+
padding: 40px 20px;
|
|
205
|
+
color: #2d3748;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.container {
|
|
209
|
+
max-width: 1400px;
|
|
210
|
+
margin: 0 auto;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.header {
|
|
214
|
+
background: white;
|
|
215
|
+
border-radius: 16px;
|
|
216
|
+
padding: 32px;
|
|
217
|
+
margin-bottom: 24px;
|
|
218
|
+
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.header-top {
|
|
222
|
+
display: flex;
|
|
223
|
+
justify-content: space-between;
|
|
224
|
+
align-items: center;
|
|
225
|
+
margin-bottom: 24px;
|
|
226
|
+
flex-wrap: wrap;
|
|
227
|
+
gap: 16px;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.title {
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: 12px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.title h1 {
|
|
237
|
+
font-size: 32px;
|
|
238
|
+
font-weight: 700;
|
|
239
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
240
|
+
-webkit-background-clip: text;
|
|
241
|
+
-webkit-text-fill-color: transparent;
|
|
242
|
+
background-clip: text;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.title-icon {
|
|
246
|
+
font-size: 40px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.timestamp {
|
|
250
|
+
color: #718096;
|
|
251
|
+
font-size: 14px;
|
|
252
|
+
display: flex;
|
|
253
|
+
align-items: center;
|
|
254
|
+
gap: 6px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.status-badge {
|
|
258
|
+
display: inline-flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
gap: 8px;
|
|
261
|
+
padding: 12px 24px;
|
|
262
|
+
border-radius: 12px;
|
|
263
|
+
font-weight: 600;
|
|
264
|
+
font-size: 16px;
|
|
265
|
+
animation: slideIn 0.5s ease;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.status-badge.success {
|
|
269
|
+
background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
|
|
270
|
+
color: white;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.status-badge.error {
|
|
274
|
+
background: linear-gradient(135deg, #f56565 0%, #e53e3e 100%);
|
|
275
|
+
color: white;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.stats-grid {
|
|
279
|
+
display: grid;
|
|
280
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
281
|
+
gap: 16px;
|
|
282
|
+
margin-top: 24px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.stat-card {
|
|
286
|
+
background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%);
|
|
287
|
+
padding: 20px;
|
|
288
|
+
border-radius: 12px;
|
|
289
|
+
border: 2px solid transparent;
|
|
290
|
+
transition: all 0.3s ease;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.stat-card:hover {
|
|
294
|
+
transform: translateY(-2px);
|
|
295
|
+
border-color: #667eea;
|
|
296
|
+
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.2);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.stat-card.error {
|
|
300
|
+
border-color: #fc8181;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.stat-card.warning {
|
|
304
|
+
border-color: #f6ad55;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.stat-card.success {
|
|
308
|
+
border-color: #68d391;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.stat-label {
|
|
312
|
+
font-size: 13px;
|
|
313
|
+
color: #718096;
|
|
314
|
+
text-transform: uppercase;
|
|
315
|
+
font-weight: 600;
|
|
316
|
+
letter-spacing: 0.5px;
|
|
317
|
+
margin-bottom: 8px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.stat-value {
|
|
321
|
+
font-size: 36px;
|
|
322
|
+
font-weight: 700;
|
|
323
|
+
display: flex;
|
|
324
|
+
align-items: center;
|
|
325
|
+
gap: 8px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.stat-value.error { color: #f56565; }
|
|
329
|
+
.stat-value.warning { color: #ed8936; }
|
|
330
|
+
.stat-value.success { color: #48bb78; }
|
|
331
|
+
|
|
332
|
+
.section {
|
|
333
|
+
background: white;
|
|
334
|
+
border-radius: 16px;
|
|
335
|
+
padding: 32px;
|
|
336
|
+
margin-bottom: 24px;
|
|
337
|
+
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
|
338
|
+
animation: fadeIn 0.5s ease;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.section-header {
|
|
342
|
+
display: flex;
|
|
343
|
+
justify-content: space-between;
|
|
344
|
+
align-items: center;
|
|
345
|
+
margin-bottom: 24px;
|
|
346
|
+
cursor: pointer;
|
|
347
|
+
user-select: none;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.section-title {
|
|
351
|
+
font-size: 24px;
|
|
352
|
+
font-weight: 700;
|
|
353
|
+
color: #2d3748;
|
|
354
|
+
display: flex;
|
|
355
|
+
align-items: center;
|
|
356
|
+
gap: 12px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.toggle-icon {
|
|
360
|
+
font-size: 20px;
|
|
361
|
+
transition: transform 0.3s ease;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.toggle-icon.collapsed {
|
|
365
|
+
transform: rotate(-90deg);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.section-content {
|
|
369
|
+
max-height: 2000px;
|
|
370
|
+
overflow: hidden;
|
|
371
|
+
transition: max-height 0.3s ease;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.section-content.collapsed {
|
|
375
|
+
max-height: 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.issues-group {
|
|
379
|
+
margin-bottom: 24px;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.issues-group:last-child {
|
|
383
|
+
margin-bottom: 0;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.issue-item {
|
|
387
|
+
background: #f7fafc;
|
|
388
|
+
border-left: 4px solid #e2e8f0;
|
|
389
|
+
padding: 16px;
|
|
390
|
+
margin-bottom: 12px;
|
|
391
|
+
border-radius: 8px;
|
|
392
|
+
transition: all 0.2s ease;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.issue-item:hover {
|
|
396
|
+
background: #edf2f7;
|
|
397
|
+
transform: translateX(4px);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.issue-item.error {
|
|
401
|
+
border-left-color: #f56565;
|
|
402
|
+
background: #fff5f5;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.issue-item.error:hover {
|
|
406
|
+
background: #fed7d7;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.issue-item.warning {
|
|
410
|
+
border-left-color: #ed8936;
|
|
411
|
+
background: #fffaf0;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.issue-item.warning:hover {
|
|
415
|
+
background: #feebc8;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.issue-header {
|
|
419
|
+
display: flex;
|
|
420
|
+
align-items: flex-start;
|
|
421
|
+
gap: 12px;
|
|
422
|
+
font-weight: 600;
|
|
423
|
+
margin-bottom: 4px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.issue-icon {
|
|
427
|
+
font-size: 20px;
|
|
428
|
+
flex-shrink: 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.issue-text {
|
|
432
|
+
flex: 1;
|
|
433
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
434
|
+
font-size: 14px;
|
|
435
|
+
line-height: 1.6;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.terminal-output {
|
|
439
|
+
background: #1e1e1e;
|
|
440
|
+
border-radius: 12px;
|
|
441
|
+
overflow: hidden;
|
|
442
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
443
|
+
font-size: 13px;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.terminal-header {
|
|
447
|
+
background: #323233;
|
|
448
|
+
padding: 12px 16px;
|
|
449
|
+
display: flex;
|
|
450
|
+
justify-content: space-between;
|
|
451
|
+
align-items: center;
|
|
452
|
+
border-bottom: 1px solid #1e1e1e;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.terminal-title {
|
|
456
|
+
color: #d4d4d4;
|
|
457
|
+
font-weight: 600;
|
|
458
|
+
display: flex;
|
|
459
|
+
align-items: center;
|
|
460
|
+
gap: 8px;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.terminal-buttons {
|
|
464
|
+
display: flex;
|
|
465
|
+
gap: 8px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.terminal-button {
|
|
469
|
+
background: #4a5568;
|
|
470
|
+
color: white;
|
|
471
|
+
border: none;
|
|
472
|
+
padding: 6px 12px;
|
|
473
|
+
border-radius: 6px;
|
|
474
|
+
cursor: pointer;
|
|
475
|
+
font-size: 12px;
|
|
476
|
+
display: flex;
|
|
477
|
+
align-items: center;
|
|
478
|
+
gap: 6px;
|
|
479
|
+
transition: all 0.2s ease;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.terminal-button:hover {
|
|
483
|
+
background: #667eea;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.terminal-button:active {
|
|
487
|
+
transform: scale(0.95);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.terminal-body {
|
|
491
|
+
padding: 20px;
|
|
492
|
+
max-height: 600px;
|
|
493
|
+
overflow-y: auto;
|
|
494
|
+
background: #1e1e1e;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.terminal-line {
|
|
498
|
+
display: flex;
|
|
499
|
+
gap: 12px;
|
|
500
|
+
padding: 4px 0;
|
|
501
|
+
line-height: 1.6;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.line-number {
|
|
505
|
+
color: #6e7681;
|
|
506
|
+
user-select: none;
|
|
507
|
+
min-width: 40px;
|
|
508
|
+
text-align: right;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.line-content {
|
|
512
|
+
color: #d4d4d4;
|
|
513
|
+
flex: 1;
|
|
514
|
+
white-space: pre-wrap;
|
|
515
|
+
word-break: break-word;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.line-error .line-content {
|
|
519
|
+
color: #f87171;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.line-warning .line-content {
|
|
523
|
+
color: #fbbf24;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.line-success .line-content {
|
|
527
|
+
color: #4ade80;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.line-info .line-content {
|
|
531
|
+
color: #60a5fa;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.search-box {
|
|
535
|
+
margin-bottom: 16px;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.search-input {
|
|
539
|
+
width: 100%;
|
|
540
|
+
padding: 12px 16px;
|
|
541
|
+
border: 2px solid #e2e8f0;
|
|
542
|
+
border-radius: 8px;
|
|
543
|
+
font-size: 14px;
|
|
544
|
+
transition: all 0.2s ease;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.search-input:focus {
|
|
548
|
+
outline: none;
|
|
549
|
+
border-color: #667eea;
|
|
550
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.footer {
|
|
554
|
+
text-align: center;
|
|
555
|
+
color: white;
|
|
556
|
+
margin-top: 40px;
|
|
557
|
+
font-size: 14px;
|
|
558
|
+
opacity: 0.9;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.footer a {
|
|
562
|
+
color: white;
|
|
563
|
+
text-decoration: underline;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
@keyframes slideIn {
|
|
567
|
+
from {
|
|
568
|
+
opacity: 0;
|
|
569
|
+
transform: translateY(-10px);
|
|
570
|
+
}
|
|
571
|
+
to {
|
|
572
|
+
opacity: 1;
|
|
573
|
+
transform: translateY(0);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
@keyframes fadeIn {
|
|
578
|
+
from {
|
|
579
|
+
opacity: 0;
|
|
580
|
+
}
|
|
581
|
+
to {
|
|
582
|
+
opacity: 1;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.highlight {
|
|
587
|
+
background: #fef3c7;
|
|
588
|
+
padding: 2px 4px;
|
|
589
|
+
border-radius: 3px;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.suggestions-intro {
|
|
593
|
+
background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 100%);
|
|
594
|
+
padding: 16px 20px;
|
|
595
|
+
border-radius: 8px;
|
|
596
|
+
margin-bottom: 20px;
|
|
597
|
+
border-left: 4px solid #667eea;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.suggestions-intro p {
|
|
601
|
+
margin: 0;
|
|
602
|
+
color: #4c51bf;
|
|
603
|
+
font-weight: 500;
|
|
604
|
+
font-size: 15px;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.suggestions-list {
|
|
608
|
+
display: flex;
|
|
609
|
+
flex-direction: column;
|
|
610
|
+
gap: 16px;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.suggestion-card {
|
|
614
|
+
display: flex;
|
|
615
|
+
gap: 16px;
|
|
616
|
+
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
|
|
617
|
+
border: 2px solid #86efac;
|
|
618
|
+
border-radius: 12px;
|
|
619
|
+
padding: 20px;
|
|
620
|
+
transition: all 0.3s ease;
|
|
621
|
+
position: relative;
|
|
622
|
+
overflow: hidden;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.suggestion-card::before {
|
|
626
|
+
content: '';
|
|
627
|
+
position: absolute;
|
|
628
|
+
top: 0;
|
|
629
|
+
left: 0;
|
|
630
|
+
width: 4px;
|
|
631
|
+
height: 100%;
|
|
632
|
+
background: linear-gradient(180deg, #10b981 0%, #059669 100%);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.suggestion-card:hover {
|
|
636
|
+
transform: translateX(4px);
|
|
637
|
+
box-shadow: 0 8px 24px rgba(16, 185, 129, 0.2);
|
|
638
|
+
border-color: #10b981;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.suggestion-number {
|
|
642
|
+
flex-shrink: 0;
|
|
643
|
+
width: 32px;
|
|
644
|
+
height: 32px;
|
|
645
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
646
|
+
color: white;
|
|
647
|
+
border-radius: 50%;
|
|
648
|
+
display: flex;
|
|
649
|
+
align-items: center;
|
|
650
|
+
justify-content: center;
|
|
651
|
+
font-weight: 700;
|
|
652
|
+
font-size: 14px;
|
|
653
|
+
box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.suggestion-content {
|
|
657
|
+
flex: 1;
|
|
658
|
+
display: flex;
|
|
659
|
+
flex-direction: column;
|
|
660
|
+
gap: 12px;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.suggestion-text {
|
|
664
|
+
color: #065f46;
|
|
665
|
+
font-size: 15px;
|
|
666
|
+
line-height: 1.6;
|
|
667
|
+
font-weight: 500;
|
|
668
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.suggestion-link {
|
|
672
|
+
color: #059669;
|
|
673
|
+
text-decoration: none;
|
|
674
|
+
font-weight: 600;
|
|
675
|
+
font-size: 14px;
|
|
676
|
+
display: inline-flex;
|
|
677
|
+
align-items: center;
|
|
678
|
+
gap: 4px;
|
|
679
|
+
transition: all 0.2s ease;
|
|
680
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.suggestion-link:hover {
|
|
684
|
+
color: #047857;
|
|
685
|
+
gap: 8px;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
@media (max-width: 768px) {
|
|
689
|
+
body {
|
|
690
|
+
padding: 20px 12px;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.header {
|
|
694
|
+
padding: 20px;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.title h1 {
|
|
698
|
+
font-size: 24px;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.section {
|
|
702
|
+
padding: 20px;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.stats-grid {
|
|
706
|
+
grid-template-columns: 1fr;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
</style>
|
|
710
|
+
</head>
|
|
711
|
+
<body>
|
|
712
|
+
<div class="container">
|
|
713
|
+
<!-- Header -->
|
|
714
|
+
<div class="header">
|
|
715
|
+
<div class="header-top">
|
|
716
|
+
<div class="title">
|
|
717
|
+
<h1>Budexp Report</h1>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="timestamp">
|
|
720
|
+
${timestamp}
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
|
|
724
|
+
<div class="stats-grid">
|
|
725
|
+
<div class="stat-card ${summary.errors && summary.errors.length > 0 ? 'error' : ''}">
|
|
726
|
+
<div class="stat-label">Errors</div>
|
|
727
|
+
<div class="stat-value error">
|
|
728
|
+
<span>❌</span>
|
|
729
|
+
<span>${summary.errors ? summary.errors.length : 0}</span>
|
|
730
|
+
</div>
|
|
731
|
+
</div>
|
|
732
|
+
|
|
733
|
+
<div class="stat-card ${summary.warnings && summary.warnings.length > 0 ? 'warning' : ''}">
|
|
734
|
+
<div class="stat-label">Warnings</div>
|
|
735
|
+
<div class="stat-value warning">
|
|
736
|
+
<span>⚠️</span>
|
|
737
|
+
<span>${summary.warnings ? summary.warnings.length : 0}</span>
|
|
738
|
+
</div>
|
|
739
|
+
</div>
|
|
740
|
+
|
|
741
|
+
<div class="stat-card success">
|
|
742
|
+
<div class="stat-label">Total Checks</div>
|
|
743
|
+
<div class="stat-value success">
|
|
744
|
+
<span>✅</span>
|
|
745
|
+
<span>${lines.length}</span>
|
|
746
|
+
</div>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
</div>
|
|
750
|
+
|
|
751
|
+
<!-- Issues Section -->
|
|
752
|
+
${groupedIssuesHTML}
|
|
753
|
+
|
|
754
|
+
<!-- Full Output Section -->
|
|
755
|
+
<div class="section">
|
|
756
|
+
<div class="section-header" onclick="toggleSection('output')">
|
|
757
|
+
<div class="section-title">
|
|
758
|
+
<span>Full Terminal Output</span>
|
|
759
|
+
</div>
|
|
760
|
+
<span class="toggle-icon" id="output-toggle">▼</span>
|
|
761
|
+
</div>
|
|
762
|
+
|
|
763
|
+
<div class="section-content" id="output-content">
|
|
764
|
+
<div class="search-box">
|
|
765
|
+
<input
|
|
766
|
+
type="text"
|
|
767
|
+
class="search-input"
|
|
768
|
+
id="search-input"
|
|
769
|
+
placeholder="🔍 Search in output..."
|
|
770
|
+
onkeyup="searchInOutput()"
|
|
771
|
+
/>
|
|
772
|
+
</div>
|
|
773
|
+
|
|
774
|
+
<div class="terminal-output">
|
|
775
|
+
<div class="terminal-header">
|
|
776
|
+
<div class="terminal-title">
|
|
777
|
+
<span>💻</span>
|
|
778
|
+
<span>expo-doctor output</span>
|
|
779
|
+
</div>
|
|
780
|
+
<div class="terminal-buttons">
|
|
781
|
+
<button class="terminal-button" onclick="copyOutput()">
|
|
782
|
+
📋 Copy
|
|
783
|
+
</button>
|
|
784
|
+
</div>
|
|
785
|
+
</div>
|
|
786
|
+
<div class="terminal-body" id="terminal-body">
|
|
787
|
+
${renderedLines
|
|
788
|
+
.map(
|
|
789
|
+
(line) => `
|
|
790
|
+
<div class="terminal-line ${line.className}" data-line="${line.text.toLowerCase()}">
|
|
791
|
+
<span class="line-number">${line.number}</span>
|
|
792
|
+
<span class="line-content">${line.icon ? line.icon + ' ' : ''}${line.text || ' '}</span>
|
|
793
|
+
</div>
|
|
794
|
+
`
|
|
795
|
+
)
|
|
796
|
+
.join('')}
|
|
797
|
+
</div>
|
|
798
|
+
</div>
|
|
799
|
+
</div>
|
|
800
|
+
</div>
|
|
801
|
+
|
|
802
|
+
<div class="footer">
|
|
803
|
+
Generated by <strong>budexp</strong> CLI tool
|
|
804
|
+
</div>
|
|
805
|
+
</div>
|
|
806
|
+
|
|
807
|
+
<script>
|
|
808
|
+
function toggleSection(sectionId) {
|
|
809
|
+
const content = document.getElementById(sectionId + '-content');
|
|
810
|
+
const toggle = document.getElementById(sectionId + '-toggle');
|
|
811
|
+
|
|
812
|
+
content.classList.toggle('collapsed');
|
|
813
|
+
toggle.classList.toggle('collapsed');
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function copyOutput() {
|
|
817
|
+
const lines = document.querySelectorAll('.terminal-line .line-content');
|
|
818
|
+
const text = Array.from(lines).map(line => line.textContent).join('\\n');
|
|
819
|
+
|
|
820
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
821
|
+
const button = event.target.closest('.terminal-button');
|
|
822
|
+
const originalText = button.innerHTML;
|
|
823
|
+
button.innerHTML = '✅ Copied!';
|
|
824
|
+
setTimeout(() => {
|
|
825
|
+
button.innerHTML = originalText;
|
|
826
|
+
}, 2000);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function searchInOutput() {
|
|
831
|
+
const searchTerm = document.getElementById('search-input').value.toLowerCase();
|
|
832
|
+
const lines = document.querySelectorAll('.terminal-line');
|
|
833
|
+
|
|
834
|
+
lines.forEach(line => {
|
|
835
|
+
const text = line.getAttribute('data-line');
|
|
836
|
+
const content = line.querySelector('.line-content');
|
|
837
|
+
|
|
838
|
+
if (searchTerm === '') {
|
|
839
|
+
line.style.display = 'flex';
|
|
840
|
+
content.innerHTML = content.textContent;
|
|
841
|
+
} else if (text.includes(searchTerm)) {
|
|
842
|
+
line.style.display = 'flex';
|
|
843
|
+
|
|
844
|
+
// Highlight matching text
|
|
845
|
+
const originalText = content.textContent;
|
|
846
|
+
const regex = new RegExp(\`(\${searchTerm})\`, 'gi');
|
|
847
|
+
content.innerHTML = originalText.replace(regex, '<span class="highlight">$1</span>');
|
|
848
|
+
} else {
|
|
849
|
+
line.style.display = 'none';
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
</script>
|
|
854
|
+
</body>
|
|
855
|
+
</html>`;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Extract suggestions/advice from output
|
|
860
|
+
*/
|
|
861
|
+
function extractSuggestions(output) {
|
|
862
|
+
const suggestions = [];
|
|
863
|
+
const lines = output.split('\n');
|
|
864
|
+
|
|
865
|
+
for (let i = 0; i < lines.length; i++) {
|
|
866
|
+
const line = lines[i].trim();
|
|
867
|
+
const lowerLine = line.toLowerCase();
|
|
868
|
+
|
|
869
|
+
// Look for "Advice:" sections and collect following lines
|
|
870
|
+
if (lowerLine.startsWith('advice:')) {
|
|
871
|
+
// Get the next non-empty lines as suggestions
|
|
872
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
873
|
+
const suggestionLine = lines[j].trim();
|
|
874
|
+
if (!suggestionLine) continue;
|
|
875
|
+
|
|
876
|
+
// Stop if we hit another section or check
|
|
877
|
+
if (
|
|
878
|
+
suggestionLine.toLowerCase().startsWith('advice:') ||
|
|
879
|
+
suggestionLine.includes('✖') ||
|
|
880
|
+
suggestionLine.includes('✔') ||
|
|
881
|
+
suggestionLine.toLowerCase().includes('check ')
|
|
882
|
+
) {
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
suggestions.push({
|
|
887
|
+
text: suggestionLine,
|
|
888
|
+
type: 'advice',
|
|
889
|
+
});
|
|
890
|
+
break; // Only get the first line after "Advice:"
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Also capture lines that start with common suggestion patterns
|
|
895
|
+
if (
|
|
896
|
+
lowerLine.startsWith('use ') ||
|
|
897
|
+
lowerLine.startsWith('run ') ||
|
|
898
|
+
lowerLine.startsWith('resolve ') ||
|
|
899
|
+
lowerLine.startsWith('update ') ||
|
|
900
|
+
lowerLine.startsWith('install ')
|
|
901
|
+
) {
|
|
902
|
+
// Avoid duplicates
|
|
903
|
+
if (!suggestions.some((s) => s.text === line)) {
|
|
904
|
+
suggestions.push({
|
|
905
|
+
text: line,
|
|
906
|
+
type: 'action',
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
return suggestions;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Generate grouped issues HTML section
|
|
917
|
+
*/
|
|
918
|
+
function generateGroupedIssues(summary, output) {
|
|
919
|
+
if (!summary.hasIssues) {
|
|
920
|
+
return `
|
|
921
|
+
<div class="section">
|
|
922
|
+
<div class="section-title">
|
|
923
|
+
<span>Great News!</span>
|
|
924
|
+
</div>
|
|
925
|
+
<div class="section-content">
|
|
926
|
+
<p style="color: #48bb78; font-size: 18px; text-align: center; padding: 40px;">
|
|
927
|
+
All checks passed! Your Expo project is healthy and ready to go!
|
|
928
|
+
</p>
|
|
929
|
+
</div>
|
|
930
|
+
</div>
|
|
931
|
+
`;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
let html = '';
|
|
935
|
+
|
|
936
|
+
// Suggestions/Recommendations section
|
|
937
|
+
const suggestions = extractSuggestions(output);
|
|
938
|
+
if (suggestions.length > 0) {
|
|
939
|
+
html += `
|
|
940
|
+
<div class="section">
|
|
941
|
+
<div class="section-header" onclick="toggleSection('suggestions')">
|
|
942
|
+
<div class="section-title">
|
|
943
|
+
<span>Recommendations (${suggestions.length})</span>
|
|
944
|
+
</div>
|
|
945
|
+
<span class="toggle-icon" id="suggestions-toggle">▼</span>
|
|
946
|
+
</div>
|
|
947
|
+
<div class="section-content" id="suggestions-content">
|
|
948
|
+
<div class="suggestions-intro">
|
|
949
|
+
<p>Here are actionable steps to resolve the issues found in your project:</p>
|
|
950
|
+
</div>
|
|
951
|
+
<div class="suggestions-list">
|
|
952
|
+
${suggestions
|
|
953
|
+
.map((suggestion, index) => {
|
|
954
|
+
// Check if it contains a URL
|
|
955
|
+
const urlMatch = suggestion.text.match(/(https?:\/\/[^\s]+)/);
|
|
956
|
+
let displayText = suggestion.text;
|
|
957
|
+
let linkHtml = '';
|
|
958
|
+
|
|
959
|
+
if (urlMatch) {
|
|
960
|
+
const url = urlMatch[0];
|
|
961
|
+
displayText = suggestion.text.replace(url, '').trim();
|
|
962
|
+
linkHtml = `<a href="${url}" target="_blank" class="suggestion-link">📚 Learn more →</a>`;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
return `
|
|
966
|
+
<div class="suggestion-card">
|
|
967
|
+
<div class="suggestion-number">${index + 1}</div>
|
|
968
|
+
<div class="suggestion-content">
|
|
969
|
+
<div class="suggestion-text">${displayText}</div>
|
|
970
|
+
${linkHtml}
|
|
971
|
+
</div>
|
|
972
|
+
</div>
|
|
973
|
+
`;
|
|
974
|
+
})
|
|
975
|
+
.join('')}
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
`;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Errors section
|
|
983
|
+
if (summary.errors && summary.errors.length > 0) {
|
|
984
|
+
html += `
|
|
985
|
+
<div class="section">
|
|
986
|
+
<div class="section-header" onclick="toggleSection('errors')">
|
|
987
|
+
<div class="section-title">
|
|
988
|
+
<span>Errors (${summary.errors.length})</span>
|
|
989
|
+
</div>
|
|
990
|
+
<span class="toggle-icon" id="errors-toggle">▼</span>
|
|
991
|
+
</div>
|
|
992
|
+
<div class="section-content" id="errors-content">
|
|
993
|
+
<div class="issues-group">
|
|
994
|
+
${summary.errors
|
|
995
|
+
.map(
|
|
996
|
+
(issue) => `
|
|
997
|
+
<div class="issue-item error">
|
|
998
|
+
<div class="issue-header">
|
|
999
|
+
<span class="issue-icon">❌</span>
|
|
1000
|
+
<div class="issue-text">${issue.message}</div>
|
|
1001
|
+
</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
`
|
|
1004
|
+
)
|
|
1005
|
+
.join('')}
|
|
1006
|
+
</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
</div>
|
|
1009
|
+
`;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Warnings section
|
|
1013
|
+
if (summary.warnings && summary.warnings.length > 0) {
|
|
1014
|
+
html += `
|
|
1015
|
+
<div class="section">
|
|
1016
|
+
<div class="section-header" onclick="toggleSection('warnings')">
|
|
1017
|
+
<div class="section-title">
|
|
1018
|
+
<span>Warnings (${summary.warnings.length})</span>
|
|
1019
|
+
</div>
|
|
1020
|
+
<span class="toggle-icon" id="warnings-toggle">▼</span>
|
|
1021
|
+
</div>
|
|
1022
|
+
<div class="section-content" id="warnings-content">
|
|
1023
|
+
<div class="issues-group">
|
|
1024
|
+
${summary.warnings
|
|
1025
|
+
.map(
|
|
1026
|
+
(issue) => `
|
|
1027
|
+
<div class="issue-item warning">
|
|
1028
|
+
<div class="issue-header">
|
|
1029
|
+
<span class="issue-icon">⚠️</span>
|
|
1030
|
+
<div class="issue-text">${issue.message}</div>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
`
|
|
1034
|
+
)
|
|
1035
|
+
.join('')}
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
`;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return html;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Parse expo-doctor output for issues
|
|
1047
|
+
*/
|
|
1048
|
+
function parseIssues(output) {
|
|
1049
|
+
const issues = [];
|
|
1050
|
+
const lines = output.split('\n');
|
|
1051
|
+
|
|
1052
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1053
|
+
const line = lines[i];
|
|
1054
|
+
if (line.includes('✖') || line.includes('⚠') || line.toLowerCase().includes('error')) {
|
|
1055
|
+
issues.push({
|
|
1056
|
+
line: i + 1,
|
|
1057
|
+
message: line.trim(),
|
|
1058
|
+
severity: line.includes('✖') ? 'error' : 'warning',
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return issues;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Get human-readable summary of issues
|
|
1068
|
+
*/
|
|
1069
|
+
function getIssuesSummary(output) {
|
|
1070
|
+
const issues = parseIssues(output);
|
|
1071
|
+
|
|
1072
|
+
if (issues.length === 0) {
|
|
1073
|
+
return {
|
|
1074
|
+
hasIssues: false,
|
|
1075
|
+
summary: 'All checks passed! Your Expo project is healthy.',
|
|
1076
|
+
issues: [],
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const errors = issues.filter((i) => i.severity === 'error');
|
|
1081
|
+
const warnings = issues.filter((i) => i.severity === 'warning');
|
|
1082
|
+
|
|
1083
|
+
return {
|
|
1084
|
+
hasIssues: true,
|
|
1085
|
+
summary: `Found ${errors.length} error(s) and ${warnings.length} warning(s)`,
|
|
1086
|
+
errors,
|
|
1087
|
+
warnings,
|
|
1088
|
+
issues,
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
module.exports = {
|
|
1093
|
+
runExpoDoctor,
|
|
1094
|
+
generateHTMLReport,
|
|
1095
|
+
parseIssues,
|
|
1096
|
+
getIssuesSummary,
|
|
1097
|
+
};
|