@reliverse/dler 2.0.6 → 2.0.7
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 -12
- 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,439 +0,0 @@
|
|
|
1
|
-
// apps/dler/src/cmds/perf/analysis/monorepo.ts
|
|
2
|
-
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
5
|
-
import { logger } from "@reliverse/dler-logger";
|
|
6
|
-
import { createIgnoreFilter } from "@reliverse/dler-matcher";
|
|
7
|
-
import {
|
|
8
|
-
getWorkspacePatterns,
|
|
9
|
-
hasWorkspaces,
|
|
10
|
-
readPackageJSON,
|
|
11
|
-
} from "@reliverse/dler-pkg-tsc";
|
|
12
|
-
import type {
|
|
13
|
-
Bottleneck,
|
|
14
|
-
CircularDependency,
|
|
15
|
-
DependencyEdge,
|
|
16
|
-
DependencyGraph,
|
|
17
|
-
MonorepoAnalysisResult,
|
|
18
|
-
PackageInfo,
|
|
19
|
-
} from "../types";
|
|
20
|
-
|
|
21
|
-
export interface MonorepoAnalysisOptions {
|
|
22
|
-
cwd?: string;
|
|
23
|
-
ignore?: string | string[];
|
|
24
|
-
verbose?: boolean;
|
|
25
|
-
includeDevDependencies?: boolean;
|
|
26
|
-
analyzeBuildOrder?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class MonorepoAnalyzer {
|
|
30
|
-
private options: MonorepoAnalysisOptions;
|
|
31
|
-
private packages: PackageInfo[] = [];
|
|
32
|
-
private dependencyGraph: DependencyGraph = {
|
|
33
|
-
nodes: [],
|
|
34
|
-
edges: [],
|
|
35
|
-
levels: [],
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
constructor(options: MonorepoAnalysisOptions) {
|
|
39
|
-
this.options = options;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async analyze(): Promise<MonorepoAnalysisResult> {
|
|
43
|
-
const startTime = Date.now();
|
|
44
|
-
const { cwd, verbose } = this.options;
|
|
45
|
-
|
|
46
|
-
if (verbose) {
|
|
47
|
-
logger.info("🔍 Analyzing monorepo structure...");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Find monorepo root
|
|
51
|
-
const monorepoRoot = await this.findMonorepoRoot(cwd);
|
|
52
|
-
if (!monorepoRoot) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
'No monorepo found. Ensure package.json has "workspaces" field.',
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (verbose) {
|
|
59
|
-
logger.info(` Monorepo root: ${monorepoRoot}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Discover packages
|
|
63
|
-
this.packages = await this.discoverPackages(monorepoRoot);
|
|
64
|
-
|
|
65
|
-
if (verbose) {
|
|
66
|
-
logger.info(` Found ${this.packages.length} packages`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Build dependency graph
|
|
70
|
-
this.dependencyGraph = await this.buildDependencyGraph();
|
|
71
|
-
|
|
72
|
-
// Analyze circular dependencies
|
|
73
|
-
const circularDependencies = this.findCircularDependencies();
|
|
74
|
-
|
|
75
|
-
// Calculate build order
|
|
76
|
-
const buildOrder = this.calculateBuildOrder();
|
|
77
|
-
|
|
78
|
-
// Find critical path
|
|
79
|
-
const criticalPath = this.findCriticalPath();
|
|
80
|
-
|
|
81
|
-
// Identify bottlenecks
|
|
82
|
-
const bottlenecks = this.identifyBottlenecks();
|
|
83
|
-
|
|
84
|
-
// Suggest optimal concurrency
|
|
85
|
-
const suggestedConcurrency = this.suggestOptimalConcurrency();
|
|
86
|
-
|
|
87
|
-
const analysisTime = Date.now() - startTime;
|
|
88
|
-
|
|
89
|
-
if (verbose) {
|
|
90
|
-
logger.info(` Analysis completed in ${analysisTime}ms`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
packages: this.packages,
|
|
95
|
-
dependencies: this.dependencyGraph,
|
|
96
|
-
circularDependencies,
|
|
97
|
-
criticalPath,
|
|
98
|
-
buildOrder,
|
|
99
|
-
bottlenecks,
|
|
100
|
-
suggestedConcurrency,
|
|
101
|
-
analysisTime,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private async findMonorepoRoot(startDir?: string): Promise<string | null> {
|
|
106
|
-
let currentDir = resolve(startDir ?? process.cwd());
|
|
107
|
-
|
|
108
|
-
while (currentDir !== "/") {
|
|
109
|
-
const pkgPath = join(currentDir, "package.json");
|
|
110
|
-
|
|
111
|
-
if (existsSync(pkgPath)) {
|
|
112
|
-
const pkg = await readPackageJSON(currentDir);
|
|
113
|
-
if (pkg && hasWorkspaces(pkg)) {
|
|
114
|
-
return currentDir;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const parentDir = resolve(currentDir, "..");
|
|
119
|
-
if (parentDir === currentDir) break;
|
|
120
|
-
currentDir = parentDir;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private async discoverPackages(monorepoRoot: string): Promise<PackageInfo[]> {
|
|
127
|
-
const rootPkg = await readPackageJSON(monorepoRoot);
|
|
128
|
-
if (!rootPkg) {
|
|
129
|
-
throw new Error("Could not read root package.json");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const patterns = getWorkspacePatterns(rootPkg);
|
|
133
|
-
if (!patterns.length) {
|
|
134
|
-
throw new Error("No workspace patterns found in package.json");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const packages: PackageInfo[] = [];
|
|
138
|
-
const seenPaths = new Set<string>();
|
|
139
|
-
|
|
140
|
-
for (const pattern of patterns) {
|
|
141
|
-
const glob = new Bun.Glob(pattern);
|
|
142
|
-
const matches = glob.scanSync({ cwd: monorepoRoot, onlyFiles: false });
|
|
143
|
-
|
|
144
|
-
for (const match of matches) {
|
|
145
|
-
const packagePath = resolve(monorepoRoot, match);
|
|
146
|
-
if (seenPaths.has(packagePath)) continue;
|
|
147
|
-
seenPaths.add(packagePath);
|
|
148
|
-
|
|
149
|
-
const pkgInfo = await this.resolvePackageInfo(packagePath);
|
|
150
|
-
if (pkgInfo) {
|
|
151
|
-
packages.push(pkgInfo);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Apply ignore filters
|
|
157
|
-
if (this.options.ignore) {
|
|
158
|
-
const ignoreFilter = createIgnoreFilter(this.options.ignore);
|
|
159
|
-
return ignoreFilter(packages);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return packages;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private async resolvePackageInfo(
|
|
166
|
-
packagePath: string,
|
|
167
|
-
): Promise<PackageInfo | null> {
|
|
168
|
-
const pkgJsonPath = join(packagePath, "package.json");
|
|
169
|
-
if (!existsSync(pkgJsonPath)) return null;
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
const pkg = await readPackageJSON(packagePath);
|
|
173
|
-
if (!pkg?.name) return null;
|
|
174
|
-
|
|
175
|
-
const dependencies = [
|
|
176
|
-
...Object.keys(pkg.dependencies ?? {}),
|
|
177
|
-
...(this.options.includeDevDependencies
|
|
178
|
-
? Object.keys(pkg.devDependencies ?? {})
|
|
179
|
-
: []),
|
|
180
|
-
...Object.keys(pkg.peerDependencies ?? {}),
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
name: pkg.name,
|
|
185
|
-
path: packagePath,
|
|
186
|
-
dependencies,
|
|
187
|
-
dependents: [], // Will be filled later
|
|
188
|
-
buildTime: 0, // Would need to measure actual build time
|
|
189
|
-
size: 0, // Would need to calculate package size
|
|
190
|
-
};
|
|
191
|
-
} catch {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private async buildDependencyGraph(): Promise<DependencyGraph> {
|
|
197
|
-
const nodes = this.packages.map((pkg) => pkg.name);
|
|
198
|
-
const edges: DependencyEdge[] = [];
|
|
199
|
-
|
|
200
|
-
// Build dependency edges
|
|
201
|
-
for (const pkg of this.packages) {
|
|
202
|
-
for (const dep of pkg.dependencies) {
|
|
203
|
-
// Check if dependency is within the monorepo
|
|
204
|
-
const depPkg = this.packages.find((p) => p.name === dep);
|
|
205
|
-
if (depPkg) {
|
|
206
|
-
edges.push({
|
|
207
|
-
from: pkg.name,
|
|
208
|
-
to: dep,
|
|
209
|
-
type: "dependency",
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// Add to dependents
|
|
213
|
-
depPkg.dependents.push(pkg.name);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Calculate levels (topological sort)
|
|
219
|
-
const levels = this.calculateLevels(nodes, edges);
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
nodes,
|
|
223
|
-
edges,
|
|
224
|
-
levels,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private calculateLevels(
|
|
229
|
-
nodes: string[],
|
|
230
|
-
edges: DependencyEdge[],
|
|
231
|
-
): string[][] {
|
|
232
|
-
const inDegree = new Map<string, number>();
|
|
233
|
-
const graph = new Map<string, string[]>();
|
|
234
|
-
|
|
235
|
-
// Initialize
|
|
236
|
-
for (const node of nodes) {
|
|
237
|
-
inDegree.set(node, 0);
|
|
238
|
-
graph.set(node, []);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Build graph and calculate in-degrees
|
|
242
|
-
for (const edge of edges) {
|
|
243
|
-
const current = inDegree.get(edge.to) ?? 0;
|
|
244
|
-
inDegree.set(edge.to, current + 1);
|
|
245
|
-
|
|
246
|
-
const neighbors = graph.get(edge.from) ?? [];
|
|
247
|
-
neighbors.push(edge.to);
|
|
248
|
-
graph.set(edge.from, neighbors);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Topological sort
|
|
252
|
-
const levels: string[][] = [];
|
|
253
|
-
const queue: string[] = [];
|
|
254
|
-
|
|
255
|
-
// Start with nodes that have no dependencies
|
|
256
|
-
for (const [node, degree] of inDegree) {
|
|
257
|
-
if (degree === 0) {
|
|
258
|
-
queue.push(node);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
while (queue.length > 0) {
|
|
263
|
-
const currentLevel: string[] = [];
|
|
264
|
-
const nextQueue: string[] = [];
|
|
265
|
-
|
|
266
|
-
for (const node of queue) {
|
|
267
|
-
currentLevel.push(node);
|
|
268
|
-
|
|
269
|
-
// Process neighbors
|
|
270
|
-
const neighbors = graph.get(node) ?? [];
|
|
271
|
-
for (const neighbor of neighbors) {
|
|
272
|
-
const degree = inDegree.get(neighbor) ?? 0;
|
|
273
|
-
inDegree.set(neighbor, degree - 1);
|
|
274
|
-
|
|
275
|
-
if (degree - 1 === 0) {
|
|
276
|
-
nextQueue.push(neighbor);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
levels.push(currentLevel);
|
|
282
|
-
queue.length = 0;
|
|
283
|
-
queue.push(...nextQueue);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return levels;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
private findCircularDependencies(): CircularDependency[] {
|
|
290
|
-
const circular: CircularDependency[] = [];
|
|
291
|
-
const visited = new Set<string>();
|
|
292
|
-
const recursionStack = new Set<string>();
|
|
293
|
-
|
|
294
|
-
for (const pkg of this.packages) {
|
|
295
|
-
if (!visited.has(pkg.name)) {
|
|
296
|
-
const cycle = this.detectCycle(pkg.name, visited, recursionStack, []);
|
|
297
|
-
if (cycle.length > 0) {
|
|
298
|
-
circular.push({
|
|
299
|
-
packages: cycle,
|
|
300
|
-
cycle,
|
|
301
|
-
severity: this.calculateCycleSeverity(cycle),
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return circular;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
private detectCycle(
|
|
311
|
-
node: string,
|
|
312
|
-
visited: Set<string>,
|
|
313
|
-
recursionStack: Set<string>,
|
|
314
|
-
path: string[],
|
|
315
|
-
): string[] {
|
|
316
|
-
visited.add(node);
|
|
317
|
-
recursionStack.add(node);
|
|
318
|
-
path.push(node);
|
|
319
|
-
|
|
320
|
-
const pkg = this.packages.find((p) => p.name === node);
|
|
321
|
-
if (pkg) {
|
|
322
|
-
for (const dep of pkg.dependencies) {
|
|
323
|
-
const depPkg = this.packages.find((p) => p.name === dep);
|
|
324
|
-
if (depPkg) {
|
|
325
|
-
if (!visited.has(dep)) {
|
|
326
|
-
const cycle = this.detectCycle(dep, visited, recursionStack, [
|
|
327
|
-
...path,
|
|
328
|
-
]);
|
|
329
|
-
if (cycle.length > 0) {
|
|
330
|
-
return cycle;
|
|
331
|
-
}
|
|
332
|
-
} else if (recursionStack.has(dep)) {
|
|
333
|
-
// Found a cycle
|
|
334
|
-
const cycleStart = path.indexOf(dep);
|
|
335
|
-
return path.slice(cycleStart);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
recursionStack.delete(node);
|
|
342
|
-
return [];
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private calculateCycleSeverity(cycle: string[]): "low" | "medium" | "high" {
|
|
346
|
-
if (cycle.length <= 2) return "low";
|
|
347
|
-
if (cycle.length <= 4) return "medium";
|
|
348
|
-
return "high";
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
private calculateBuildOrder(): string[] {
|
|
352
|
-
const order: string[] = [];
|
|
353
|
-
|
|
354
|
-
for (const level of this.dependencyGraph.levels) {
|
|
355
|
-
order.push(...level);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return order;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
private findCriticalPath(): string[] {
|
|
362
|
-
// Simple heuristic: packages with the most dependents
|
|
363
|
-
const dependentCounts = new Map<string, number>();
|
|
364
|
-
|
|
365
|
-
for (const pkg of this.packages) {
|
|
366
|
-
dependentCounts.set(pkg.name, pkg.dependents.length);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return Array.from(dependentCounts.entries())
|
|
370
|
-
.sort(([, a], [, b]) => b - a)
|
|
371
|
-
.map(([name]) => name);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
private identifyBottlenecks(): Bottleneck[] {
|
|
375
|
-
const bottlenecks: Bottleneck[] = [];
|
|
376
|
-
|
|
377
|
-
// Find packages with many dependencies
|
|
378
|
-
for (const pkg of this.packages) {
|
|
379
|
-
if (pkg.dependencies.length > 10) {
|
|
380
|
-
bottlenecks.push({
|
|
381
|
-
package: pkg.name,
|
|
382
|
-
type: "many-dependencies",
|
|
383
|
-
impact: pkg.dependencies.length,
|
|
384
|
-
suggestion: `Consider splitting ${pkg.name} - it has ${pkg.dependencies.length} dependencies`,
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Find circular dependencies
|
|
390
|
-
const circularDeps = this.findCircularDependencies();
|
|
391
|
-
for (const circular of circularDeps) {
|
|
392
|
-
bottlenecks.push({
|
|
393
|
-
package: circular.packages[0]!,
|
|
394
|
-
type: "circular-dependency",
|
|
395
|
-
impact: circular.packages.length,
|
|
396
|
-
suggestion: `Resolve circular dependency: ${circular.cycle.join(" → ")}`,
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Find slow packages (heuristic based on dependents)
|
|
401
|
-
for (const pkg of this.packages) {
|
|
402
|
-
if (pkg.dependents.length > 5) {
|
|
403
|
-
bottlenecks.push({
|
|
404
|
-
package: pkg.name,
|
|
405
|
-
type: "slow-build",
|
|
406
|
-
impact: pkg.dependents.length,
|
|
407
|
-
suggestion: `Optimize ${pkg.name} - it blocks ${pkg.dependents.length} other packages`,
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return bottlenecks.sort((a, b) => b.impact - a.impact);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
private suggestOptimalConcurrency(): number {
|
|
416
|
-
// Simple heuristic based on dependency levels
|
|
417
|
-
const maxLevel = this.dependencyGraph.levels.length;
|
|
418
|
-
const avgLevelSize = this.packages.length / maxLevel;
|
|
419
|
-
|
|
420
|
-
// Suggest concurrency based on level size and CPU cores
|
|
421
|
-
const cpuCores = require("node:os").cpus().length;
|
|
422
|
-
const suggested = Math.min(Math.ceil(avgLevelSize), cpuCores);
|
|
423
|
-
|
|
424
|
-
return Math.max(1, suggested);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
export const analyzeMonorepo = async (
|
|
429
|
-
options: MonorepoAnalysisOptions,
|
|
430
|
-
): Promise<MonorepoAnalysisResult> => {
|
|
431
|
-
const analyzer = new MonorepoAnalyzer(options);
|
|
432
|
-
return analyzer.analyze();
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
export const createMonorepoAnalyzer = (
|
|
436
|
-
options: MonorepoAnalysisOptions,
|
|
437
|
-
): MonorepoAnalyzer => {
|
|
438
|
-
return new MonorepoAnalyzer(options);
|
|
439
|
-
};
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// apps/dler/src/cmds/perf/benchmarks/command.ts
|
|
2
|
-
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
import { lookpath } from "lookpath";
|
|
6
|
-
import type { Measurement, MemoryUsage } from "../types";
|
|
7
|
-
|
|
8
|
-
export interface CommandExecutionOptions {
|
|
9
|
-
cwd?: string;
|
|
10
|
-
timeout?: number;
|
|
11
|
-
env?: Record<string, string>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface CommandResult {
|
|
15
|
-
success: boolean;
|
|
16
|
-
duration: number;
|
|
17
|
-
memory: MemoryUsage;
|
|
18
|
-
stdout: string;
|
|
19
|
-
stderr: string;
|
|
20
|
-
error?: string;
|
|
21
|
-
exitCode: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const executeCommand = async (
|
|
25
|
-
command: string,
|
|
26
|
-
options: CommandExecutionOptions = {},
|
|
27
|
-
): Promise<CommandResult> => {
|
|
28
|
-
const startTime = process.hrtime.bigint();
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
// Parse command and arguments
|
|
32
|
-
const [cmd, ...args] = parseCommand(command);
|
|
33
|
-
|
|
34
|
-
// Check if command exists
|
|
35
|
-
if (!cmd) {
|
|
36
|
-
throw new Error("Command is empty");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const commandPath = await findCommand(cmd);
|
|
40
|
-
if (!commandPath) {
|
|
41
|
-
throw new Error(`Command not found: ${cmd}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Execute command
|
|
45
|
-
const proc = Bun.spawn([commandPath, ...args], {
|
|
46
|
-
cwd: options.cwd ?? process.cwd(),
|
|
47
|
-
env: { ...process.env, ...options.env },
|
|
48
|
-
stdout: "pipe",
|
|
49
|
-
stderr: "pipe",
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Set timeout if specified
|
|
53
|
-
let timeoutId: Timer | undefined;
|
|
54
|
-
if (options.timeout) {
|
|
55
|
-
timeoutId = setTimeout(() => {
|
|
56
|
-
proc.kill();
|
|
57
|
-
}, options.timeout);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Wait for completion
|
|
61
|
-
const [stdout, stderr] = await Promise.all([
|
|
62
|
-
new Response(proc.stdout).text(),
|
|
63
|
-
new Response(proc.stderr).text(),
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
const exitCode = await proc.exited;
|
|
67
|
-
const endTime = process.hrtime.bigint();
|
|
68
|
-
const endMemory = process.memoryUsage();
|
|
69
|
-
|
|
70
|
-
// Clear timeout
|
|
71
|
-
if (timeoutId) {
|
|
72
|
-
clearTimeout(timeoutId);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const duration = Number(endTime - startTime) / 1_000_000; // Convert to milliseconds
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
success: exitCode === 0,
|
|
79
|
-
duration,
|
|
80
|
-
memory: {
|
|
81
|
-
rss: endMemory.rss,
|
|
82
|
-
heapTotal: endMemory.heapTotal,
|
|
83
|
-
heapUsed: endMemory.heapUsed,
|
|
84
|
-
external: endMemory.external,
|
|
85
|
-
arrayBuffers: endMemory.arrayBuffers,
|
|
86
|
-
},
|
|
87
|
-
stdout,
|
|
88
|
-
stderr,
|
|
89
|
-
exitCode,
|
|
90
|
-
};
|
|
91
|
-
} catch (error) {
|
|
92
|
-
const endTime = process.hrtime.bigint();
|
|
93
|
-
const endMemory = process.memoryUsage();
|
|
94
|
-
const duration = Number(endTime - startTime) / 1_000_000;
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
success: false,
|
|
98
|
-
duration,
|
|
99
|
-
memory: {
|
|
100
|
-
rss: endMemory.rss,
|
|
101
|
-
heapTotal: endMemory.heapTotal,
|
|
102
|
-
heapUsed: endMemory.heapUsed,
|
|
103
|
-
external: endMemory.external,
|
|
104
|
-
arrayBuffers: endMemory.arrayBuffers,
|
|
105
|
-
},
|
|
106
|
-
stdout: "",
|
|
107
|
-
stderr: "",
|
|
108
|
-
error: error instanceof Error ? error.message : String(error),
|
|
109
|
-
exitCode: 1,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
export const executeCommandWithMemoryTracking = async (
|
|
115
|
-
command: string,
|
|
116
|
-
options: CommandExecutionOptions = {},
|
|
117
|
-
): Promise<Measurement> => {
|
|
118
|
-
const result = await executeCommand(command, options);
|
|
119
|
-
const endMemory = process.memoryUsage();
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
run: 0, // Will be set by the runner
|
|
123
|
-
duration: result.duration,
|
|
124
|
-
memory: {
|
|
125
|
-
rss: endMemory.rss,
|
|
126
|
-
heapTotal: endMemory.heapTotal,
|
|
127
|
-
heapUsed: endMemory.heapUsed,
|
|
128
|
-
external: endMemory.external,
|
|
129
|
-
arrayBuffers: endMemory.arrayBuffers,
|
|
130
|
-
},
|
|
131
|
-
success: result.success,
|
|
132
|
-
error: result.error,
|
|
133
|
-
stdout: result.stdout,
|
|
134
|
-
stderr: result.stderr,
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const parseCommand = (command: string): string[] => {
|
|
139
|
-
// Simple command parsing - handles basic cases
|
|
140
|
-
// For more complex cases, consider using a proper shell parser
|
|
141
|
-
const parts: string[] = [];
|
|
142
|
-
let current = "";
|
|
143
|
-
let inQuotes = false;
|
|
144
|
-
let quoteChar = "";
|
|
145
|
-
|
|
146
|
-
for (let i = 0; i < command.length; i++) {
|
|
147
|
-
const char = command[i]!;
|
|
148
|
-
|
|
149
|
-
if (char === '"' || char === "'") {
|
|
150
|
-
if (!inQuotes) {
|
|
151
|
-
inQuotes = true;
|
|
152
|
-
quoteChar = char;
|
|
153
|
-
} else if (char === quoteChar) {
|
|
154
|
-
inQuotes = false;
|
|
155
|
-
quoteChar = "";
|
|
156
|
-
} else {
|
|
157
|
-
current += char;
|
|
158
|
-
}
|
|
159
|
-
} else if (char === " " && !inQuotes) {
|
|
160
|
-
if (current.trim()) {
|
|
161
|
-
parts.push(current.trim());
|
|
162
|
-
current = "";
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
current += char;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (current.trim()) {
|
|
170
|
-
parts.push(current.trim());
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return parts;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const findCommand = async (cmd: string): Promise<string | null> => {
|
|
177
|
-
if (!cmd) {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Check if it's a dler command first
|
|
182
|
-
if (cmd === "dler") {
|
|
183
|
-
return "bun";
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Check if it's a Bun command
|
|
187
|
-
if (cmd === "bun") {
|
|
188
|
-
return "bun";
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Check if it's a Node.js command
|
|
192
|
-
if (cmd === "node") {
|
|
193
|
-
return "node";
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Check if it's a direct executable
|
|
197
|
-
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
198
|
-
const fullPath = resolve(cmd);
|
|
199
|
-
return existsSync(fullPath) ? fullPath : null;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Use lookpath to find the command
|
|
203
|
-
try {
|
|
204
|
-
const path = await lookpath(cmd);
|
|
205
|
-
return path ?? null;
|
|
206
|
-
} catch {
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
export const isDlerCommand = (command: string): boolean => {
|
|
212
|
-
return command.startsWith("dler ") || command === "dler";
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
export const isBunCommand = (command: string): boolean => {
|
|
216
|
-
return command.startsWith("bun ") || command === "bun";
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
export const isNodeCommand = (command: string): boolean => {
|
|
220
|
-
return command.startsWith("node ") || command === "node";
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
export const getCommandType = (
|
|
224
|
-
command: string,
|
|
225
|
-
): "dler" | "bun" | "node" | "external" => {
|
|
226
|
-
if (isDlerCommand(command)) return "dler";
|
|
227
|
-
if (isBunCommand(command)) return "bun";
|
|
228
|
-
if (isNodeCommand(command)) return "node";
|
|
229
|
-
return "external";
|
|
230
|
-
};
|