@reliverse/dler 2.2.5 → 2.2.10

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.
Files changed (56) hide show
  1. package/README.md +14 -14
  2. package/dist/cli.js +1 -1
  3. package/dist/cmds/biome/cmd.js +58 -0
  4. package/dist/cmds/biome/impl.d.ts +26 -0
  5. package/dist/cmds/biome/impl.js +272 -0
  6. package/dist/cmds/build/cmd.js +18 -10
  7. package/dist/cmds/clean/cmd.js +6 -6
  8. package/dist/cmds/clean/impl.js +16 -12
  9. package/dist/cmds/clean/presets.js +2 -2
  10. package/dist/cmds/publish/cmd.js +7 -7
  11. package/dist/cmds/senv/cmd.js +13 -15
  12. package/dist/cmds/tsc/cache.js +1 -1
  13. package/dist/cmds/tsc/cmd.js +11 -8
  14. package/dist/cmds/tsc/impl.js +132 -17
  15. package/dist/cmds/update/cmd.js +11 -10
  16. package/dist/cmds/update/impl.d.ts +4 -4
  17. package/dist/cmds/update/impl.js +10 -11
  18. package/dist/cmds/update/utils.d.ts +23 -4
  19. package/dist/cmds/update/utils.js +22 -16
  20. package/package.json +16 -13
  21. package/dist/cmds/perf/analysis/bundle.d.ts +0 -20
  22. package/dist/cmds/perf/analysis/bundle.js +0 -225
  23. package/dist/cmds/perf/analysis/filesystem.d.ts +0 -27
  24. package/dist/cmds/perf/analysis/filesystem.js +0 -245
  25. package/dist/cmds/perf/analysis/monorepo.d.ts +0 -30
  26. package/dist/cmds/perf/analysis/monorepo.js +0 -345
  27. package/dist/cmds/perf/benchmarks/command.d.ts +0 -21
  28. package/dist/cmds/perf/benchmarks/command.js +0 -162
  29. package/dist/cmds/perf/benchmarks/memory.d.ts +0 -41
  30. package/dist/cmds/perf/benchmarks/memory.js +0 -169
  31. package/dist/cmds/perf/benchmarks/runner.d.ts +0 -22
  32. package/dist/cmds/perf/benchmarks/runner.js +0 -157
  33. package/dist/cmds/perf/cmd.js +0 -240
  34. package/dist/cmds/perf/impl.d.ts +0 -24
  35. package/dist/cmds/perf/impl.js +0 -297
  36. package/dist/cmds/perf/reporters/console.d.ts +0 -12
  37. package/dist/cmds/perf/reporters/console.js +0 -257
  38. package/dist/cmds/perf/reporters/html.d.ts +0 -27
  39. package/dist/cmds/perf/reporters/html.js +0 -881
  40. package/dist/cmds/perf/reporters/json.d.ts +0 -9
  41. package/dist/cmds/perf/reporters/json.js +0 -32
  42. package/dist/cmds/perf/types.d.ts +0 -184
  43. package/dist/cmds/perf/types.js +0 -0
  44. package/dist/cmds/perf/utils/cache.d.ts +0 -23
  45. package/dist/cmds/perf/utils/cache.js +0 -172
  46. package/dist/cmds/perf/utils/formatter.d.ts +0 -17
  47. package/dist/cmds/perf/utils/formatter.js +0 -134
  48. package/dist/cmds/perf/utils/stats.d.ts +0 -15
  49. package/dist/cmds/perf/utils/stats.js +0 -101
  50. package/dist/cmds/port/cmd.d.ts +0 -2
  51. package/dist/cmds/port/cmd.js +0 -58
  52. package/dist/cmds/port/impl.d.ts +0 -5
  53. package/dist/cmds/port/impl.js +0 -280
  54. package/dist/cmds/shell/cmd.d.ts +0 -2
  55. package/dist/cmds/shell/cmd.js +0 -46
  56. /package/dist/cmds/{perf → biome}/cmd.d.ts +0 -0
