@reliverse/dler 2.0.6 → 2.0.8
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/package.json +16 -15
- package/src/cli.ts +0 -8
- package/src/cmds/build/cmd.ts +0 -568
- package/src/cmds/clean/cmd.ts +0 -166
- package/src/cmds/clean/impl.ts +0 -900
- package/src/cmds/clean/presets.ts +0 -158
- package/src/cmds/clean/types.ts +0 -71
- package/src/cmds/init/cmd.ts +0 -68
- package/src/cmds/init/impl/config.ts +0 -105
- package/src/cmds/init/impl/generators.ts +0 -220
- package/src/cmds/init/impl/prompts.ts +0 -137
- package/src/cmds/init/impl/types.ts +0 -25
- package/src/cmds/init/impl/utils.ts +0 -17
- package/src/cmds/init/impl/validators.ts +0 -55
- package/src/cmds/integrate/cmd.ts +0 -82
- package/src/cmds/integrate/impl.ts +0 -204
- package/src/cmds/integrate/integrations/base.ts +0 -69
- package/src/cmds/integrate/integrations/nextjs.ts +0 -227
- package/src/cmds/integrate/integrations/registry.ts +0 -45
- package/src/cmds/integrate/integrations/ultracite.ts +0 -53
- package/src/cmds/integrate/types.ts +0 -48
- package/src/cmds/integrate/utils/biome.ts +0 -173
- package/src/cmds/integrate/utils/context.ts +0 -148
- package/src/cmds/integrate/utils/temp.ts +0 -47
- package/src/cmds/perf/analysis/bundle.ts +0 -311
- package/src/cmds/perf/analysis/filesystem.ts +0 -324
- package/src/cmds/perf/analysis/monorepo.ts +0 -439
- package/src/cmds/perf/benchmarks/command.ts +0 -230
- package/src/cmds/perf/benchmarks/memory.ts +0 -249
- package/src/cmds/perf/benchmarks/runner.ts +0 -220
- package/src/cmds/perf/cmd.ts +0 -285
- package/src/cmds/perf/impl.ts +0 -411
- package/src/cmds/perf/reporters/console.ts +0 -331
- package/src/cmds/perf/reporters/html.ts +0 -984
- package/src/cmds/perf/reporters/json.ts +0 -42
- package/src/cmds/perf/types.ts +0 -220
- package/src/cmds/perf/utils/cache.ts +0 -234
- package/src/cmds/perf/utils/formatter.ts +0 -190
- package/src/cmds/perf/utils/stats.ts +0 -153
- package/src/cmds/publish/cmd.ts +0 -213
- package/src/cmds/shell/cmd.ts +0 -61
- package/src/cmds/tsc/cache.ts +0 -237
- package/src/cmds/tsc/cmd.ts +0 -139
- package/src/cmds/tsc/impl.ts +0 -855
- package/src/cmds/tsc/types.ts +0 -66
- package/tsconfig.json +0 -9
|
@@ -1,984 +0,0 @@
|
|
|
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
|
-
};
|