@openqa/cli 1.3.3 → 2.0.0
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/README.md +1 -1
- package/dist/agent/brain/diff-analyzer.js +140 -0
- package/dist/agent/brain/diff-analyzer.js.map +1 -0
- package/dist/agent/brain/llm-cache.js +47 -0
- package/dist/agent/brain/llm-cache.js.map +1 -0
- package/dist/agent/brain/llm-resilience.js +252 -0
- package/dist/agent/brain/llm-resilience.js.map +1 -0
- package/dist/agent/config/index.js +588 -0
- package/dist/agent/config/index.js.map +1 -0
- package/dist/agent/coverage/index.js +74 -0
- package/dist/agent/coverage/index.js.map +1 -0
- package/dist/agent/export/index.js +158 -0
- package/dist/agent/export/index.js.map +1 -0
- package/dist/agent/index-v2.js +2795 -0
- package/dist/agent/index-v2.js.map +1 -0
- package/dist/agent/index.js +387 -55
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/logger.js +41 -0
- package/dist/agent/logger.js.map +1 -0
- package/dist/agent/metrics.js +39 -0
- package/dist/agent/metrics.js.map +1 -0
- package/dist/agent/notifications/index.js +106 -0
- package/dist/agent/notifications/index.js.map +1 -0
- package/dist/agent/openapi/spec.js +338 -0
- package/dist/agent/openapi/spec.js.map +1 -0
- package/dist/agent/tools/project-runner.js +481 -0
- package/dist/agent/tools/project-runner.js.map +1 -0
- package/dist/cli/config.html.js +454 -0
- package/dist/cli/daemon.js +7572 -0
- package/dist/cli/dashboard.html.js +1619 -0
- package/dist/cli/index.js +3624 -1675
- package/dist/cli/kanban.html.js +577 -0
- package/dist/cli/routes.js +895 -0
- package/dist/cli/routes.js.map +1 -0
- package/dist/cli/server.js +3564 -1646
- package/dist/database/index.js +503 -10
- package/dist/database/index.js.map +1 -1
- package/dist/database/sqlite.js +281 -0
- package/dist/database/sqlite.js.map +1 -0
- package/package.json +18 -5
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
// agent/tools/project-runner.ts
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
5
|
+
import { join, resolve } from "path";
|
|
6
|
+
|
|
7
|
+
// agent/errors.ts
|
|
8
|
+
var OpenQAError = class extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(message, code) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "OpenQAError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var ProjectRunnerError = class extends OpenQAError {
|
|
17
|
+
constructor(message, code = "PROJECT_RUNNER_ERROR") {
|
|
18
|
+
super(message, code);
|
|
19
|
+
this.name = "ProjectRunnerError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// agent/tools/project-runner.ts
|
|
24
|
+
function sanitizeRepoPath(inputPath) {
|
|
25
|
+
if (typeof inputPath !== "string" || !inputPath.trim()) {
|
|
26
|
+
throw new ProjectRunnerError("repoPath must be a non-empty string");
|
|
27
|
+
}
|
|
28
|
+
const resolved = resolve(inputPath);
|
|
29
|
+
try {
|
|
30
|
+
const stat = statSync(resolved);
|
|
31
|
+
if (!stat.isDirectory()) {
|
|
32
|
+
throw new ProjectRunnerError(`repoPath is not a directory: ${resolved}`);
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err instanceof ProjectRunnerError) throw err;
|
|
36
|
+
throw new ProjectRunnerError(`repoPath does not exist: ${resolved}`);
|
|
37
|
+
}
|
|
38
|
+
return resolved;
|
|
39
|
+
}
|
|
40
|
+
var ProjectRunner = class extends EventEmitter {
|
|
41
|
+
serverProcess = null;
|
|
42
|
+
serverUrl = null;
|
|
43
|
+
installed = false;
|
|
44
|
+
projectType = null;
|
|
45
|
+
repoPath = null;
|
|
46
|
+
/**
|
|
47
|
+
* Detect what kind of project lives at the given path
|
|
48
|
+
*/
|
|
49
|
+
detectProjectType(repoPath) {
|
|
50
|
+
repoPath = sanitizeRepoPath(repoPath);
|
|
51
|
+
const pkgPath = join(repoPath, "package.json");
|
|
52
|
+
if (existsSync(pkgPath)) {
|
|
53
|
+
return this.detectNodeProject(repoPath, pkgPath);
|
|
54
|
+
}
|
|
55
|
+
if (existsSync(join(repoPath, "requirements.txt")) || existsSync(join(repoPath, "pyproject.toml"))) {
|
|
56
|
+
return this.detectPythonProject(repoPath);
|
|
57
|
+
}
|
|
58
|
+
if (existsSync(join(repoPath, "go.mod"))) {
|
|
59
|
+
return {
|
|
60
|
+
language: "go",
|
|
61
|
+
packageManager: "go",
|
|
62
|
+
scripts: {},
|
|
63
|
+
testRunner: "go test",
|
|
64
|
+
devCommand: "go run ."
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (existsSync(join(repoPath, "Cargo.toml"))) {
|
|
68
|
+
return {
|
|
69
|
+
language: "rust",
|
|
70
|
+
packageManager: "cargo",
|
|
71
|
+
scripts: {},
|
|
72
|
+
testRunner: "cargo test",
|
|
73
|
+
devCommand: "cargo run"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return { language: "unknown", packageManager: "unknown", scripts: {} };
|
|
77
|
+
}
|
|
78
|
+
detectNodeProject(repoPath, pkgPath) {
|
|
79
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
80
|
+
const scripts = pkg.scripts || {};
|
|
81
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
82
|
+
let packageManager = "npm";
|
|
83
|
+
if (existsSync(join(repoPath, "pnpm-lock.yaml"))) packageManager = "pnpm";
|
|
84
|
+
else if (existsSync(join(repoPath, "yarn.lock"))) packageManager = "yarn";
|
|
85
|
+
else if (existsSync(join(repoPath, "bun.lockb"))) packageManager = "bun";
|
|
86
|
+
let framework;
|
|
87
|
+
if (deps["next"]) framework = "next";
|
|
88
|
+
else if (deps["nuxt"]) framework = "nuxt";
|
|
89
|
+
else if (deps["@angular/core"]) framework = "angular";
|
|
90
|
+
else if (deps["svelte"] || deps["@sveltejs/kit"]) framework = "svelte";
|
|
91
|
+
else if (deps["vue"]) framework = "vue";
|
|
92
|
+
else if (deps["react"]) framework = "react";
|
|
93
|
+
else if (deps["express"]) framework = "express";
|
|
94
|
+
else if (deps["fastify"]) framework = "fastify";
|
|
95
|
+
else if (deps["@nestjs/core"]) framework = "nestjs";
|
|
96
|
+
let testRunner;
|
|
97
|
+
if (deps["vitest"]) testRunner = "vitest";
|
|
98
|
+
else if (deps["jest"]) testRunner = "jest";
|
|
99
|
+
else if (deps["mocha"]) testRunner = "mocha";
|
|
100
|
+
else if (deps["playwright"] || deps["@playwright/test"]) testRunner = "playwright";
|
|
101
|
+
else if (deps["cypress"]) testRunner = "cypress";
|
|
102
|
+
let devCommand;
|
|
103
|
+
if (scripts["dev"]) devCommand = `${packageManager} run dev`;
|
|
104
|
+
else if (scripts["start"]) devCommand = `${packageManager} run start`;
|
|
105
|
+
else if (scripts["serve"]) devCommand = `${packageManager} run serve`;
|
|
106
|
+
let buildCommand;
|
|
107
|
+
if (scripts["build"]) buildCommand = `${packageManager} run build`;
|
|
108
|
+
let port;
|
|
109
|
+
const devScript = scripts["dev"] || scripts["start"] || "";
|
|
110
|
+
const portMatch = devScript.match(/--port[= ](\d+)|-p[= ]?(\d+)/);
|
|
111
|
+
if (portMatch) {
|
|
112
|
+
port = parseInt(portMatch[1] || portMatch[2]);
|
|
113
|
+
} else if (framework === "next") {
|
|
114
|
+
port = 3e3;
|
|
115
|
+
} else if (framework === "nuxt" || framework === "vue") {
|
|
116
|
+
port = 3e3;
|
|
117
|
+
} else if (framework === "angular") {
|
|
118
|
+
port = 4200;
|
|
119
|
+
} else if (framework === "svelte") {
|
|
120
|
+
port = 5173;
|
|
121
|
+
} else if (framework === "react") {
|
|
122
|
+
port = 3e3;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
language: "node",
|
|
126
|
+
framework,
|
|
127
|
+
packageManager,
|
|
128
|
+
scripts,
|
|
129
|
+
testRunner,
|
|
130
|
+
devCommand,
|
|
131
|
+
buildCommand,
|
|
132
|
+
port
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
detectPythonProject(repoPath) {
|
|
136
|
+
let testRunner;
|
|
137
|
+
const reqPath = join(repoPath, "requirements.txt");
|
|
138
|
+
if (existsSync(reqPath)) {
|
|
139
|
+
const content = readFileSync(reqPath, "utf-8");
|
|
140
|
+
if (content.includes("pytest")) testRunner = "pytest";
|
|
141
|
+
}
|
|
142
|
+
const pyprojectPath = join(repoPath, "pyproject.toml");
|
|
143
|
+
if (existsSync(pyprojectPath)) {
|
|
144
|
+
const content = readFileSync(pyprojectPath, "utf-8");
|
|
145
|
+
if (content.includes("pytest")) testRunner = "pytest";
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
language: "python",
|
|
149
|
+
packageManager: "pip",
|
|
150
|
+
scripts: {},
|
|
151
|
+
testRunner: testRunner || "pytest",
|
|
152
|
+
devCommand: "python -m flask run"
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Install project dependencies
|
|
157
|
+
*/
|
|
158
|
+
async installDependencies(repoPath) {
|
|
159
|
+
repoPath = sanitizeRepoPath(repoPath);
|
|
160
|
+
const projectType = this.detectProjectType(repoPath);
|
|
161
|
+
this.projectType = projectType;
|
|
162
|
+
this.repoPath = repoPath;
|
|
163
|
+
let command;
|
|
164
|
+
let args;
|
|
165
|
+
switch (projectType.packageManager) {
|
|
166
|
+
case "pnpm":
|
|
167
|
+
command = "pnpm";
|
|
168
|
+
args = ["install"];
|
|
169
|
+
break;
|
|
170
|
+
case "yarn":
|
|
171
|
+
command = "yarn";
|
|
172
|
+
args = ["install"];
|
|
173
|
+
break;
|
|
174
|
+
case "bun":
|
|
175
|
+
command = "bun";
|
|
176
|
+
args = ["install"];
|
|
177
|
+
break;
|
|
178
|
+
case "npm":
|
|
179
|
+
command = "npm";
|
|
180
|
+
args = ["install"];
|
|
181
|
+
break;
|
|
182
|
+
case "pip":
|
|
183
|
+
command = "pip";
|
|
184
|
+
args = ["install", "-r", "requirements.txt"];
|
|
185
|
+
break;
|
|
186
|
+
case "cargo":
|
|
187
|
+
command = "cargo";
|
|
188
|
+
args = ["build"];
|
|
189
|
+
break;
|
|
190
|
+
case "go":
|
|
191
|
+
command = "go";
|
|
192
|
+
args = ["mod", "download"];
|
|
193
|
+
break;
|
|
194
|
+
default:
|
|
195
|
+
throw new ProjectRunnerError(`Unknown package manager: ${projectType.packageManager}`);
|
|
196
|
+
}
|
|
197
|
+
this.emit("install-start", { command, packageManager: projectType.packageManager });
|
|
198
|
+
return new Promise((resolve2, reject) => {
|
|
199
|
+
const proc = spawn(command, args, {
|
|
200
|
+
cwd: repoPath,
|
|
201
|
+
stdio: "pipe",
|
|
202
|
+
timeout: 3e5
|
|
203
|
+
// 5 min
|
|
204
|
+
});
|
|
205
|
+
let output = "";
|
|
206
|
+
proc.stdout?.on("data", (data) => {
|
|
207
|
+
const text = data.toString();
|
|
208
|
+
output += text;
|
|
209
|
+
this.emit("install-progress", { text });
|
|
210
|
+
});
|
|
211
|
+
proc.stderr?.on("data", (data) => {
|
|
212
|
+
const text = data.toString();
|
|
213
|
+
output += text;
|
|
214
|
+
this.emit("install-progress", { text });
|
|
215
|
+
});
|
|
216
|
+
proc.on("close", (code) => {
|
|
217
|
+
if (code === 0) {
|
|
218
|
+
this.installed = true;
|
|
219
|
+
this.emit("install-complete", { success: true });
|
|
220
|
+
resolve2();
|
|
221
|
+
} else {
|
|
222
|
+
const err = new ProjectRunnerError(
|
|
223
|
+
`Install failed (exit code ${code}): ${output.slice(-500)}`
|
|
224
|
+
);
|
|
225
|
+
this.emit("install-complete", { success: false, error: err.message });
|
|
226
|
+
reject(err);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
proc.on("error", (err) => {
|
|
230
|
+
reject(new ProjectRunnerError(`Failed to spawn ${command}: ${err.message}`));
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Start the project's dev server
|
|
236
|
+
*/
|
|
237
|
+
async startDevServer(repoPath) {
|
|
238
|
+
repoPath = sanitizeRepoPath(repoPath);
|
|
239
|
+
const projectType = this.projectType || this.detectProjectType(repoPath);
|
|
240
|
+
this.repoPath = repoPath;
|
|
241
|
+
if (!projectType.devCommand) {
|
|
242
|
+
throw new ProjectRunnerError("No dev command detected for this project");
|
|
243
|
+
}
|
|
244
|
+
const [cmd, ...args] = projectType.devCommand.split(" ");
|
|
245
|
+
this.emit("server-starting", { command: projectType.devCommand });
|
|
246
|
+
return new Promise((resolve2, reject) => {
|
|
247
|
+
const proc = spawn(cmd, args, {
|
|
248
|
+
cwd: repoPath,
|
|
249
|
+
stdio: "pipe",
|
|
250
|
+
detached: true
|
|
251
|
+
});
|
|
252
|
+
this.serverProcess = proc;
|
|
253
|
+
let resolved = false;
|
|
254
|
+
const readyPatterns = [
|
|
255
|
+
/ready on\s+(https?:\/\/\S+)/i,
|
|
256
|
+
/listening on\s+(https?:\/\/\S+)/i,
|
|
257
|
+
/started on\s+(https?:\/\/\S+)/i,
|
|
258
|
+
/Local:\s+(https?:\/\/\S+)/i,
|
|
259
|
+
/http:\/\/localhost:(\d+)/,
|
|
260
|
+
/http:\/\/127\.0\.0\.1:(\d+)/,
|
|
261
|
+
/http:\/\/0\.0\.0\.0:(\d+)/,
|
|
262
|
+
/ready in \d+/i,
|
|
263
|
+
/compiled successfully/i
|
|
264
|
+
];
|
|
265
|
+
const checkOutput = (text) => {
|
|
266
|
+
if (resolved) return;
|
|
267
|
+
for (const pattern of readyPatterns) {
|
|
268
|
+
const match = text.match(pattern);
|
|
269
|
+
if (match) {
|
|
270
|
+
resolved = true;
|
|
271
|
+
let url;
|
|
272
|
+
if (match[1]?.startsWith("http")) {
|
|
273
|
+
url = match[1];
|
|
274
|
+
} else if (match[1]) {
|
|
275
|
+
url = `http://localhost:${match[1]}`;
|
|
276
|
+
} else if (projectType.port) {
|
|
277
|
+
url = `http://localhost:${projectType.port}`;
|
|
278
|
+
} else {
|
|
279
|
+
url = "http://localhost:3000";
|
|
280
|
+
}
|
|
281
|
+
this.serverUrl = url;
|
|
282
|
+
this.emit("server-ready", { url, pid: proc.pid });
|
|
283
|
+
resolve2({ url, pid: proc.pid });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
proc.stdout?.on("data", (data) => {
|
|
289
|
+
checkOutput(data.toString());
|
|
290
|
+
});
|
|
291
|
+
proc.stderr?.on("data", (data) => {
|
|
292
|
+
checkOutput(data.toString());
|
|
293
|
+
});
|
|
294
|
+
proc.on("error", (err) => {
|
|
295
|
+
if (!resolved) {
|
|
296
|
+
reject(new ProjectRunnerError(`Failed to start dev server: ${err.message}`));
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
proc.on("close", (code) => {
|
|
300
|
+
if (!resolved) {
|
|
301
|
+
reject(new ProjectRunnerError(`Dev server exited with code ${code}`));
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
setTimeout(async () => {
|
|
305
|
+
if (resolved) return;
|
|
306
|
+
const port = projectType.port || 3e3;
|
|
307
|
+
const candidatePorts = [port, 5173, 8080, 4200, 3001];
|
|
308
|
+
for (const p of candidatePorts) {
|
|
309
|
+
try {
|
|
310
|
+
const controller = new AbortController();
|
|
311
|
+
const timeoutId = setTimeout(() => controller.abort(), 2e3);
|
|
312
|
+
await fetch(`http://localhost:${p}`, { signal: controller.signal });
|
|
313
|
+
clearTimeout(timeoutId);
|
|
314
|
+
resolved = true;
|
|
315
|
+
const url = `http://localhost:${p}`;
|
|
316
|
+
this.serverUrl = url;
|
|
317
|
+
this.emit("server-ready", { url, pid: proc.pid });
|
|
318
|
+
resolve2({ url, pid: proc.pid });
|
|
319
|
+
return;
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
reject(new ProjectRunnerError("Dev server did not become ready within 60s"));
|
|
324
|
+
}, 6e4);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Run the project's existing test suite
|
|
329
|
+
*/
|
|
330
|
+
async runExistingTests(repoPath) {
|
|
331
|
+
repoPath = sanitizeRepoPath(repoPath);
|
|
332
|
+
const projectType = this.projectType || this.detectProjectType(repoPath);
|
|
333
|
+
if (!projectType.testRunner) {
|
|
334
|
+
throw new ProjectRunnerError("No test runner detected for this project");
|
|
335
|
+
}
|
|
336
|
+
let command;
|
|
337
|
+
let args;
|
|
338
|
+
switch (projectType.testRunner) {
|
|
339
|
+
case "vitest":
|
|
340
|
+
command = "npx";
|
|
341
|
+
args = ["vitest", "run", "--reporter=json"];
|
|
342
|
+
break;
|
|
343
|
+
case "jest":
|
|
344
|
+
command = "npx";
|
|
345
|
+
args = ["jest", "--json", "--forceExit"];
|
|
346
|
+
break;
|
|
347
|
+
case "mocha":
|
|
348
|
+
command = "npx";
|
|
349
|
+
args = ["mocha", "--reporter", "json"];
|
|
350
|
+
break;
|
|
351
|
+
case "playwright":
|
|
352
|
+
command = "npx";
|
|
353
|
+
args = ["playwright", "test", "--reporter=json"];
|
|
354
|
+
break;
|
|
355
|
+
case "pytest":
|
|
356
|
+
command = "python";
|
|
357
|
+
args = ["-m", "pytest", "--tb=short", "-q"];
|
|
358
|
+
break;
|
|
359
|
+
case "go test":
|
|
360
|
+
command = "go";
|
|
361
|
+
args = ["test", "-json", "./..."];
|
|
362
|
+
break;
|
|
363
|
+
case "cargo test":
|
|
364
|
+
command = "cargo";
|
|
365
|
+
args = ["test", "--", "--format=json"];
|
|
366
|
+
break;
|
|
367
|
+
default:
|
|
368
|
+
command = "npx";
|
|
369
|
+
args = [projectType.testRunner, "--json"];
|
|
370
|
+
}
|
|
371
|
+
this.emit("test-start", { runner: projectType.testRunner });
|
|
372
|
+
const startTime = Date.now();
|
|
373
|
+
return new Promise((resolve2, reject) => {
|
|
374
|
+
const proc = spawn(command, args, {
|
|
375
|
+
cwd: repoPath,
|
|
376
|
+
stdio: "pipe",
|
|
377
|
+
timeout: 3e5
|
|
378
|
+
});
|
|
379
|
+
let stdout = "";
|
|
380
|
+
let stderr = "";
|
|
381
|
+
proc.stdout?.on("data", (data) => {
|
|
382
|
+
stdout += data.toString();
|
|
383
|
+
this.emit("test-progress", { text: data.toString() });
|
|
384
|
+
});
|
|
385
|
+
proc.stderr?.on("data", (data) => {
|
|
386
|
+
stderr += data.toString();
|
|
387
|
+
});
|
|
388
|
+
proc.on("close", (code) => {
|
|
389
|
+
const durationMs = Date.now() - startTime;
|
|
390
|
+
const raw = stdout + "\n" + stderr;
|
|
391
|
+
const result = this.parseTestResults(
|
|
392
|
+
projectType.testRunner,
|
|
393
|
+
stdout,
|
|
394
|
+
stderr,
|
|
395
|
+
durationMs
|
|
396
|
+
);
|
|
397
|
+
this.emit("test-complete", result);
|
|
398
|
+
resolve2(result);
|
|
399
|
+
});
|
|
400
|
+
proc.on("error", (err) => {
|
|
401
|
+
reject(new ProjectRunnerError(`Failed to run tests: ${err.message}`));
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
parseTestResults(runner, stdout, stderr, durationMs) {
|
|
406
|
+
const raw = stdout + "\n" + stderr;
|
|
407
|
+
if (runner === "vitest" || runner === "jest") {
|
|
408
|
+
try {
|
|
409
|
+
const jsonMatch = stdout.match(/\{[\s\S]*"numPassedTests"[\s\S]*\}/);
|
|
410
|
+
if (jsonMatch) {
|
|
411
|
+
const json = JSON.parse(jsonMatch[0]);
|
|
412
|
+
return {
|
|
413
|
+
runner,
|
|
414
|
+
passed: json.numPassedTests || 0,
|
|
415
|
+
failed: json.numFailedTests || 0,
|
|
416
|
+
skipped: json.numPendingTests || 0,
|
|
417
|
+
total: json.numTotalTests || 0,
|
|
418
|
+
durationMs,
|
|
419
|
+
raw
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
let passed = 0, failed = 0, skipped = 0;
|
|
426
|
+
const summaryMatch = raw.match(/(\d+)\s*passed.*?(\d+)\s*failed/i);
|
|
427
|
+
if (summaryMatch) {
|
|
428
|
+
passed = parseInt(summaryMatch[1]);
|
|
429
|
+
failed = parseInt(summaryMatch[2]);
|
|
430
|
+
}
|
|
431
|
+
const passedMatch = raw.match(/(\d+)\s*pass(?:ed|ing)/i);
|
|
432
|
+
if (passedMatch && !summaryMatch) passed = parseInt(passedMatch[1]);
|
|
433
|
+
const failedMatch = raw.match(/(\d+)\s*fail(?:ed|ing|ure)/i);
|
|
434
|
+
if (failedMatch && !summaryMatch) failed = parseInt(failedMatch[1]);
|
|
435
|
+
const skippedMatch = raw.match(/(\d+)\s*(?:skip(?:ped)?|pending|todo)/i);
|
|
436
|
+
if (skippedMatch) skipped = parseInt(skippedMatch[1]);
|
|
437
|
+
return {
|
|
438
|
+
runner,
|
|
439
|
+
passed,
|
|
440
|
+
failed,
|
|
441
|
+
skipped,
|
|
442
|
+
total: passed + failed + skipped,
|
|
443
|
+
durationMs,
|
|
444
|
+
raw
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Stop the dev server and clean up
|
|
449
|
+
*/
|
|
450
|
+
cleanup() {
|
|
451
|
+
if (this.serverProcess) {
|
|
452
|
+
try {
|
|
453
|
+
if (this.serverProcess.pid) {
|
|
454
|
+
process.kill(-this.serverProcess.pid, "SIGTERM");
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
try {
|
|
458
|
+
this.serverProcess.kill("SIGTERM");
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
this.serverProcess = null;
|
|
463
|
+
this.serverUrl = null;
|
|
464
|
+
this.emit("server-stopped");
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
getStatus() {
|
|
468
|
+
return {
|
|
469
|
+
repoPath: this.repoPath || "",
|
|
470
|
+
projectType: this.projectType,
|
|
471
|
+
installed: this.installed,
|
|
472
|
+
serverRunning: this.serverProcess !== null && !this.serverProcess.killed,
|
|
473
|
+
serverUrl: this.serverUrl,
|
|
474
|
+
serverPid: this.serverProcess?.pid || null
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
export {
|
|
479
|
+
ProjectRunner
|
|
480
|
+
};
|
|
481
|
+
//# sourceMappingURL=project-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../agent/tools/project-runner.ts","../../../agent/errors.ts"],"sourcesContent":["import { EventEmitter } from 'events';\nimport { spawn, ChildProcess } from 'child_process';\nimport { existsSync, readFileSync, statSync } from 'fs';\nimport { join, resolve } from 'path';\nimport { ProjectRunnerError } from '../errors.js';\n\n/**\n * Resolve and validate a repo path — must be an existing directory.\n * Prevents path traversal by normalising before use.\n */\nfunction sanitizeRepoPath(inputPath: string): string {\n if (typeof inputPath !== 'string' || !inputPath.trim()) {\n throw new ProjectRunnerError('repoPath must be a non-empty string');\n }\n const resolved = resolve(inputPath);\n try {\n const stat = statSync(resolved);\n if (!stat.isDirectory()) {\n throw new ProjectRunnerError(`repoPath is not a directory: ${resolved}`);\n }\n } catch (err) {\n if (err instanceof ProjectRunnerError) throw err;\n throw new ProjectRunnerError(`repoPath does not exist: ${resolved}`);\n }\n return resolved;\n}\n\nexport interface ProjectType {\n language: 'node' | 'python' | 'go' | 'rust' | 'unknown';\n framework?: string;\n packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun' | 'pip' | 'cargo' | 'go' | 'unknown';\n scripts: Record<string, string>;\n testRunner?: string;\n devCommand?: string;\n buildCommand?: string;\n port?: number;\n}\n\nexport interface TestRunResult {\n runner: string;\n passed: number;\n failed: number;\n skipped: number;\n total: number;\n durationMs: number;\n raw: string;\n}\n\nexport interface ProjectStatus {\n repoPath: string;\n projectType: ProjectType | null;\n installed: boolean;\n serverRunning: boolean;\n serverUrl: string | null;\n serverPid: number | null;\n}\n\nexport class ProjectRunner extends EventEmitter {\n private serverProcess: ChildProcess | null = null;\n private serverUrl: string | null = null;\n private installed = false;\n private projectType: ProjectType | null = null;\n private repoPath: string | null = null;\n\n /**\n * Detect what kind of project lives at the given path\n */\n detectProjectType(repoPath: string): ProjectType {\n repoPath = sanitizeRepoPath(repoPath);\n\n // Node.js project\n const pkgPath = join(repoPath, 'package.json');\n if (existsSync(pkgPath)) {\n return this.detectNodeProject(repoPath, pkgPath);\n }\n\n // Python project\n if (existsSync(join(repoPath, 'requirements.txt')) || existsSync(join(repoPath, 'pyproject.toml'))) {\n return this.detectPythonProject(repoPath);\n }\n\n // Go project\n if (existsSync(join(repoPath, 'go.mod'))) {\n return {\n language: 'go',\n packageManager: 'go',\n scripts: {},\n testRunner: 'go test',\n devCommand: 'go run .',\n };\n }\n\n // Rust project\n if (existsSync(join(repoPath, 'Cargo.toml'))) {\n return {\n language: 'rust',\n packageManager: 'cargo',\n scripts: {},\n testRunner: 'cargo test',\n devCommand: 'cargo run',\n };\n }\n\n return { language: 'unknown', packageManager: 'unknown', scripts: {} };\n }\n\n private detectNodeProject(repoPath: string, pkgPath: string): ProjectType {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n const scripts: Record<string, string> = pkg.scripts || {};\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n\n // Detect package manager\n let packageManager: ProjectType['packageManager'] = 'npm';\n if (existsSync(join(repoPath, 'pnpm-lock.yaml'))) packageManager = 'pnpm';\n else if (existsSync(join(repoPath, 'yarn.lock'))) packageManager = 'yarn';\n else if (existsSync(join(repoPath, 'bun.lockb'))) packageManager = 'bun';\n\n // Detect framework\n let framework: string | undefined;\n if (deps['next']) framework = 'next';\n else if (deps['nuxt']) framework = 'nuxt';\n else if (deps['@angular/core']) framework = 'angular';\n else if (deps['svelte'] || deps['@sveltejs/kit']) framework = 'svelte';\n else if (deps['vue']) framework = 'vue';\n else if (deps['react']) framework = 'react';\n else if (deps['express']) framework = 'express';\n else if (deps['fastify']) framework = 'fastify';\n else if (deps['@nestjs/core']) framework = 'nestjs';\n\n // Detect test runner\n let testRunner: string | undefined;\n if (deps['vitest']) testRunner = 'vitest';\n else if (deps['jest']) testRunner = 'jest';\n else if (deps['mocha']) testRunner = 'mocha';\n else if (deps['playwright'] || deps['@playwright/test']) testRunner = 'playwright';\n else if (deps['cypress']) testRunner = 'cypress';\n\n // Detect dev command\n let devCommand: string | undefined;\n if (scripts['dev']) devCommand = `${packageManager} run dev`;\n else if (scripts['start']) devCommand = `${packageManager} run start`;\n else if (scripts['serve']) devCommand = `${packageManager} run serve`;\n\n // Detect build command\n let buildCommand: string | undefined;\n if (scripts['build']) buildCommand = `${packageManager} run build`;\n\n // Detect port\n let port: number | undefined;\n const devScript = scripts['dev'] || scripts['start'] || '';\n const portMatch = devScript.match(/--port[= ](\\d+)|-p[= ]?(\\d+)/);\n if (portMatch) {\n port = parseInt(portMatch[1] || portMatch[2]);\n } else if (framework === 'next') {\n port = 3000;\n } else if (framework === 'nuxt' || framework === 'vue') {\n port = 3000;\n } else if (framework === 'angular') {\n port = 4200;\n } else if (framework === 'svelte') {\n port = 5173;\n } else if (framework === 'react') {\n port = 3000;\n }\n\n return {\n language: 'node',\n framework,\n packageManager,\n scripts,\n testRunner,\n devCommand,\n buildCommand,\n port,\n };\n }\n\n private detectPythonProject(repoPath: string): ProjectType {\n let testRunner: string | undefined;\n\n // Check for pytest in requirements\n const reqPath = join(repoPath, 'requirements.txt');\n if (existsSync(reqPath)) {\n const content = readFileSync(reqPath, 'utf-8');\n if (content.includes('pytest')) testRunner = 'pytest';\n }\n\n const pyprojectPath = join(repoPath, 'pyproject.toml');\n if (existsSync(pyprojectPath)) {\n const content = readFileSync(pyprojectPath, 'utf-8');\n if (content.includes('pytest')) testRunner = 'pytest';\n }\n\n return {\n language: 'python',\n packageManager: 'pip',\n scripts: {},\n testRunner: testRunner || 'pytest',\n devCommand: 'python -m flask run',\n };\n }\n\n /**\n * Install project dependencies\n */\n async installDependencies(repoPath: string): Promise<void> {\n repoPath = sanitizeRepoPath(repoPath);\n const projectType = this.detectProjectType(repoPath);\n this.projectType = projectType;\n this.repoPath = repoPath;\n\n let command: string;\n let args: string[];\n\n switch (projectType.packageManager) {\n case 'pnpm':\n command = 'pnpm'; args = ['install']; break;\n case 'yarn':\n command = 'yarn'; args = ['install']; break;\n case 'bun':\n command = 'bun'; args = ['install']; break;\n case 'npm':\n command = 'npm'; args = ['install']; break;\n case 'pip':\n command = 'pip'; args = ['install', '-r', 'requirements.txt']; break;\n case 'cargo':\n command = 'cargo'; args = ['build']; break;\n case 'go':\n command = 'go'; args = ['mod', 'download']; break;\n default:\n throw new ProjectRunnerError(`Unknown package manager: ${projectType.packageManager}`);\n }\n\n this.emit('install-start', { command, packageManager: projectType.packageManager });\n\n return new Promise((resolve, reject) => {\n const proc = spawn(command, args, {\n cwd: repoPath,\n stdio: 'pipe',\n timeout: 300000, // 5 min\n });\n\n let output = '';\n\n proc.stdout?.on('data', (data: Buffer) => {\n const text = data.toString();\n output += text;\n this.emit('install-progress', { text });\n });\n\n proc.stderr?.on('data', (data: Buffer) => {\n const text = data.toString();\n output += text;\n this.emit('install-progress', { text });\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n this.installed = true;\n this.emit('install-complete', { success: true });\n resolve();\n } else {\n const err = new ProjectRunnerError(\n `Install failed (exit code ${code}): ${output.slice(-500)}`,\n );\n this.emit('install-complete', { success: false, error: err.message });\n reject(err);\n }\n });\n\n proc.on('error', (err) => {\n reject(new ProjectRunnerError(`Failed to spawn ${command}: ${err.message}`));\n });\n });\n }\n\n /**\n * Start the project's dev server\n */\n async startDevServer(repoPath: string): Promise<{ url: string; pid: number }> {\n repoPath = sanitizeRepoPath(repoPath);\n const projectType = this.projectType || this.detectProjectType(repoPath);\n this.repoPath = repoPath;\n\n if (!projectType.devCommand) {\n throw new ProjectRunnerError('No dev command detected for this project');\n }\n\n const [cmd, ...args] = projectType.devCommand.split(' ');\n\n this.emit('server-starting', { command: projectType.devCommand });\n\n return new Promise((resolve, reject) => {\n const proc = spawn(cmd, args, {\n cwd: repoPath,\n stdio: 'pipe',\n detached: true,\n });\n\n this.serverProcess = proc;\n let resolved = false;\n\n const readyPatterns = [\n /ready on\\s+(https?:\\/\\/\\S+)/i,\n /listening on\\s+(https?:\\/\\/\\S+)/i,\n /started on\\s+(https?:\\/\\/\\S+)/i,\n /Local:\\s+(https?:\\/\\/\\S+)/i,\n /http:\\/\\/localhost:(\\d+)/,\n /http:\\/\\/127\\.0\\.0\\.1:(\\d+)/,\n /http:\\/\\/0\\.0\\.0\\.0:(\\d+)/,\n /ready in \\d+/i,\n /compiled successfully/i,\n ];\n\n const checkOutput = (text: string) => {\n if (resolved) return;\n\n for (const pattern of readyPatterns) {\n const match = text.match(pattern);\n if (match) {\n resolved = true;\n let url: string;\n\n if (match[1]?.startsWith('http')) {\n url = match[1];\n } else if (match[1]) {\n url = `http://localhost:${match[1]}`;\n } else if (projectType.port) {\n url = `http://localhost:${projectType.port}`;\n } else {\n url = 'http://localhost:3000';\n }\n\n this.serverUrl = url;\n this.emit('server-ready', { url, pid: proc.pid });\n resolve({ url, pid: proc.pid! });\n return;\n }\n }\n };\n\n proc.stdout?.on('data', (data: Buffer) => {\n checkOutput(data.toString());\n });\n\n proc.stderr?.on('data', (data: Buffer) => {\n checkOutput(data.toString());\n });\n\n proc.on('error', (err) => {\n if (!resolved) {\n reject(new ProjectRunnerError(`Failed to start dev server: ${err.message}`));\n }\n });\n\n proc.on('close', (code) => {\n if (!resolved) {\n reject(new ProjectRunnerError(`Dev server exited with code ${code}`));\n }\n });\n\n // Timeout: if no ready pattern matched after 60s, try probing the port\n setTimeout(async () => {\n if (resolved) return;\n\n const port = projectType.port || 3000;\n const candidatePorts = [port, 5173, 8080, 4200, 3001];\n\n for (const p of candidatePorts) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 2000);\n await fetch(`http://localhost:${p}`, { signal: controller.signal });\n clearTimeout(timeoutId);\n\n resolved = true;\n const url = `http://localhost:${p}`;\n this.serverUrl = url;\n this.emit('server-ready', { url, pid: proc.pid });\n resolve({ url, pid: proc.pid! });\n return;\n } catch {\n // Port not ready\n }\n }\n\n reject(new ProjectRunnerError('Dev server did not become ready within 60s'));\n }, 60000);\n });\n }\n\n /**\n * Run the project's existing test suite\n */\n async runExistingTests(repoPath: string): Promise<TestRunResult> {\n repoPath = sanitizeRepoPath(repoPath);\n const projectType = this.projectType || this.detectProjectType(repoPath);\n\n if (!projectType.testRunner) {\n throw new ProjectRunnerError('No test runner detected for this project');\n }\n\n let command: string;\n let args: string[];\n\n switch (projectType.testRunner) {\n case 'vitest':\n command = 'npx';\n args = ['vitest', 'run', '--reporter=json'];\n break;\n case 'jest':\n command = 'npx';\n args = ['jest', '--json', '--forceExit'];\n break;\n case 'mocha':\n command = 'npx';\n args = ['mocha', '--reporter', 'json'];\n break;\n case 'playwright':\n command = 'npx';\n args = ['playwright', 'test', '--reporter=json'];\n break;\n case 'pytest':\n command = 'python';\n args = ['-m', 'pytest', '--tb=short', '-q'];\n break;\n case 'go test':\n command = 'go';\n args = ['test', '-json', './...'];\n break;\n case 'cargo test':\n command = 'cargo';\n args = ['test', '--', '--format=json'];\n break;\n default:\n command = 'npx';\n args = [projectType.testRunner, '--json'];\n }\n\n this.emit('test-start', { runner: projectType.testRunner });\n\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const proc = spawn(command, args, {\n cwd: repoPath,\n stdio: 'pipe',\n timeout: 300000,\n });\n\n let stdout = '';\n let stderr = '';\n\n proc.stdout?.on('data', (data: Buffer) => {\n stdout += data.toString();\n this.emit('test-progress', { text: data.toString() });\n });\n\n proc.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n proc.on('close', (code) => {\n const durationMs = Date.now() - startTime;\n const raw = stdout + '\\n' + stderr;\n\n const result = this.parseTestResults(\n projectType.testRunner!,\n stdout,\n stderr,\n durationMs,\n );\n\n this.emit('test-complete', result);\n // Resolve even if tests fail — the result contains pass/fail info\n resolve(result);\n });\n\n proc.on('error', (err) => {\n reject(new ProjectRunnerError(`Failed to run tests: ${err.message}`));\n });\n });\n }\n\n private parseTestResults(\n runner: string,\n stdout: string,\n stderr: string,\n durationMs: number,\n ): TestRunResult {\n const raw = stdout + '\\n' + stderr;\n\n // Try JSON parsing for vitest/jest\n if (runner === 'vitest' || runner === 'jest') {\n try {\n const jsonMatch = stdout.match(/\\{[\\s\\S]*\"numPassedTests\"[\\s\\S]*\\}/);\n if (jsonMatch) {\n const json = JSON.parse(jsonMatch[0]);\n return {\n runner,\n passed: json.numPassedTests || 0,\n failed: json.numFailedTests || 0,\n skipped: json.numPendingTests || 0,\n total: json.numTotalTests || 0,\n durationMs,\n raw,\n };\n }\n } catch {\n // Fall through to regex parsing\n }\n }\n\n // Regex fallback: parse summary lines\n let passed = 0, failed = 0, skipped = 0;\n\n // \"Tests: X passed, Y failed, Z skipped, W total\"\n const summaryMatch = raw.match(/(\\d+)\\s*passed.*?(\\d+)\\s*failed/i);\n if (summaryMatch) {\n passed = parseInt(summaryMatch[1]);\n failed = parseInt(summaryMatch[2]);\n }\n\n const passedMatch = raw.match(/(\\d+)\\s*pass(?:ed|ing)/i);\n if (passedMatch && !summaryMatch) passed = parseInt(passedMatch[1]);\n\n const failedMatch = raw.match(/(\\d+)\\s*fail(?:ed|ing|ure)/i);\n if (failedMatch && !summaryMatch) failed = parseInt(failedMatch[1]);\n\n const skippedMatch = raw.match(/(\\d+)\\s*(?:skip(?:ped)?|pending|todo)/i);\n if (skippedMatch) skipped = parseInt(skippedMatch[1]);\n\n return {\n runner,\n passed,\n failed,\n skipped,\n total: passed + failed + skipped,\n durationMs,\n raw,\n };\n }\n\n /**\n * Stop the dev server and clean up\n */\n cleanup() {\n if (this.serverProcess) {\n // Kill the process group (detached)\n try {\n if (this.serverProcess.pid) {\n process.kill(-this.serverProcess.pid, 'SIGTERM');\n }\n } catch {\n // Process may already be dead\n try {\n this.serverProcess.kill('SIGTERM');\n } catch {\n // Ignore\n }\n }\n this.serverProcess = null;\n this.serverUrl = null;\n this.emit('server-stopped');\n }\n }\n\n getStatus(): ProjectStatus {\n return {\n repoPath: this.repoPath || '',\n projectType: this.projectType,\n installed: this.installed,\n serverRunning: this.serverProcess !== null && !this.serverProcess.killed,\n serverUrl: this.serverUrl,\n serverPid: this.serverProcess?.pid || null,\n };\n }\n}\n","// ─── Custom Error Classes for OpenQA ───\n\nexport class OpenQAError extends Error {\n readonly code: string;\n\n constructor(message: string, code: string) {\n super(message);\n this.name = 'OpenQAError';\n this.code = code;\n }\n}\n\nexport class BrowserError extends OpenQAError {\n constructor(message: string, code = 'BROWSER_ERROR') {\n super(message, code);\n this.name = 'BrowserError';\n }\n}\n\nexport class ConfigError extends OpenQAError {\n constructor(message: string, code = 'CONFIG_ERROR') {\n super(message, code);\n this.name = 'ConfigError';\n }\n}\n\nexport class DatabaseError extends OpenQAError {\n constructor(message: string, code = 'DATABASE_ERROR') {\n super(message, code);\n this.name = 'DatabaseError';\n }\n}\n\nexport class GitError extends OpenQAError {\n constructor(message: string, code = 'GIT_ERROR') {\n super(message, code);\n this.name = 'GitError';\n }\n}\n\nexport class BrainError extends OpenQAError {\n constructor(message: string, code = 'BRAIN_ERROR') {\n super(message, code);\n this.name = 'BrainError';\n }\n}\n\nexport class ProjectRunnerError extends OpenQAError {\n constructor(message: string, code = 'PROJECT_RUNNER_ERROR') {\n super(message, code);\n this.name = 'ProjectRunnerError';\n }\n}\n\nexport function isOpenQAError(e: unknown): e is OpenQAError {\n return e instanceof OpenQAError;\n}\n\nexport function toSafeMessage(e: unknown): string {\n if (e instanceof Error) return e.message;\n if (typeof e === 'string') return e;\n return 'Unknown error';\n}\n"],"mappings":"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","names":["resolve"]}
|