@@ -1,881 +0,0 @@
1
- import { writeFileSync } from "node:fs";
2
- import { logger } from "@reliverse/dler-logger";
3
- import {
4
- formatBytes,
5
- formatDuration,
6
- formatPercentage
7
- } from "../utils/formatter.js";
8
- export class HtmlReporter {
9
- outputPath;
10
- constructor(outputPath) {
11
- this.outputPath = outputPath;
12
- }
13
- report(report) {
14
- const html = this.generateHtml(report);
15
- if (this.outputPath) {
16
- try {
17
- writeFileSync(this.outputPath, html, "utf-8");
18
- logger.success(`\u{1F310} HTML report saved to: ${this.outputPath}`);
19
- } catch (error) {
20
- logger.error(`Failed to save HTML report: ${error}`);
21
- }
22
- } else {
23
- console.log(html);
24
- }
25
- }
26
- generateHtml(report) {
27
- const timestamp = new Date(report.timestamp).toLocaleString();
28
- return `<!DOCTYPE html>
29
- <html lang="en">
30
- <head>
31
- <meta charset="UTF-8">
32
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
- <title>Performance Analysis Report</title>
34
- <style>
35
- ${this.getStyles()}
36
- </style>
37
- </head>
38
- <body>
39
- <div class="container">
40
- <header>
41
- <h1>\u{1F4CA} Performance Analysis Report</h1>
42
- <p class="timestamp">Generated: ${timestamp}</p>
43
- </header>
44
-
45
- <main>
46
- ${this.generateBenchmarkSection(report.benchmark)}
47
- ${this.generateBundleAnalysisSection(report.bundleAnalysis)}
48
- ${this.generateFileSystemAnalysisSection(report.fileSystemAnalysis)}
49
- ${this.generateMonorepoAnalysisSection(report.monorepoAnalysis)}
50
- ${this.generateBaselineComparisonSection(report.baseline)}
51
- </main>
52
-
53
- <footer>
54
- <p>Generated by dler perf</p>
55
- </footer>
56
- </div>
57
-
58
- <script>
59
- ${this.getScripts()}
60
- <\/script>
61
- </body>
62
- </html>`;
63
- }
64
- generateBenchmarkSection(benchmark) {
65
- if (!benchmark) return "";
66
- return `
67
- <section class="section">
68
- <h2>\u{1F680} Command Benchmark Results</h2>
69
- <div class="summary">
70
- <div class="metric">
71
- <span class="label">Command:</span>
72
- <span class="value">${benchmark.command}</span>
73
- </div>
74
- <div class="metric">
75
- <span class="label">Runs:</span>
76
- <span class="value">${benchmark.runs} (${benchmark.warmup} warmup)</span>
77
- </div>
78
- <div class="metric">
79
- <span class="label">Concurrency:</span>
80
- <span class="value">${benchmark.concurrency}</span>
81
- </div>
82
- <div class="metric">
83
- <span class="label">Success:</span>
84
- <span class="value ${benchmark.success ? "success" : "error"}">${benchmark.success ? "\u2705" : "\u274C"}</span>
85
- </div>
86
- </div>
87
-
88
- <div class="stats-grid">
89
- <div class="stat-card">
90
- <h3>\u23F1\uFE0F Timing Statistics</h3>
91
- <div class="stat-list">
92
- <div class="stat-item">
93
- <span>Mean:</span>
94
- <span>${formatDuration(benchmark.statistics.mean)}</span>
95
- </div>
96
- <div class="stat-item">
97
- <span>Median:</span>
98
- <span>${formatDuration(benchmark.statistics.median)}</span>
99
- </div>
100
- <div class="stat-item">
101
- <span>Min:</span>
102
- <span>${formatDuration(benchmark.statistics.min)}</span>
103
- </div>
104
- <div class="stat-item">
105
- <span>Max:</span>
106
- <span>${formatDuration(benchmark.statistics.max)}</span>
107
- </div>
108
- <div class="stat-item">
109
- <span>P95:</span>
110
- <span>${formatDuration(benchmark.statistics.p95)}</span>
111
- </div>
112
- <div class="stat-item">
113
- <span>P99:</span>
114
- <span>${formatDuration(benchmark.statistics.p99)}</span>
115
- </div>
116
- </div>
117
- </div>
118
-
119
- <div class="stat-card">
120
- <h3>\u{1F4BE} Memory Statistics</h3>
121
- <div class="stat-list">
122
- <div class="stat-item">
123
- <span>Peak RSS:</span>
124
- <span>${formatBytes(benchmark.memory.peak.rss)}</span>
125
- </div>
126
- <div class="stat-item">
127
- <span>Avg RSS:</span>
128
- <span>${formatBytes(benchmark.memory.average.rss)}</span>
129
- </div>
130
- <div class="stat-item">
131
- <span>Peak Heap:</span>
132
- <span>${formatBytes(benchmark.memory.peak.heapUsed)}</span>
133
- </div>
134
- <div class="stat-item">
135
- <span>Avg Heap:</span>
136
- <span>${formatBytes(benchmark.memory.average.heapUsed)}</span>
137
- </div>
138
- <div class="stat-item">
139
- <span>Growth:</span>
140
- <span>${formatBytes(benchmark.memory.growth)}</span>
141
- </div>
142
- </div>
143
- </div>
144
- </div>
145
-
146
- ${this.generateTimingChart(benchmark)}
147
- </section>`;
148
- }
149
- generateBundleAnalysisSection(analysis) {
150
- if (!analysis) return "";
151
- return `
152
- <section class="section">
153
- <h2>\u{1F4E6} Bundle Analysis Results</h2>
154
- <div class="summary">
155
- <div class="metric">
156
- <span class="label">Target:</span>
157
- <span class="value">${analysis.target}</span>
158
- </div>
159
- <div class="metric">
160
- <span class="label">Total Size:</span>
161
- <span class="value">${formatBytes(analysis.totalSize)}</span>
162
- </div>
163
- <div class="metric">
164
- <span class="label">File Count:</span>
165
- <span class="value">${analysis.fileCount.toLocaleString()}</span>
166
- </div>
167
- <div class="metric">
168
- <span class="label">Compression Potential:</span>
169
- <span class="value">${analysis.compressionPotential.toFixed(1)}%</span>
170
- </div>
171
- </div>
172
-
173
- ${this.generateFileTable("Largest Files", analysis.largestFiles, ["File", "Size", "Percentage", "Type"])}
174
- ${this.generateModuleTable("Top Modules", analysis.modules, ["Module", "Size", "Percentage", "Type"])}
175
- ${this.generateDuplicateTable("Duplicate Dependencies", analysis.duplicates, ["Module", "Count", "Total Size", "Locations"])}
176
- </section>`;
177
- }
178
- generateFileSystemAnalysisSection(analysis) {
179
- if (!analysis) return "";
180
- return `
181
- <section class="section">
182
- <h2>\u{1F4C1} File System Analysis Results</h2>
183
- <div class="summary">
184
- <div class="metric">
185
- <span class="label">Target:</span>
186
- <span class="value">${analysis.target}</span>
187
- </div>
188
- <div class="metric">
189
- <span class="label">Total Files:</span>
190
- <span class="value">${analysis.totalFiles.toLocaleString()}</span>
191
- </div>
192
- <div class="metric">
193
- <span class="label">Total Size:</span>
194
- <span class="value">${formatBytes(analysis.totalSize)}</span>
195
- </div>
196
- <div class="metric">
197
- <span class="label">Directories:</span>
198
- <span class="value">${analysis.directoryCount.toLocaleString()}</span>
199
- </div>
200
- <div class="metric">
201
- <span class="label">Max Depth:</span>
202
- <span class="value">${analysis.maxDepth}</span>
203
- </div>
204
- <div class="metric">
205
- <span class="label">Compression Potential:</span>
206
- <span class="value">${analysis.compressionPotential.toFixed(1)}%</span>
207
- </div>
208
- </div>
209
-
210
- ${this.generateFileTable("Largest Files", analysis.largestFiles, ["File", "Size", "Percentage", "Type"])}
211
- ${this.generateDirectoryTable("Largest Directories", analysis.largestDirectories, ["Directory", "Size", "Files", "Depth"])}
212
- ${this.generateFileTypeTable("File Type Distribution", analysis.fileTypes, ["Type", "Count", "Size", "Percentage"])}
213
- </section>`;
214
- }
215
- generateMonorepoAnalysisSection(analysis) {
216
- if (!analysis) return "";
217
- return `
218
- <section class="section">
219
- <h2>\u{1F3D7}\uFE0F Monorepo Analysis Results</h2>
220
- <div class="summary">
221
- <div class="metric">
222
- <span class="label">Packages:</span>
223
- <span class="value">${analysis.packages.length.toLocaleString()}</span>
224
- </div>
225
- <div class="metric">
226
- <span class="label">Dependencies:</span>
227
- <span class="value">${analysis.dependencies.edges.length.toLocaleString()}</span>
228
- </div>
229
- <div class="metric">
230
- <span class="label">Circular Dependencies:</span>
231
- <span class="value">${analysis.circularDependencies.length}</span>
232
- </div>
233
- <div class="metric">
234
- <span class="label">Suggested Concurrency:</span>
235
- <span class="value">${analysis.suggestedConcurrency}</span>
236
- </div>
237
- </div>
238
-
239
- ${this.generateBuildOrderSection(analysis)}
240
- ${this.generateCircularDependenciesSection(analysis.circularDependencies)}
241
- ${this.generateBottlenecksSection(analysis.bottlenecks)}
242
- </section>`;
243
- }
244
- generateBaselineComparisonSection(baseline) {
245
- if (!baseline?.exists) return "";
246
- return `
247
- <section class="section">
248
- <h2>\u{1F4C8} Baseline Comparison</h2>
249
- <div class="comparison">
250
- ${baseline.improvement !== void 0 ? `
251
- <div class="metric">
252
- <span class="label">Performance:</span>
253
- <span class="value improvement">+${baseline.improvement.toFixed(2)}%</span>
254
- </div>
255
- ` : ""}
256
- ${baseline.regression !== void 0 ? `
257
- <div class="metric">
258
- <span class="label">Performance:</span>
259
- <span class="value regression">-${baseline.regression.toFixed(2)}%</span>
260
- </div>
261
- ` : ""}
262
- </div>
263
- </section>`;
264
- }
265
- generateFileTable(title, files, headers) {
266
- if (files.length === 0) return "";
267
- return `
268
- <div class="table-section">
269
- <h3>${title}</h3>
270
- <div class="table-container">
271
- <table>
272
- <thead>
273
- <tr>
274
- ${headers.map((h) => `<th>${h}</th>`).join("")}
275
- </tr>
276
- </thead>
277
- <tbody>
278
- ${files.map(
279
- (file) => `
280
- <tr>
281
- <td class="file-path">${this.truncatePath(file.path || file.name, 50)}</td>
282
- <td>${formatBytes(file.size)}</td>
283
- <td>${formatPercentage(file.percentage || 0, 100)}</td>
284
- <td>${file.type || (file.isExternal ? "External" : "Internal")}</td>
285
- </tr>
286
- `
287
- ).join("")}
288
- </tbody>
289
- </table>
290
- </div>
291
- </div>`;
292
- }
293
- generateModuleTable(title, modules, headers) {
294
- if (modules.length === 0) return "";
295
- return `
296
- <div class="table-section">
297
- <h3>${title}</h3>
298
- <div class="table-container">
299
- <table>
300
- <thead>
301
- <tr>
302
- ${headers.map((h) => `<th>${h}</th>`).join("")}
303
- </tr>
304
- </thead>
305
- <tbody>
306
- ${modules.map(
307
- (module) => `
308
- <tr>
309
- <td class="file-path">${this.truncatePath(module.name, 50)}</td>
310
- <td>${formatBytes(module.size)}</td>
311
- <td>${formatPercentage(module.percentage || 0, 100)}</td>
312
- <td>${module.isExternal ? "External" : "Internal"}</td>
313
- </tr>
314
- `
315
- ).join("")}
316
- </tbody>
317
- </table>
318
- </div>
319
- </div>`;
320
- }
321
- generateDuplicateTable(title, duplicates, headers) {
322
- if (duplicates.length === 0) return "";
323
- return `
324
- <div class="table-section">
325
- <h3>${title}</h3>
326
- <div class="table-container">
327
- <table>
328
- <thead>
329
- <tr>
330
- ${headers.map((h) => `<th>${h}</th>`).join("")}
331
- </tr>
332
- </thead>
333
- <tbody>
334
- ${duplicates.map(
335
- (dup) => `
336
- <tr>
337
- <td class="file-path">${this.truncatePath(dup.name, 30)}</td>
338
- <td>${dup.count}</td>
339
- <td>${formatBytes(dup.totalSize)}</td>
340
- <td>${dup.locations.length}</td>
341
- </tr>
342
- `
343
- ).join("")}
344
- </tbody>
345
- </table>
346
- </div>
347
- </div>`;
348
- }
349
- generateDirectoryTable(title, directories, headers) {
350
- if (directories.length === 0) return "";
351
- return `
352
- <div class="table-section">
353
- <h3>${title}</h3>
354
- <div class="table-container">
355
- <table>
356
- <thead>
357
- <tr>
358
- ${headers.map((h) => `<th>${h}</th>`).join("")}
359
- </tr>
360
- </thead>
361
- <tbody>
362
- ${directories.map(
363
- (dir) => `
364
- <tr>
365
- <td class="file-path">${this.truncatePath(dir.path, 50)}</td>
366
- <td>${formatBytes(dir.size)}</td>
367
- <td>${dir.fileCount.toLocaleString()}</td>
368
- <td>${dir.depth}</td>
369
- </tr>
370
- `
371
- ).join("")}
372
- </tbody>
373
- </table>
374
- </div>
375
- </div>`;
376
- }
377
- generateFileTypeTable(title, types, headers) {
378
- if (types.length === 0) return "";
379
- return `
380
- <div class="table-section">
381
- <h3>${title}</h3>
382
- <div class="table-container">
383
- <table>
384
- <thead>
385
- <tr>
386
- ${headers.map((h) => `<th>${h}</th>`).join("")}
387
- </tr>
388
- </thead>
389
- <tbody>
390
- ${types.map(
391
- (type) => `
392
- <tr>
393
- <td>${type.extension || "no-extension"}</td>
394
- <td>${type.count.toLocaleString()}</td>
395
- <td>${formatBytes(type.totalSize)}</td>
396
- <td>${formatPercentage(type.percentage || 0, 100)}</td>
397
- </tr>
398
- `
399
- ).join("")}
400
- </tbody>
401
- </table>
402
- </div>
403
- </div>`;
404
- }
405
- generateBuildOrderSection(analysis) {
406
- if (!analysis?.dependencies.levels.length) return "";
407
- return `
408
- <div class="table-section">
409
- <h3>\u{1F504} Build Order</h3>
410
- <div class="build-order">
411
- ${analysis.dependencies.levels.map(
412
- (level, i) => `
413
- <div class="build-level">
414
- <h4>Level ${i + 1}</h4>
415
- <div class="package-list">
416
- ${level.map((pkg) => `<span class="package">${pkg}</span>`).join("")}
417
- </div>
418
- </div>
419
- `
420
- ).join("")}
421
- </div>
422
- </div>`;
423
- }
424
- generateCircularDependenciesSection(circular) {
425
- if (circular.length === 0) return "";
426
- return `
427
- <div class="table-section">
428
- <h3>\u{1F504} Circular Dependencies</h3>
429
- <div class="circular-deps">
430
- ${circular.map(
431
- (circ) => `
432
- <div class="circular-item ${circ.severity}">
433
- <span class="severity">${circ.severity.toUpperCase()}</span>
434
- <span class="cycle">${circ.cycle.join(" \u2192 ")}</span>
435
- </div>
436
- `
437
- ).join("")}
438
- </div>
439
- </div>`;
440
- }
441
- generateBottlenecksSection(bottlenecks) {
442
- if (bottlenecks.length === 0) return "";
443
- return `
444
- <div class="table-section">
445
- <h3>\u26A0\uFE0F Bottlenecks</h3>
446
- <div class="table-container">
447
- <table>
448
- <thead>
449
- <tr>
450
- <th>Package</th>
451
- <th>Type</th>
452
- <th>Impact</th>
453
- <th>Suggestion</th>
454
- </tr>
455
- </thead>
456
- <tbody>
457
- ${bottlenecks.map(
458
- (bottleneck) => `
459
- <tr>
460
- <td>${bottleneck.package}</td>
461
- <td>${bottleneck.type}</td>
462
- <td>${bottleneck.impact}</td>
463
- <td>${bottleneck.suggestion}</td>
464
- </tr>
465
- `
466
- ).join("")}
467
- </tbody>
468
- </table>
469
- </div>
470
- </div>`;
471
- }
472
- generateTimingChart(benchmark) {
473
- if (!benchmark) return "";
474
- const measurements = benchmark.measurements.filter((m) => m.success);
475
- if (measurements.length === 0) return "";
476
- const maxDuration = Math.max(...measurements.map((m) => m.duration));
477
- return `
478
- <div class="chart-section">
479
- <h3>\u{1F4CA} Timing Distribution</h3>
480
- <div class="timing-chart">
481
- ${measurements.map(
482
- (m, i) => `
483
- <div class="timing-bar">
484
- <span class="run-number">${i + 1}</span>
485
- <div class="bar-container">
486
- <div class="bar" style="width: ${m.duration / maxDuration * 100}%"></div>
487
- <span class="bar-value">${formatDuration(m.duration)}</span>
488
- </div>
489
- </div>
490
- `
491
- ).join("")}
492
- </div>
493
- </div>`;
494
- }
495
- truncatePath(path, maxLength) {
496
- if (path.length <= maxLength) return path;
497
- const start = path.substring(0, Math.floor(maxLength / 2) - 2);
498
- const end = path.substring(path.length - Math.floor(maxLength / 2) + 2);
499
- return `${start}...${end}`;
500
- }
501
- getStyles() {
502
- return `
503
- * {
504
- margin: 0;
505
- padding: 0;
506
- box-sizing: border-box;
507
- }
508
-
509
- body {
510
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
511
- line-height: 1.6;
512
- color: #333;
513
- background: #f8f9fa;
514
- }
515
-
516
- .container {
517
- max-width: 1200px;
518
- margin: 0 auto;
519
- padding: 20px;
520
- }
521
-
522
- header {
523
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
524
- color: white;
525
- padding: 30px;
526
- border-radius: 10px;
527
- margin-bottom: 30px;
528
- text-align: center;
529
- }
530
-
531
- header h1 {
532
- font-size: 2.5em;
533
- margin-bottom: 10px;
534
- }
535
-
536
- .timestamp {
537
- opacity: 0.9;
538
- font-size: 1.1em;
539
- }
540
-
541
- .section {
542
- background: white;
543
- padding: 30px;
544
- border-radius: 10px;
545
- margin-bottom: 30px;
546
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
547
- }
548
-
549
- .section h2 {
550
- color: #2c3e50;
551
- margin-bottom: 20px;
552
- font-size: 1.8em;
553
- }
554
-
555
- .summary {
556
- display: grid;
557
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
558
- gap: 20px;
559
- margin-bottom: 30px;
560
- }
561
-
562
- .metric {
563
- display: flex;
564
- justify-content: space-between;
565
- align-items: center;
566
- padding: 15px;
567
- background: #f8f9fa;
568
- border-radius: 8px;
569
- border-left: 4px solid #667eea;
570
- }
571
-
572
- .metric .label {
573
- font-weight: 600;
574
- color: #555;
575
- }
576
-
577
- .metric .value {
578
- font-weight: 700;
579
- color: #2c3e50;
580
- }
581
-
582
- .metric .value.success {
583
- color: #27ae60;
584
- }
585
-
586
- .metric .value.error {
587
- color: #e74c3c;
588
- }
589
-
590
- .metric .value.improvement {
591
- color: #27ae60;
592
- }
593
-
594
- .metric .value.regression {
595
- color: #e74c3c;
596
- }
597
-
598
- .stats-grid {
599
- display: grid;
600
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
601
- gap: 30px;
602
- margin-bottom: 30px;
603
- }
604
-
605
- .stat-card {
606
- background: #f8f9fa;
607
- padding: 20px;
608
- border-radius: 8px;
609
- border: 1px solid #e9ecef;
610
- }
611
-
612
- .stat-card h3 {
613
- color: #2c3e50;
614
- margin-bottom: 15px;
615
- font-size: 1.3em;
616
- }
617
-
618
- .stat-list {
619
- display: flex;
620
- flex-direction: column;
621
- gap: 10px;
622
- }
623
-
624
- .stat-item {
625
- display: flex;
626
- justify-content: space-between;
627
- align-items: center;
628
- padding: 8px 0;
629
- border-bottom: 1px solid #e9ecef;
630
- }
631
-
632
- .stat-item:last-child {
633
- border-bottom: none;
634
- }
635
-
636
- .table-section {
637
- margin-bottom: 30px;
638
- }
639
-
640
- .table-section h3 {
641
- color: #2c3e50;
642
- margin-bottom: 15px;
643
- font-size: 1.3em;
644
- }
645
-
646
- .table-container {
647
- overflow-x: auto;
648
- }
649
-
650
- table {
651
- width: 100%;
652
- border-collapse: collapse;
653
- background: white;
654
- border-radius: 8px;
655
- overflow: hidden;
656
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
657
- }
658
-
659
- th {
660
- background: #667eea;
661
- color: white;
662
- padding: 15px;
663
- text-align: left;
664
- font-weight: 600;
665
- }
666
-
667
- td {
668
- padding: 12px 15px;
669
- border-bottom: 1px solid #e9ecef;
670
- }
671
-
672
- tr:hover {
673
- background: #f8f9fa;
674
- }
675
-
676
- .file-path {
677
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
678
- font-size: 0.9em;
679
- color: #666;
680
- }
681
-
682
- .build-order {
683
- display: flex;
684
- flex-direction: column;
685
- gap: 20px;
686
- }
687
-
688
- .build-level {
689
- background: #f8f9fa;
690
- padding: 20px;
691
- border-radius: 8px;
692
- border-left: 4px solid #667eea;
693
- }
694
-
695
- .build-level h4 {
696
- color: #2c3e50;
697
- margin-bottom: 10px;
698
- }
699
-
700
- .package-list {
701
- display: flex;
702
- flex-wrap: wrap;
703
- gap: 10px;
704
- }
705
-
706
- .package {
707
- background: #667eea;
708
- color: white;
709
- padding: 5px 12px;
710
- border-radius: 20px;
711
- font-size: 0.9em;
712
- font-weight: 500;
713
- }
714
-
715
- .circular-deps {
716
- display: flex;
717
- flex-direction: column;
718
- gap: 15px;
719
- }
720
-
721
- .circular-item {
722
- display: flex;
723
- align-items: center;
724
- gap: 15px;
725
- padding: 15px;
726
- border-radius: 8px;
727
- border-left: 4px solid #e74c3c;
728
- }
729
-
730
- .circular-item.low {
731
- border-left-color: #f39c12;
732
- }
733
-
734
- .circular-item.medium {
735
- border-left-color: #e67e22;
736
- }
737
-
738
- .circular-item.high {
739
- border-left-color: #e74c3c;
740
- }
741
-
742
- .severity {
743
- background: #e74c3c;
744
- color: white;
745
- padding: 4px 8px;
746
- border-radius: 4px;
747
- font-size: 0.8em;
748
- font-weight: 600;
749
- text-transform: uppercase;
750
- }
751
-
752
- .circular-item.low .severity {
753
- background: #f39c12;
754
- }
755
-
756
- .circular-item.medium .severity {
757
- background: #e67e22;
758
- }
759
-
760
- .cycle {
761
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
762
- font-size: 0.9em;
763
- color: #666;
764
- }
765
-
766
- .chart-section {
767
- margin-top: 30px;
768
- }
769
-
770
- .chart-section h3 {
771
- color: #2c3e50;
772
- margin-bottom: 15px;
773
- font-size: 1.3em;
774
- }
775
-
776
- .timing-chart {
777
- background: #f8f9fa;
778
- padding: 20px;
779
- border-radius: 8px;
780
- }
781
-
782
- .timing-bar {
783
- display: flex;
784
- align-items: center;
785
- gap: 15px;
786
- margin-bottom: 10px;
787
- }
788
-
789
- .run-number {
790
- min-width: 30px;
791
- font-weight: 600;
792
- color: #666;
793
- }
794
-
795
- .bar-container {
796
- flex: 1;
797
- position: relative;
798
- height: 25px;
799
- background: #e9ecef;
800
- border-radius: 12px;
801
- overflow: hidden;
802
- }
803
-
804
- .bar {
805
- height: 100%;
806
- background: linear-gradient(90deg, #667eea, #764ba2);
807
- border-radius: 12px;
808
- transition: width 0.3s ease;
809
- }
810
-
811
- .bar-value {
812
- position: absolute;
813
- right: 10px;
814
- top: 50%;
815
- transform: translateY(-50%);
816
- font-size: 0.9em;
817
- font-weight: 600;
818
- color: #2c3e50;
819
- }
820
-
821
- footer {
822
- text-align: center;
823
- padding: 20px;
824
- color: #666;
825
- font-size: 0.9em;
826
- }
827
-
828
- @media (max-width: 768px) {
829
- .container {
830
- padding: 10px;
831
- }
832
-
833
- .summary {
834
- grid-template-columns: 1fr;
835
- }
836
-
837
- .stats-grid {
838
- grid-template-columns: 1fr;
839
- }
840
-
841
- .package-list {
842
- flex-direction: column;
843
- }
844
- }
845
- `;
846
- }
847
- getScripts() {
848
- return `
849
- // Add any interactive functionality here
850
- document.addEventListener('DOMContentLoaded', function() {
851
- // Add smooth scrolling for better UX
852
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
853
- anchor.addEventListener('click', function (e) {
854
- e.preventDefault();
855
- document.querySelector(this.getAttribute('href')).scrollIntoView({
856
- behavior: 'smooth'
857
- });
858
- });
859
- });
860
-
861
- // Add tooltips for truncated paths
862
- document.querySelectorAll('.file-path').forEach(element => {
863
- if (element.textContent.length > 50) {
864
- element.title = element.textContent;
865
- }
866
- });
867
- });
868
- `;
869
- }
870
- static save(report, outputPath) {
871
- const reporter = new HtmlReporter(outputPath);
872
- reporter.report(report);
873
- }
874
- static print(report) {
875
- const reporter = new HtmlReporter();
876
- reporter.report(report);
877
- }
878
- }
879
- export const createHtmlReporter = (outputPath) => {
880
- return new HtmlReporter(outputPath);
881
- };