@reliverse/dler 2.0.0 → 2.0.2

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