@reliverse/dler 2.0.0 → 2.0.1
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/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/cmds/build/cmd.d.ts +2 -0
- package/dist/cmds/build/cmd.js +564 -0
- package/dist/cmds/clean/cmd.d.ts +2 -0
- package/dist/cmds/clean/cmd.js +146 -0
- package/dist/cmds/clean/impl.d.ts +2 -0
- package/dist/cmds/clean/impl.js +627 -0
- package/dist/cmds/clean/presets.d.ts +10 -0
- package/dist/cmds/clean/presets.js +112 -0
- package/dist/cmds/clean/types.d.ts +62 -0
- package/dist/cmds/clean/types.js +0 -0
- package/dist/cmds/init/cmd.d.ts +3 -0
- package/dist/cmds/init/cmd.js +56 -0
- package/dist/cmds/init/impl/config.d.ts +45 -0
- package/dist/cmds/init/impl/config.js +99 -0
- package/dist/cmds/init/impl/generators.d.ts +6 -0
- package/dist/cmds/init/impl/generators.js +178 -0
- package/dist/cmds/init/impl/prompts.d.ts +2 -0
- package/dist/cmds/init/impl/prompts.js +98 -0
- package/dist/cmds/init/impl/types.d.ts +22 -0
- package/dist/cmds/init/impl/types.js +0 -0
- package/dist/cmds/init/impl/utils.d.ts +4 -0
- package/dist/cmds/init/impl/utils.js +11 -0
- package/dist/cmds/init/impl/validators.d.ts +4 -0
- package/dist/cmds/init/impl/validators.js +42 -0
- package/dist/cmds/integrate/cmd.d.ts +3 -0
- package/dist/cmds/integrate/cmd.js +70 -0
- package/dist/cmds/integrate/impl.d.ts +7 -0
- package/dist/cmds/integrate/impl.js +127 -0
- package/dist/cmds/integrate/integrations/base.d.ts +13 -0
- package/dist/cmds/integrate/integrations/base.js +41 -0
- package/dist/cmds/integrate/integrations/nextjs.d.ts +16 -0
- package/dist/cmds/integrate/integrations/nextjs.js +167 -0
- package/dist/cmds/integrate/integrations/registry.d.ts +7 -0
- package/dist/cmds/integrate/integrations/registry.js +31 -0
- package/dist/cmds/integrate/integrations/ultracite.d.ts +11 -0
- package/dist/cmds/integrate/integrations/ultracite.js +40 -0
- package/dist/cmds/integrate/types.d.ts +39 -0
- package/dist/cmds/integrate/types.js +0 -0
- package/dist/cmds/integrate/utils/biome.d.ts +4 -0
- package/dist/cmds/integrate/utils/biome.js +140 -0
- package/dist/cmds/integrate/utils/context.d.ts +3 -0
- package/dist/cmds/integrate/utils/context.js +111 -0
- package/dist/cmds/integrate/utils/temp.d.ts +3 -0
- package/dist/cmds/integrate/utils/temp.js +36 -0
- package/dist/cmds/perf/analysis/bundle.d.ts +20 -0
- package/dist/cmds/perf/analysis/bundle.js +225 -0
- package/dist/cmds/perf/analysis/filesystem.d.ts +27 -0
- package/dist/cmds/perf/analysis/filesystem.js +246 -0
- package/dist/cmds/perf/analysis/monorepo.d.ts +29 -0
- package/dist/cmds/perf/analysis/monorepo.js +307 -0
- package/dist/cmds/perf/benchmarks/command.d.ts +21 -0
- package/dist/cmds/perf/benchmarks/command.js +162 -0
- package/dist/cmds/perf/benchmarks/memory.d.ts +41 -0
- package/dist/cmds/perf/benchmarks/memory.js +169 -0
- package/dist/cmds/perf/benchmarks/runner.d.ts +22 -0
- package/dist/cmds/perf/benchmarks/runner.js +157 -0
- package/dist/cmds/perf/cmd.d.ts +2 -0
- package/dist/cmds/perf/cmd.js +238 -0
- package/dist/cmds/perf/impl.d.ts +24 -0
- package/dist/cmds/perf/impl.js +304 -0
- package/dist/cmds/perf/reporters/console.d.ts +12 -0
- package/dist/cmds/perf/reporters/console.js +257 -0
- package/dist/cmds/perf/reporters/html.d.ts +27 -0
- package/dist/cmds/perf/reporters/html.js +881 -0
- package/dist/cmds/perf/reporters/json.d.ts +9 -0
- package/dist/cmds/perf/reporters/json.js +32 -0
- package/dist/cmds/perf/types.d.ts +184 -0
- package/dist/cmds/perf/types.js +0 -0
- package/dist/cmds/perf/utils/cache.d.ts +23 -0
- package/dist/cmds/perf/utils/cache.js +171 -0
- package/dist/cmds/perf/utils/formatter.d.ts +17 -0
- package/dist/cmds/perf/utils/formatter.js +134 -0
- package/dist/cmds/perf/utils/stats.d.ts +15 -0
- package/dist/cmds/perf/utils/stats.js +101 -0
- package/dist/cmds/publish/cmd.d.ts +3 -0
- package/dist/cmds/publish/cmd.js +189 -0
- package/dist/cmds/shell/cmd.d.ts +3 -0
- package/dist/cmds/shell/cmd.js +50 -0
- package/dist/cmds/tsc/cache.d.ts +27 -0
- package/dist/cmds/tsc/cache.js +160 -0
- package/dist/cmds/tsc/cmd.d.ts +2 -0
- package/dist/cmds/tsc/cmd.js +111 -0
- package/dist/cmds/tsc/impl.d.ts +41 -0
- package/dist/cmds/tsc/impl.js +572 -0
- package/dist/cmds/tsc/types.d.ts +57 -0
- package/dist/cmds/tsc/types.js +0 -0
- package/package.json +4 -11
- package/src/cli.ts +8 -0
- package/src/cmds/build/cmd.ts +582 -0
- package/src/cmds/clean/cmd.ts +166 -0
- package/src/cmds/clean/impl.ts +900 -0
- package/src/cmds/clean/presets.ts +158 -0
- package/src/cmds/clean/types.ts +71 -0
- package/src/cmds/init/cmd.ts +68 -0
- package/src/cmds/init/impl/config.ts +105 -0
- package/src/cmds/init/impl/generators.ts +220 -0
- package/src/cmds/init/impl/prompts.ts +137 -0
- package/src/cmds/init/impl/types.ts +25 -0
- package/src/cmds/init/impl/utils.ts +17 -0
- package/src/cmds/init/impl/validators.ts +55 -0
- package/src/cmds/integrate/cmd.ts +82 -0
- package/src/cmds/integrate/impl.ts +204 -0
- package/src/cmds/integrate/integrations/base.ts +69 -0
- package/src/cmds/integrate/integrations/nextjs.ts +227 -0
- package/src/cmds/integrate/integrations/registry.ts +45 -0
- package/src/cmds/integrate/integrations/ultracite.ts +53 -0
- package/src/cmds/integrate/types.ts +48 -0
- package/src/cmds/integrate/utils/biome.ts +173 -0
- package/src/cmds/integrate/utils/context.ts +148 -0
- package/src/cmds/integrate/utils/temp.ts +47 -0
- package/src/cmds/perf/analysis/bundle.ts +311 -0
- package/src/cmds/perf/analysis/filesystem.ts +324 -0
- package/src/cmds/perf/analysis/monorepo.ts +439 -0
- package/src/cmds/perf/benchmarks/command.ts +230 -0
- package/src/cmds/perf/benchmarks/memory.ts +249 -0
- package/src/cmds/perf/benchmarks/runner.ts +220 -0
- package/src/cmds/perf/cmd.ts +285 -0
- package/src/cmds/perf/impl.ts +411 -0
- package/src/cmds/perf/reporters/console.ts +331 -0
- package/src/cmds/perf/reporters/html.ts +984 -0
- package/src/cmds/perf/reporters/json.ts +42 -0
- package/src/cmds/perf/types.ts +220 -0
- package/src/cmds/perf/utils/cache.ts +234 -0
- package/src/cmds/perf/utils/formatter.ts +190 -0
- package/src/cmds/perf/utils/stats.ts +153 -0
- package/src/cmds/publish/cmd.ts +215 -0
- package/src/cmds/shell/cmd.ts +61 -0
- package/src/cmds/tsc/cache.ts +237 -0
- package/src/cmds/tsc/cmd.ts +139 -0
- package/src/cmds/tsc/impl.ts +855 -0
- package/src/cmds/tsc/types.ts +66 -0
- package/tsconfig.json +9 -0
- package/cli.js +0 -1316
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// apps/dler/src/cmds/perf/cmd.ts
|
|
2
|
+
|
|
3
|
+
// Examples:
|
|
4
|
+
// # Command benchmarking
|
|
5
|
+
// dler perf --target "node --version" --runs 10 --warmup 2
|
|
6
|
+
// bun dler perf --runs 10 --warmup 2 --target "node --version"
|
|
7
|
+
// # Monorepo analysis
|
|
8
|
+
// dler perf --type monorepo --verbose
|
|
9
|
+
// # File system profiling
|
|
10
|
+
// dler perf --target ./src --type file --verbose
|
|
11
|
+
// # JSON output
|
|
12
|
+
// dler perf --target "node --version" --output json
|
|
13
|
+
// # Baseline comparison
|
|
14
|
+
// dler perf build --save
|
|
15
|
+
// dler perf build --compare
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
defineCmd,
|
|
19
|
+
defineCmdArgs,
|
|
20
|
+
defineCmdCfg,
|
|
21
|
+
} from "@reliverse/dler-launcher";
|
|
22
|
+
import { logger } from "@reliverse/dler-logger";
|
|
23
|
+
import { runPerfAnalysis } from "./impl";
|
|
24
|
+
import { createConsoleReporter } from "./reporters/console";
|
|
25
|
+
import { createHtmlReporter } from "./reporters/html";
|
|
26
|
+
import { createJsonReporter } from "./reporters/json";
|
|
27
|
+
import type { PerfAnalysisType, PerfOptions, PerfOutputFormat } from "./types";
|
|
28
|
+
|
|
29
|
+
const perfCmd = async (args: PerfOptions): Promise<void> => {
|
|
30
|
+
try {
|
|
31
|
+
// Check if running in Bun
|
|
32
|
+
if (typeof process.versions.bun === "undefined") {
|
|
33
|
+
logger.error("❌ This command requires Bun runtime. Sorry.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Validate type parameter
|
|
38
|
+
const validTypes: PerfAnalysisType[] = [
|
|
39
|
+
"command",
|
|
40
|
+
"bundle",
|
|
41
|
+
"file",
|
|
42
|
+
"monorepo",
|
|
43
|
+
"auto",
|
|
44
|
+
];
|
|
45
|
+
if (args.type && !validTypes.includes(args.type as PerfAnalysisType)) {
|
|
46
|
+
logger.error(
|
|
47
|
+
`❌ Invalid type: ${args.type}. Must be one of: ${validTypes.join(", ")}`,
|
|
48
|
+
);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validate output parameter
|
|
53
|
+
const validOutputs: PerfOutputFormat[] = ["console", "json", "html", "all"];
|
|
54
|
+
if (
|
|
55
|
+
args.output &&
|
|
56
|
+
!validOutputs.includes(args.output as PerfOutputFormat)
|
|
57
|
+
) {
|
|
58
|
+
logger.error(
|
|
59
|
+
`❌ Invalid output format: ${args.output}. Must be one of: ${validOutputs.join(", ")}`,
|
|
60
|
+
);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Run performance analysis
|
|
65
|
+
const result = await runPerfAnalysis(args);
|
|
66
|
+
|
|
67
|
+
if (!result.success) {
|
|
68
|
+
logger.error(`❌ Performance analysis failed: ${result.error}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Generate reports based on output format
|
|
73
|
+
const outputFormats: PerfOutputFormat[] =
|
|
74
|
+
args.output === "all"
|
|
75
|
+
? ["console", "json", "html"]
|
|
76
|
+
: [(args.output as PerfOutputFormat) ?? "console"];
|
|
77
|
+
|
|
78
|
+
for (const format of outputFormats) {
|
|
79
|
+
switch (format) {
|
|
80
|
+
case "console": {
|
|
81
|
+
const consoleReporter = createConsoleReporter(args.verbose);
|
|
82
|
+
consoleReporter.report(result.report);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case "json": {
|
|
87
|
+
const jsonReporter = createJsonReporter();
|
|
88
|
+
jsonReporter.report(result.report);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case "html": {
|
|
93
|
+
const htmlReporter = createHtmlReporter();
|
|
94
|
+
htmlReporter.report(result.report);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
logger.success("\n✅ Performance analysis completed successfully!");
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.error("\n❌ Performance analysis failed:");
|
|
103
|
+
|
|
104
|
+
if (error instanceof Error) {
|
|
105
|
+
logger.error(error.message);
|
|
106
|
+
} else {
|
|
107
|
+
logger.error(String(error));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const perfCmdArgs = defineCmdArgs({
|
|
115
|
+
target: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "What to measure (command name, file path, or directory)",
|
|
118
|
+
},
|
|
119
|
+
type: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description:
|
|
122
|
+
"Type of analysis: command, bundle, file, monorepo, or auto (default: auto)",
|
|
123
|
+
},
|
|
124
|
+
runs: {
|
|
125
|
+
type: "number",
|
|
126
|
+
description: "Number of benchmark iterations (default: 10)",
|
|
127
|
+
},
|
|
128
|
+
warmup: {
|
|
129
|
+
type: "number",
|
|
130
|
+
description: "Warmup runs before measurement (default: 2)",
|
|
131
|
+
},
|
|
132
|
+
concurrency: {
|
|
133
|
+
type: "number",
|
|
134
|
+
description: "For parallel benchmarks (default: 1)",
|
|
135
|
+
},
|
|
136
|
+
compare: {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
description: "Compare with cached baseline (default: false)",
|
|
139
|
+
},
|
|
140
|
+
output: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description:
|
|
143
|
+
"Output format: console, json, html, or all (default: console)",
|
|
144
|
+
},
|
|
145
|
+
save: {
|
|
146
|
+
type: "boolean",
|
|
147
|
+
description: "Save results as baseline (default: false)",
|
|
148
|
+
},
|
|
149
|
+
verbose: {
|
|
150
|
+
type: "boolean",
|
|
151
|
+
description: "Detailed output (default: false)",
|
|
152
|
+
},
|
|
153
|
+
cwd: {
|
|
154
|
+
type: "string",
|
|
155
|
+
description: "Working directory (default: current directory)",
|
|
156
|
+
},
|
|
157
|
+
ignore: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description:
|
|
160
|
+
"Packages to ignore for monorepo analysis (supports wildcards like @reliverse/*)",
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const perfCmdCfg = defineCmdCfg({
|
|
165
|
+
name: "perf",
|
|
166
|
+
description:
|
|
167
|
+
"Comprehensive performance measurement and analysis tool. Benchmarks commands, analyzes bundles, profiles files/directories, and identifies monorepo bottlenecks.",
|
|
168
|
+
examples: [
|
|
169
|
+
"# Command Benchmarking:",
|
|
170
|
+
"dler perf build",
|
|
171
|
+
'dler perf "bun dler tsc"',
|
|
172
|
+
'dler perf "node --version"',
|
|
173
|
+
"dler perf build --runs 20 --warmup 5",
|
|
174
|
+
"dler perf build --concurrency 4 --verbose",
|
|
175
|
+
"dler perf build --compare --save",
|
|
176
|
+
"",
|
|
177
|
+
"# Bundle Analysis:",
|
|
178
|
+
"dler perf ./dist --type bundle",
|
|
179
|
+
"dler perf ./build --type bundle",
|
|
180
|
+
"dler perf ./dist --type bundle --verbose",
|
|
181
|
+
"",
|
|
182
|
+
"# File System Profiling:",
|
|
183
|
+
"dler perf ./src --type file",
|
|
184
|
+
"dler perf ./packages --type file",
|
|
185
|
+
"dler perf ./src --type file --verbose",
|
|
186
|
+
"",
|
|
187
|
+
"# Monorepo Analysis:",
|
|
188
|
+
"dler perf --type monorepo",
|
|
189
|
+
"dler perf --type monorepo --ignore '@reliverse/*'",
|
|
190
|
+
"dler perf --type monorepo --verbose",
|
|
191
|
+
"",
|
|
192
|
+
"# Auto-detection:",
|
|
193
|
+
"dler perf build # Auto-detects as command",
|
|
194
|
+
"dler perf ./dist # Auto-detects as bundle",
|
|
195
|
+
"dler perf ./src # Auto-detects as file system",
|
|
196
|
+
"dler perf # Auto-detects as monorepo",
|
|
197
|
+
"",
|
|
198
|
+
"# Output Formats:",
|
|
199
|
+
"dler perf build --output json",
|
|
200
|
+
"dler perf build --output html",
|
|
201
|
+
"dler perf build --output all",
|
|
202
|
+
"",
|
|
203
|
+
"# Baseline Comparison:",
|
|
204
|
+
"dler perf build --save # Save current as baseline",
|
|
205
|
+
"dler perf build --compare # Compare with baseline",
|
|
206
|
+
"dler perf build --compare --save # Compare and update baseline",
|
|
207
|
+
"",
|
|
208
|
+
"# Advanced Examples:",
|
|
209
|
+
"dler perf 'bun dler tsc' --runs 50 --warmup 10 --concurrency 2",
|
|
210
|
+
"dler perf ./dist --type bundle --output html --verbose",
|
|
211
|
+
"dler perf --type monorepo --ignore '@reliverse/*' --output json",
|
|
212
|
+
"dler perf build --runs 100 --compare --save --output all",
|
|
213
|
+
"",
|
|
214
|
+
"# Performance Monitoring:",
|
|
215
|
+
"dler perf build --runs 10 --save # Establish baseline",
|
|
216
|
+
"dler perf build --compare # Check for regressions",
|
|
217
|
+
"dler perf build --runs 20 --compare --save # Update baseline",
|
|
218
|
+
"",
|
|
219
|
+
"# Analysis Types:",
|
|
220
|
+
"dler perf --type command <cmd> # Benchmark command execution",
|
|
221
|
+
"dler perf --type bundle <path> # Analyze bundle size and structure",
|
|
222
|
+
"dler perf --type file <path> # Profile file system usage",
|
|
223
|
+
"dler perf --type monorepo # Analyze monorepo dependencies",
|
|
224
|
+
"dler perf --type auto <target> # Auto-detect analysis type",
|
|
225
|
+
"",
|
|
226
|
+
"# Command Examples:",
|
|
227
|
+
"dler perf 'dler build'",
|
|
228
|
+
"dler perf 'bun dler tsc --verbose'",
|
|
229
|
+
"dler perf 'node --version'",
|
|
230
|
+
"dler perf 'npm run build'",
|
|
231
|
+
"dler perf 'yarn test'",
|
|
232
|
+
"",
|
|
233
|
+
"# Bundle Analysis Examples:",
|
|
234
|
+
"dler perf ./dist --type bundle",
|
|
235
|
+
"dler perf ./build --type bundle",
|
|
236
|
+
"dler perf ./out --type bundle",
|
|
237
|
+
"dler perf ./lib --type bundle",
|
|
238
|
+
"",
|
|
239
|
+
"# File System Examples:",
|
|
240
|
+
"dler perf ./src --type file",
|
|
241
|
+
"dler perf ./packages --type file",
|
|
242
|
+
"dler perf ./apps --type file",
|
|
243
|
+
"dler perf ./examples --type file",
|
|
244
|
+
"",
|
|
245
|
+
"# Monorepo Examples:",
|
|
246
|
+
"dler perf --type monorepo",
|
|
247
|
+
"dler perf --type monorepo --cwd /path/to/monorepo",
|
|
248
|
+
"dler perf --type monorepo --ignore '@reliverse/*'",
|
|
249
|
+
"dler perf --type monorepo --ignore '@reliverse/* @company/*'",
|
|
250
|
+
"",
|
|
251
|
+
"# Output Examples:",
|
|
252
|
+
"dler perf build --output console # Console output (default)",
|
|
253
|
+
"dler perf build --output json # JSON output",
|
|
254
|
+
"dler perf build --output html # HTML report",
|
|
255
|
+
"dler perf build --output all # All formats",
|
|
256
|
+
"",
|
|
257
|
+
"# Baseline Management:",
|
|
258
|
+
"dler perf build --save # Save current run as baseline",
|
|
259
|
+
"dler perf build --compare # Compare with saved baseline",
|
|
260
|
+
"dler perf build --compare --save # Compare and update baseline",
|
|
261
|
+
"",
|
|
262
|
+
"# Verbose Output:",
|
|
263
|
+
"dler perf build --verbose # Show detailed progress",
|
|
264
|
+
"dler perf ./dist --type bundle --verbose",
|
|
265
|
+
"dler perf --type monorepo --verbose",
|
|
266
|
+
"",
|
|
267
|
+
"# Concurrency Control:",
|
|
268
|
+
"dler perf build --concurrency 1 # Sequential execution",
|
|
269
|
+
"dler perf build --concurrency 4 # Parallel execution",
|
|
270
|
+
"dler perf build --concurrency 8 # High parallelism",
|
|
271
|
+
"",
|
|
272
|
+
"# Iteration Control:",
|
|
273
|
+
"dler perf build --runs 5 # Few iterations (faster)",
|
|
274
|
+
"dler perf build --runs 50 # Many iterations (more accurate)",
|
|
275
|
+
"dler perf build --warmup 0 # No warmup",
|
|
276
|
+
"dler perf build --warmup 10 # More warmup runs",
|
|
277
|
+
"",
|
|
278
|
+
"# Working Directory:",
|
|
279
|
+
"dler perf build --cwd /path/to/project",
|
|
280
|
+
"dler perf ./dist --type bundle --cwd /path/to/project",
|
|
281
|
+
"dler perf --type monorepo --cwd /path/to/monorepo",
|
|
282
|
+
],
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
export default defineCmd(perfCmd, perfCmdArgs, perfCmdCfg);
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
// apps/dler/src/cmds/perf/impl.ts
|
|
2
|
+
|
|
3
|
+
import { existsSync, statSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { logger } from "@reliverse/dler-logger";
|
|
6
|
+
import { analyzeBundle } from "./analysis/bundle";
|
|
7
|
+
import { analyzeFileSystem } from "./analysis/filesystem";
|
|
8
|
+
import { analyzeMonorepo } from "./analysis/monorepo";
|
|
9
|
+
import { runBenchmark } from "./benchmarks/runner";
|
|
10
|
+
import type {
|
|
11
|
+
BenchmarkResult,
|
|
12
|
+
BundleAnalysisResult,
|
|
13
|
+
FileSystemAnalysisResult,
|
|
14
|
+
MonorepoAnalysisResult,
|
|
15
|
+
PerfOptions,
|
|
16
|
+
PerfReport,
|
|
17
|
+
} from "./types";
|
|
18
|
+
import { createPerfCache } from "./utils/cache";
|
|
19
|
+
import { calculateImprovement, calculateRegression } from "./utils/stats";
|
|
20
|
+
|
|
21
|
+
export interface PerfAnalysisResult {
|
|
22
|
+
report: PerfReport;
|
|
23
|
+
success: boolean;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class PerfAnalyzer {
|
|
28
|
+
private options: PerfOptions;
|
|
29
|
+
private cache = createPerfCache();
|
|
30
|
+
|
|
31
|
+
constructor(options: PerfOptions) {
|
|
32
|
+
this.options = options;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async analyze(): Promise<PerfAnalysisResult> {
|
|
36
|
+
try {
|
|
37
|
+
const startTime = Date.now();
|
|
38
|
+
|
|
39
|
+
// Auto-detect type if not specified
|
|
40
|
+
const analysisType = this.options.type ?? (await this.detectType());
|
|
41
|
+
|
|
42
|
+
if (this.options.verbose) {
|
|
43
|
+
logger.info(`🔍 Starting performance analysis (type: ${analysisType})`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Run analysis based on type
|
|
47
|
+
let benchmark: BenchmarkResult | undefined;
|
|
48
|
+
let bundleAnalysis: BundleAnalysisResult | undefined;
|
|
49
|
+
let fileSystemAnalysis: FileSystemAnalysisResult | undefined;
|
|
50
|
+
let monorepoAnalysis: MonorepoAnalysisResult | undefined;
|
|
51
|
+
|
|
52
|
+
switch (analysisType) {
|
|
53
|
+
case "command":
|
|
54
|
+
benchmark = await this.runCommandBenchmark();
|
|
55
|
+
break;
|
|
56
|
+
case "bundle":
|
|
57
|
+
bundleAnalysis = await this.runBundleAnalysis();
|
|
58
|
+
break;
|
|
59
|
+
case "file":
|
|
60
|
+
fileSystemAnalysis = await this.runFileSystemAnalysis();
|
|
61
|
+
break;
|
|
62
|
+
case "monorepo":
|
|
63
|
+
monorepoAnalysis = await this.runMonorepoAnalysis();
|
|
64
|
+
break;
|
|
65
|
+
case "auto":
|
|
66
|
+
// Run all analyses
|
|
67
|
+
if (this.options.target) {
|
|
68
|
+
const targetType = await this.detectTargetType(this.options.target);
|
|
69
|
+
switch (targetType) {
|
|
70
|
+
case "command":
|
|
71
|
+
benchmark = await this.runCommandBenchmark();
|
|
72
|
+
break;
|
|
73
|
+
case "bundle":
|
|
74
|
+
bundleAnalysis = await this.runBundleAnalysis();
|
|
75
|
+
break;
|
|
76
|
+
case "file":
|
|
77
|
+
fileSystemAnalysis = await this.runFileSystemAnalysis();
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
monorepoAnalysis = await this.runMonorepoAnalysis();
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Create report
|
|
87
|
+
const report: PerfReport = {
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
options: this.options,
|
|
90
|
+
benchmark,
|
|
91
|
+
bundleAnalysis,
|
|
92
|
+
fileSystemAnalysis,
|
|
93
|
+
monorepoAnalysis,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Compare with baseline if requested
|
|
97
|
+
if (this.options.compare) {
|
|
98
|
+
const baseline = await this.cache.findBaseline(
|
|
99
|
+
this.options.target ?? "unknown",
|
|
100
|
+
this.options.type,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (baseline) {
|
|
104
|
+
report.baseline = this.compareWithBaseline(report, baseline);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Save baseline if requested
|
|
109
|
+
if (this.options.save) {
|
|
110
|
+
await this.cache.save(report);
|
|
111
|
+
if (this.options.verbose) {
|
|
112
|
+
logger.info("💾 Baseline saved");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const executionTime = Date.now() - startTime;
|
|
117
|
+
|
|
118
|
+
if (this.options.verbose) {
|
|
119
|
+
logger.info(`✅ Analysis completed in ${executionTime}ms`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
report,
|
|
124
|
+
success: true,
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const errorMessage =
|
|
128
|
+
error instanceof Error ? error.message : String(error);
|
|
129
|
+
|
|
130
|
+
if (this.options.verbose) {
|
|
131
|
+
logger.error(`❌ Analysis failed: ${errorMessage}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
report: {
|
|
136
|
+
timestamp: Date.now(),
|
|
137
|
+
options: this.options,
|
|
138
|
+
},
|
|
139
|
+
success: false,
|
|
140
|
+
error: errorMessage,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async detectType(): Promise<PerfOptions["type"]> {
|
|
146
|
+
if (this.options.target) {
|
|
147
|
+
return await this.detectTargetType(this.options.target);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Default to monorepo analysis if no target specified
|
|
151
|
+
return "monorepo";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private async detectTargetType(target: string): Promise<PerfOptions["type"]> {
|
|
155
|
+
// Check if it's a command
|
|
156
|
+
if (this.isCommand(target)) {
|
|
157
|
+
return "command";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check if target exists as file/directory
|
|
161
|
+
const targetPath = resolve(target);
|
|
162
|
+
if (!existsSync(targetPath)) {
|
|
163
|
+
throw new Error(`Target not found: ${target}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const stat = statSync(targetPath);
|
|
167
|
+
|
|
168
|
+
if (stat.isFile()) {
|
|
169
|
+
// Check if it's a bundle file
|
|
170
|
+
if (this.isBundleFile(target)) {
|
|
171
|
+
return "bundle";
|
|
172
|
+
}
|
|
173
|
+
return "file";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (stat.isDirectory()) {
|
|
177
|
+
// Check if it contains bundle files
|
|
178
|
+
if (await this.containsBundleFiles(targetPath)) {
|
|
179
|
+
return "bundle";
|
|
180
|
+
}
|
|
181
|
+
return "file";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return "file";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private isCommand(target: string): boolean {
|
|
188
|
+
// Simple heuristic: if it contains spaces or starts with common command prefixes
|
|
189
|
+
return (
|
|
190
|
+
target.includes(" ") ||
|
|
191
|
+
target.startsWith("dler ") ||
|
|
192
|
+
target.startsWith("bun ") ||
|
|
193
|
+
target.startsWith("node ") ||
|
|
194
|
+
target.startsWith("npm ") ||
|
|
195
|
+
target.startsWith("yarn ") ||
|
|
196
|
+
target.startsWith("pnpm ")
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private isBundleFile(target: string): boolean {
|
|
201
|
+
const bundleExtensions = [".js", ".mjs", ".cjs", ".ts", ".jsx", ".tsx"];
|
|
202
|
+
const ext = target.split(".").pop()?.toLowerCase();
|
|
203
|
+
return ext ? bundleExtensions.includes(`.${ext}`) : false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async containsBundleFiles(dirPath: string): Promise<boolean> {
|
|
207
|
+
try {
|
|
208
|
+
const glob = new Bun.Glob("**/*.{js,mjs,cjs,ts,jsx,tsx}");
|
|
209
|
+
const matches = glob.scanSync({ cwd: dirPath, onlyFiles: true });
|
|
210
|
+
// Check if there's at least one match
|
|
211
|
+
for (const _ of matches) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private async runCommandBenchmark(): Promise<BenchmarkResult> {
|
|
221
|
+
if (!this.options.target) {
|
|
222
|
+
throw new Error("Target is required for command benchmarking");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const {
|
|
226
|
+
target,
|
|
227
|
+
runs = 10,
|
|
228
|
+
warmup = 2,
|
|
229
|
+
concurrency = 1,
|
|
230
|
+
cwd,
|
|
231
|
+
verbose,
|
|
232
|
+
} = this.options;
|
|
233
|
+
|
|
234
|
+
if (verbose) {
|
|
235
|
+
logger.info(`🚀 Benchmarking command: ${target}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return runBenchmark({
|
|
239
|
+
command: target,
|
|
240
|
+
runs,
|
|
241
|
+
warmup,
|
|
242
|
+
concurrency,
|
|
243
|
+
cwd,
|
|
244
|
+
verbose,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private async runBundleAnalysis(): Promise<BundleAnalysisResult> {
|
|
249
|
+
if (!this.options.target) {
|
|
250
|
+
throw new Error("Target is required for bundle analysis");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const { target, verbose } = this.options;
|
|
254
|
+
|
|
255
|
+
if (verbose) {
|
|
256
|
+
logger.info(`📦 Analyzing bundle: ${target}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return analyzeBundle({
|
|
260
|
+
target,
|
|
261
|
+
verbose,
|
|
262
|
+
includeSourceMaps: true,
|
|
263
|
+
analyzeDependencies: true,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private async runFileSystemAnalysis(): Promise<FileSystemAnalysisResult> {
|
|
268
|
+
if (!this.options.target) {
|
|
269
|
+
throw new Error("Target is required for file system analysis");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const { target, verbose } = this.options;
|
|
273
|
+
|
|
274
|
+
if (verbose) {
|
|
275
|
+
logger.info(`📁 Analyzing file system: ${target}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return analyzeFileSystem({
|
|
279
|
+
target,
|
|
280
|
+
verbose,
|
|
281
|
+
maxDepth: 10,
|
|
282
|
+
includeHidden: false,
|
|
283
|
+
excludePatterns: [
|
|
284
|
+
"node_modules",
|
|
285
|
+
".git",
|
|
286
|
+
".next",
|
|
287
|
+
".nuxt",
|
|
288
|
+
"dist",
|
|
289
|
+
"build",
|
|
290
|
+
],
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private async runMonorepoAnalysis(): Promise<MonorepoAnalysisResult> {
|
|
295
|
+
const { cwd, ignore, verbose } = this.options;
|
|
296
|
+
|
|
297
|
+
if (verbose) {
|
|
298
|
+
logger.info("🏗️ Analyzing monorepo structure");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return analyzeMonorepo({
|
|
302
|
+
cwd,
|
|
303
|
+
ignore,
|
|
304
|
+
verbose,
|
|
305
|
+
includeDevDependencies: true,
|
|
306
|
+
analyzeBuildOrder: true,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private compareWithBaseline(
|
|
311
|
+
current: PerfReport,
|
|
312
|
+
baseline: PerfReport,
|
|
313
|
+
): PerfReport["baseline"] {
|
|
314
|
+
const changes: NonNullable<PerfReport["baseline"]>["changes"] = {};
|
|
315
|
+
|
|
316
|
+
// Compare benchmark results
|
|
317
|
+
if (current.benchmark && baseline.benchmark) {
|
|
318
|
+
const currentDuration = current.benchmark.statistics.mean;
|
|
319
|
+
const baselineDuration = baseline.benchmark.statistics.mean;
|
|
320
|
+
|
|
321
|
+
if (currentDuration < baselineDuration) {
|
|
322
|
+
changes.duration = calculateImprovement(
|
|
323
|
+
baselineDuration,
|
|
324
|
+
currentDuration,
|
|
325
|
+
);
|
|
326
|
+
} else {
|
|
327
|
+
changes.duration = calculateRegression(
|
|
328
|
+
baselineDuration,
|
|
329
|
+
currentDuration,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const currentMemory = current.benchmark.memory.average.rss;
|
|
334
|
+
const baselineMemory = baseline.benchmark.memory.average.rss;
|
|
335
|
+
|
|
336
|
+
if (currentMemory < baselineMemory) {
|
|
337
|
+
changes.memory = calculateImprovement(baselineMemory, currentMemory);
|
|
338
|
+
} else {
|
|
339
|
+
changes.memory = calculateRegression(baselineMemory, currentMemory);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Compare bundle analysis
|
|
344
|
+
if (current.bundleAnalysis && baseline.bundleAnalysis) {
|
|
345
|
+
const currentSize = current.bundleAnalysis.totalSize;
|
|
346
|
+
const baselineSize = baseline.bundleAnalysis.totalSize;
|
|
347
|
+
|
|
348
|
+
if (currentSize < baselineSize) {
|
|
349
|
+
changes.size = calculateImprovement(baselineSize, currentSize);
|
|
350
|
+
} else {
|
|
351
|
+
changes.size = calculateRegression(baselineSize, currentSize);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const currentFiles = current.bundleAnalysis.fileCount;
|
|
355
|
+
const baselineFiles = baseline.bundleAnalysis.fileCount;
|
|
356
|
+
|
|
357
|
+
if (currentFiles !== baselineFiles) {
|
|
358
|
+
changes.files = calculateRegression(baselineFiles, currentFiles);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Compare file system analysis
|
|
363
|
+
if (current.fileSystemAnalysis && baseline.fileSystemAnalysis) {
|
|
364
|
+
const currentSize = current.fileSystemAnalysis.totalSize;
|
|
365
|
+
const baselineSize = baseline.fileSystemAnalysis.totalSize;
|
|
366
|
+
|
|
367
|
+
if (currentSize < baselineSize) {
|
|
368
|
+
changes.size = calculateImprovement(baselineSize, currentSize);
|
|
369
|
+
} else {
|
|
370
|
+
changes.size = calculateRegression(baselineSize, currentSize);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const currentFiles = current.fileSystemAnalysis.totalFiles;
|
|
374
|
+
const baselineFiles = baseline.fileSystemAnalysis.totalFiles;
|
|
375
|
+
|
|
376
|
+
if (currentFiles !== baselineFiles) {
|
|
377
|
+
changes.files = calculateRegression(baselineFiles, currentFiles);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Calculate overall improvement/regression
|
|
382
|
+
let improvement: number | undefined;
|
|
383
|
+
let regression: number | undefined;
|
|
384
|
+
|
|
385
|
+
if (changes.duration !== undefined) {
|
|
386
|
+
if (changes.duration > 0) {
|
|
387
|
+
improvement = changes.duration;
|
|
388
|
+
} else {
|
|
389
|
+
regression = Math.abs(changes.duration);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
exists: true,
|
|
395
|
+
improvement,
|
|
396
|
+
regression,
|
|
397
|
+
changes,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export const runPerfAnalysis = async (
|
|
403
|
+
options: PerfOptions,
|
|
404
|
+
): Promise<PerfAnalysisResult> => {
|
|
405
|
+
const analyzer = new PerfAnalyzer(options);
|
|
406
|
+
return analyzer.analyze();
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export const createPerfAnalyzer = (options: PerfOptions): PerfAnalyzer => {
|
|
410
|
+
return new PerfAnalyzer(options);
|
|
411
|
+
};
|