deepdebug-local-agent 1.0.23 → 1.2.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/.claude/context.md +76 -0
- package/.claude/skills/README.md +25 -0
- package/.claude/skills/tdd-debugging/SKILL.md +53 -0
- package/.claude/skills/tdd-debugging/references/anti-patterns.md +43 -0
- package/.claude/skills/tdd-debugging/references/command-reference.md +46 -0
- package/.claude/standards/README.md +51 -0
- package/.claude/standards/clean-code.md +75 -0
- package/.claude/standards/code-review-checklist.md +61 -0
- package/.claude/standards/layered-architecture.md +163 -0
- package/.claude/standards/node.md +45 -0
- package/.claude/standards/security-owasp.md +114 -0
- package/.claude/standards/simplicity.md +42 -0
- package/.claude/standards/solid.md +64 -0
- package/.claude/standards/testing.md +59 -0
- package/.cursor/rules/standards.mdc +17 -0
- package/.github/workflows/build-byoc-image.yml +315 -0
- package/.github/workflows/deploy-gcp.yml +102 -0
- package/.github/workflows/npm-publish.yml +76 -8
- package/AGENTS.md +21 -0
- package/CHANGELOG.md +76 -0
- package/CLAUDE.md +13 -0
- package/CLAUDE_CODE_AGENT_INFINITE.md +5 -3
- package/Dockerfile +4 -0
- package/Dockerfile.full +55 -3
- package/bin/install.js +16 -5
- package/cloudbuild-full.yaml +57 -0
- package/docker-compose.yml +5 -3
- package/docs/OBSERVABILITY.md +164 -0
- package/docs/byoc-agent-image.md +70 -0
- package/docs/dlp.md +78 -0
- package/docs/graphify-packaging-plan.md +133 -0
- package/docs/graphify-validation-run-2026-10-28.md +243 -0
- package/docs/releasing.md +37 -0
- package/docs/signed-commands.md +163 -0
- package/env.example +34 -0
- package/helm/Chart.yaml +2 -2
- package/helm/templates/deployment.yaml +13 -3
- package/helm/templates/networkpolicy.yaml +22 -0
- package/helm/templates/secret.yaml +1 -0
- package/helm/values.yaml +13 -14
- package/mcp-server.js +4 -1
- package/package.json +9 -4
- package/scripts/next-version.mjs +75 -0
- package/scripts/upgrade-observability-push.sh +227 -0
- package/src/ai-engine.js +22 -22
- package/src/config/trusted-signing-keys.json +3 -0
- package/src/detectors/build-tool-detector.js +50 -0
- package/src/detectors/multi-project-detector.js +22 -22
- package/src/detectors/service-detector.js +24 -24
- package/src/dlp/engine.js +111 -0
- package/src/dlp/index.js +137 -0
- package/src/dlp/rules.js +97 -0
- package/src/dlp/validators.js +123 -0
- package/src/exec-utils.js +26 -4
- package/src/git/base-git-provider.js +117 -117
- package/src/git/bitbucket-provider.js +2 -2
- package/src/git/git-provider-registry.js +18 -18
- package/src/git/github-provider.js +13 -13
- package/src/git/multi-repo-pr.js +161 -0
- package/src/mcp-http-server.js +32 -16
- package/src/observability/bundle-files.js +418 -0
- package/src/observability/detect.js +151 -0
- package/src/observability/provision.js +212 -0
- package/src/observability/render-cli.js +59 -0
- package/src/observability/routes.js +88 -0
- package/src/patch/patch-engine.js +28 -28
- package/src/patch-manager.js +85 -85
- package/src/patch_bkp.js +14 -14
- package/src/ports.js +4 -4
- package/src/routes/workspace.route.js +47 -16
- package/src/runtimes/base-runtime.js +75 -75
- package/src/runtimes/java/gradle-runtime.js +17 -17
- package/src/runtimes/java/java-integrations.js +25 -25
- package/src/runtimes/java/maven-runtime.js +34 -34
- package/src/runtimes/node/node-integrations.js +1 -1
- package/src/runtimes/node/npm-runtime.js +14 -14
- package/src/runtimes/node/yarn-runtime.js +4 -4
- package/src/runtimes/runtime-registry.js +25 -25
- package/src/server-local.js +4 -4
- package/src/server.js +1366 -333
- package/src/siblings/burst-suppressor.js +89 -0
- package/src/siblings/capabilities.js +273 -0
- package/src/siblings/env-compat.cjs +58 -0
- package/src/siblings/incident-forwarder.js +322 -0
- package/src/siblings/logger.js +15 -0
- package/src/siblings/metrics.js +94 -0
- package/src/utils/agent-auth.js +184 -0
- package/src/utils/approval-token.js +56 -0
- package/src/utils/bind-host.js +29 -0
- package/src/utils/branch-refspec.js +98 -0
- package/src/utils/child-env.js +34 -0
- package/src/utils/environment-diagnostics.js +55 -55
- package/src/utils/exec-policy.js +962 -0
- package/src/utils/exec-utils.js +15 -15
- package/src/utils/file-count.js +46 -0
- package/src/utils/fs-utils.js +10 -10
- package/src/utils/multi-repo-open.js +132 -0
- package/src/utils/open-gate.js +183 -0
- package/src/utils/ranged-read.js +65 -0
- package/src/utils/request-guards.js +77 -0
- package/src/utils/request-signing.js +342 -0
- package/src/utils/root-recovery.js +244 -0
- package/src/utils/safe-exec-args.js +58 -0
- package/src/utils/sandbox.js +182 -0
- package/src/utils/search-outcome.js +56 -0
- package/src/utils/workspace-root.js +80 -0
- package/src/validators/syntax-validator.js +149 -0
- package/src/workspace/detect-port.js +16 -16
- package/src/workspace/file-reader.js +1 -1
- package/src/workspace/process-manager.js +46 -46
- package/src/workspace-manager.js +86 -24
- package/test/adapters/registry.test.js +9 -2
- package/test/agent-auth.test.js +194 -0
- package/test/bind-host.test.js +26 -0
- package/test/branch-refspec.test.js +224 -0
- package/test/burst-suppressor.test.js +60 -0
- package/test/capabilities-access.test.js +47 -0
- package/test/capabilities.test.js +87 -0
- package/test/child-env.test.js +23 -0
- package/test/dlp/engine.test.js +173 -0
- package/test/dlp/fixtures.js +126 -0
- package/test/dlp/index.test.js +166 -0
- package/test/env-compat.test.js +71 -0
- package/test/exec-policy.test.js +463 -0
- package/test/file-count.test.js +64 -0
- package/test/incident-forwarder.test.js +231 -0
- package/test/multi-repo-open.test.js +180 -0
- package/test/multi-repo-pr.test.js +129 -0
- package/test/next-version.test.js +48 -0
- package/test/observability-detect.test.js +199 -0
- package/test/open-gate.test.js +341 -0
- package/test/ranged-read.test.js +60 -0
- package/test/request-guards.test.js +91 -0
- package/test/request-signing.test.js +284 -0
- package/test/root-recovery-sidecar.test.js +171 -0
- package/test/root-recovery.test.js +274 -0
- package/test/safe-exec-args.test.js +58 -0
- package/test/sandbox.test.js +98 -0
- package/test/search-outcome.test.js +50 -0
- package/test/workspace-root.test.js +64 -0
- package/tools/graphify-validation/README.md +92 -0
- package/tools/graphify-validation/config/questions.json +31 -0
- package/tools/graphify-validation/config/repos.json +9 -0
- package/tools/graphify-validation/config/symbols.json +6 -0
- package/tools/graphify-validation/fixtures/spring/expected.json +34 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AppConfig.java +29 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AuditLogger.java +15 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/Cache.java +7 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/CheckoutService.java +25 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/InMemoryCache.java +18 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/OrderController.java +22 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaymentGateway.java +5 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaypalGateway.java +11 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/RefundService.java +14 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/StripeGateway.java +11 -0
- package/tools/graphify-validation/lib/affected_vs_rg.py +81 -0
- package/tools/graphify-validation/lib/audit_hook/sitecustomize.py +22 -0
- package/tools/graphify-validation/lib/graph_metrics.py +96 -0
- package/tools/graphify-validation/lib/gv_common.py +130 -0
- package/tools/graphify-validation/lib/licenses.py +47 -0
- package/tools/graphify-validation/lib/pip_audit_summary.py +18 -0
- package/tools/graphify-validation/lib/recall.py +115 -0
- package/tools/graphify-validation/lib/report.py +226 -0
- package/tools/graphify-validation/lib/run_all.py +237 -0
- package/tools/graphify-validation/lib/run_graphify.py +144 -0
- package/tools/graphify-validation/lib/rusage_helper.py +17 -0
- package/tools/graphify-validation/lib/sample_edges.py +246 -0
- package/tools/graphify-validation/lib/spring_fixture.py +81 -0
- package/tools/graphify-validation/requirements-py311.lock +508 -0
- package/tools/graphify-validation/requirements-tools.in +1 -0
- package/tools/graphify-validation/requirements-tools.lock +371 -0
- package/tools/graphify-validation/requirements.in +1 -0
- package/tools/graphify-validation/requirements.lock +502 -0
- package/tools/graphify-validation/run.sh +43 -0
- package/tools/graphify-validation/tests/test_harness.py +151 -0
- package/cloudbuild-agent-qa.yaml +0 -43
- package/cloudbuild-agent-staging.yaml +0 -40
- package/cloudbuild.yaml.deprecated +0 -42
package/src/ports.js
CHANGED
|
@@ -18,10 +18,10 @@ export async function detectPort(root) {
|
|
|
18
18
|
const content = await readFile(full, "utf8");
|
|
19
19
|
const doc = yaml.load(content);
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Try to read it from the parsed object
|
|
22
22
|
let port = doc?.server?.port ?? doc?.SERVER?.PORT;
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// If it is a string like ${PORT:8090}, extract the default value
|
|
25
25
|
if (typeof port === 'string') {
|
|
26
26
|
const match = port.match(/\$\{PORT:(\d+)\}/);
|
|
27
27
|
if (match) port = match[1];
|
|
@@ -43,7 +43,7 @@ export async function detectPort(root) {
|
|
|
43
43
|
const props = PropertiesReader(full);
|
|
44
44
|
let port = props.get("server.port");
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// If it is a string like ${PORT:8090}, extract the default value
|
|
47
47
|
if (typeof port === 'string') {
|
|
48
48
|
const match = port.match(/\$\{PORT:(\d+)\}/);
|
|
49
49
|
if (match) port = match[1];
|
|
@@ -54,7 +54,7 @@ export async function detectPort(root) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Node (
|
|
57
|
+
// Node (with default .env)
|
|
58
58
|
const envCandidates = [".env", "src/.env"];
|
|
59
59
|
for (const rel of envCandidates) {
|
|
60
60
|
const full = path.join(root, rel);
|
|
@@ -5,7 +5,7 @@ import os from "os";
|
|
|
5
5
|
|
|
6
6
|
const router = express.Router();
|
|
7
7
|
|
|
8
|
-
// 🔹
|
|
8
|
+
// 🔹 Global workspace path (initialized with a default fallback)
|
|
9
9
|
// ✅ FIX: Read WORKSPACE_ROOT (set by install.js Docker) OR WORKSPACE_BASE (legacy)
|
|
10
10
|
let workspacePath =
|
|
11
11
|
process.env.WORKSPACE_ROOT ||
|
|
@@ -89,7 +89,7 @@ function translateToDockerPath(hostPath) {
|
|
|
89
89
|
|
|
90
90
|
// --------------------------------------------------
|
|
91
91
|
// 🧩 1️⃣ GET /workspace/info
|
|
92
|
-
//
|
|
92
|
+
// Returns information about the current workspace
|
|
93
93
|
// --------------------------------------------------
|
|
94
94
|
router.get("/info", async (req, res) => {
|
|
95
95
|
try {
|
|
@@ -199,11 +199,22 @@ async function ensureRepoCloned(repoUrl, token, branch = 'main') {
|
|
|
199
199
|
// Fresh clone
|
|
200
200
|
console.log(`📂 [git] Cloning ${repoUrl} (branch: ${branch})...`);
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
let result = execAsync(
|
|
203
203
|
`git clone --branch ${branch} --single-branch --depth 50 "${authUrl}" "${clonePath}"`,
|
|
204
204
|
{ timeout: 180000 } // 3 minutes for large repos
|
|
205
205
|
);
|
|
206
206
|
|
|
207
|
+
if (result.exitCode !== 0 && /could not find remote branch/i.test(result.stderr || '')) {
|
|
208
|
+
// Requested branch may not exist (repo uses master, caller assumed main):
|
|
209
|
+
// fall back to the repo's default branch instead of failing the session.
|
|
210
|
+
console.warn(`⚠️ [git] Branch '${branch}' not found — falling back to the repo default branch`);
|
|
211
|
+
execAsync(`rm -rf "${clonePath}"`);
|
|
212
|
+
result = execAsync(
|
|
213
|
+
`git clone --single-branch --depth 50 "${authUrl}" "${clonePath}"`,
|
|
214
|
+
{ timeout: 180000 }
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
207
218
|
if (result.exitCode !== 0) {
|
|
208
219
|
throw new Error(`Git clone failed: ${result.stderr}`);
|
|
209
220
|
}
|
|
@@ -225,6 +236,18 @@ async function ensureRepoCloned(repoUrl, token, branch = 'main') {
|
|
|
225
236
|
|
|
226
237
|
router.post("/open", async (req, res) => {
|
|
227
238
|
try {
|
|
239
|
+
// ==========================================
|
|
240
|
+
// Input validation and sanitization
|
|
241
|
+
// ==========================================
|
|
242
|
+
if (!req.body || typeof req.body !== 'object') {
|
|
243
|
+
console.error(`❌ [/workspace/open] Invalid request body:`, req.body);
|
|
244
|
+
return res.status(400).json({
|
|
245
|
+
ok: false,
|
|
246
|
+
error: "Invalid request body - expected JSON object"
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Now safe to destructure
|
|
228
251
|
const { root, workspacePath: wpPath, repoUrl, token, branch = 'main' } = req.body;
|
|
229
252
|
|
|
230
253
|
// ==========================================
|
|
@@ -272,7 +295,10 @@ router.post("/open", async (req, res) => {
|
|
|
272
295
|
const requestedPath = root || wpPath;
|
|
273
296
|
|
|
274
297
|
if (!requestedPath) {
|
|
275
|
-
return res.status(400).json({
|
|
298
|
+
return res.status(400).json({
|
|
299
|
+
ok: false,
|
|
300
|
+
error: "Missing 'root', 'workspacePath', or 'repoUrl' in body"
|
|
301
|
+
});
|
|
276
302
|
}
|
|
277
303
|
|
|
278
304
|
// Translate host path to docker path if needed
|
|
@@ -301,6 +327,7 @@ router.post("/open", async (req, res) => {
|
|
|
301
327
|
|
|
302
328
|
console.error(`❌ [/workspace/open] Directory not found: ${resolvedPath}`);
|
|
303
329
|
return res.status(400).json({
|
|
330
|
+
ok: false,
|
|
304
331
|
error: `Directory not found: ${resolvedPath}`,
|
|
305
332
|
requestedPath, resolvedPath,
|
|
306
333
|
availableProjects: availableDirs,
|
|
@@ -329,14 +356,18 @@ router.post("/open", async (req, res) => {
|
|
|
329
356
|
port: null
|
|
330
357
|
});
|
|
331
358
|
} catch (err) {
|
|
332
|
-
console.error("❌ [/workspace/open]
|
|
333
|
-
return res.status(500).json({
|
|
359
|
+
console.error("❌ [/workspace/open] Unexpected error:", err);
|
|
360
|
+
return res.status(500).json({
|
|
361
|
+
ok: false,
|
|
362
|
+
error: err.message || "Internal server error",
|
|
363
|
+
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
|
|
364
|
+
});
|
|
334
365
|
}
|
|
335
366
|
});
|
|
336
367
|
|
|
337
368
|
// --------------------------------------------------
|
|
338
369
|
// 🧩 2️⃣ POST /workspace
|
|
339
|
-
//
|
|
370
|
+
// Updates the workspace directory selected by the user
|
|
340
371
|
// (legacy endpoint - kept for backward compatibility)
|
|
341
372
|
// --------------------------------------------------
|
|
342
373
|
router.post("/", async (req, res) => {
|
|
@@ -370,7 +401,7 @@ router.post("/", async (req, res) => {
|
|
|
370
401
|
|
|
371
402
|
// --------------------------------------------------
|
|
372
403
|
// 🧩 3️⃣ GET /workspace/files
|
|
373
|
-
//
|
|
404
|
+
// Lists .java files (up to 3 levels deep)
|
|
374
405
|
// --------------------------------------------------
|
|
375
406
|
router.get("/files", async (req, res) => {
|
|
376
407
|
try {
|
|
@@ -421,7 +452,7 @@ router.get("/files", async (req, res) => {
|
|
|
421
452
|
|
|
422
453
|
// --------------------------------------------------
|
|
423
454
|
// 🧩 4️⃣ GET /workspace/file
|
|
424
|
-
//
|
|
455
|
+
// Reads the content of a specific file inside the workspace
|
|
425
456
|
// --------------------------------------------------
|
|
426
457
|
router.get("/file", async (req, res) => {
|
|
427
458
|
try {
|
|
@@ -431,7 +462,7 @@ router.get("/file", async (req, res) => {
|
|
|
431
462
|
return res.status(400).json({ error: "Missing 'path' query parameter" });
|
|
432
463
|
}
|
|
433
464
|
|
|
434
|
-
// 🔹
|
|
465
|
+
// 🔹 Fix the path if it comes in relative (e.g. "src/...") or with an incorrect prefix
|
|
435
466
|
let fullPath = requestedPath;
|
|
436
467
|
|
|
437
468
|
if (!path.isAbsolute(requestedPath) || !requestedPath.startsWith(workspacePath)) {
|
|
@@ -470,7 +501,7 @@ router.get("/file", async (req, res) => {
|
|
|
470
501
|
|
|
471
502
|
// --------------------------------------------------
|
|
472
503
|
// 🧩 5️⃣ POST /workspace/resolve
|
|
473
|
-
//
|
|
504
|
+
// Resolves a relative path to an absolute one (for display in the frontend)
|
|
474
505
|
// --------------------------------------------------
|
|
475
506
|
router.post("/resolve", async (req, res) => {
|
|
476
507
|
try {
|
|
@@ -498,7 +529,7 @@ router.post("/resolve", async (req, res) => {
|
|
|
498
529
|
|
|
499
530
|
// --------------------------------------------------
|
|
500
531
|
// 🧩 6️⃣ GET /workspace/detect
|
|
501
|
-
//
|
|
532
|
+
// Detects the project type (Maven / Gradle / Other)
|
|
502
533
|
// --------------------------------------------------
|
|
503
534
|
router.get("/detect", async (req, res) => {
|
|
504
535
|
try {
|
|
@@ -526,7 +557,7 @@ router.get("/detect", async (req, res) => {
|
|
|
526
557
|
|
|
527
558
|
// --------------------------------------------------
|
|
528
559
|
// 🧩 7️⃣ GET /workspace/search-file
|
|
529
|
-
//
|
|
560
|
+
// Searches for a file by name inside the workspace
|
|
530
561
|
// --------------------------------------------------
|
|
531
562
|
router.get("/search-file", async (req, res) => {
|
|
532
563
|
try {
|
|
@@ -590,7 +621,7 @@ router.get("/search-file", async (req, res) => {
|
|
|
590
621
|
|
|
591
622
|
// --------------------------------------------------
|
|
592
623
|
// 🧩 8️⃣ POST /workspace/search
|
|
593
|
-
//
|
|
624
|
+
// Searches for content inside the workspace files
|
|
594
625
|
// --------------------------------------------------
|
|
595
626
|
router.post("/search", async (req, res) => {
|
|
596
627
|
try {
|
|
@@ -654,7 +685,7 @@ router.post("/search", async (req, res) => {
|
|
|
654
685
|
|
|
655
686
|
// --------------------------------------------------
|
|
656
687
|
// 🧩 9️⃣ GET /workspace/scan
|
|
657
|
-
//
|
|
688
|
+
// Lists ALL workspace files (for the list_files tool)
|
|
658
689
|
// --------------------------------------------------
|
|
659
690
|
router.get("/scan", async (req, res) => {
|
|
660
691
|
try {
|
|
@@ -718,7 +749,7 @@ router.get("/scan", async (req, res) => {
|
|
|
718
749
|
|
|
719
750
|
// --------------------------------------------------
|
|
720
751
|
// 🧩 🔟 GET /workspace/file-content
|
|
721
|
-
// Alias
|
|
752
|
+
// Alias for /workspace/file (compatibility with Java code)
|
|
722
753
|
// --------------------------------------------------
|
|
723
754
|
router.get("/file-content", async (req, res) => {
|
|
724
755
|
try {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BaseRuntime
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Abstract interface for all language runtimes.
|
|
5
|
+
* Each language (Java, Node, Python, etc.) must implement this interface.
|
|
6
6
|
*
|
|
7
7
|
* @abstract
|
|
8
8
|
*/
|
|
@@ -19,32 +19,32 @@ export class BaseRuntime {
|
|
|
19
19
|
// ==========================================
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @returns {string}
|
|
22
|
+
* Unique identifier of the runtime
|
|
23
|
+
* @returns {string} e.g.: 'java-maven', 'node-npm', 'python-pip'
|
|
24
24
|
*/
|
|
25
25
|
getId() {
|
|
26
26
|
throw new Error('Method getId() must be implemented');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
* @returns {string}
|
|
30
|
+
* Friendly name of the runtime
|
|
31
|
+
* @returns {string} e.g.: 'Java (Maven)', 'Node.js (npm)'
|
|
32
32
|
*/
|
|
33
33
|
getName() {
|
|
34
34
|
throw new Error('Method getName() must be implemented');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
* @returns {string}
|
|
38
|
+
* Language of the runtime
|
|
39
|
+
* @returns {string} e.g.: 'java', 'node', 'python', 'dotnet', 'go'
|
|
40
40
|
*/
|
|
41
41
|
getLanguage() {
|
|
42
42
|
throw new Error('Method getLanguage() must be implemented');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Build tool
|
|
47
|
-
* @returns {string}
|
|
46
|
+
* Build tool used
|
|
47
|
+
* @returns {string} e.g.: 'maven', 'gradle', 'npm', 'yarn', 'pip'
|
|
48
48
|
*/
|
|
49
49
|
getBuildTool() {
|
|
50
50
|
throw new Error('Method getBuildTool() must be implemented');
|
|
@@ -55,16 +55,16 @@ export class BaseRuntime {
|
|
|
55
55
|
// ==========================================
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
59
|
-
* @returns {string[]}
|
|
58
|
+
* Marker files that indicate this runtime
|
|
59
|
+
* @returns {string[]} e.g.: ['pom.xml'], ['package.json']
|
|
60
60
|
*/
|
|
61
61
|
getMarkerFiles() {
|
|
62
62
|
throw new Error('Method getMarkerFiles() must be implemented');
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
* @param {string} workspacePath -
|
|
66
|
+
* Checks whether this runtime is compatible with the workspace
|
|
67
|
+
* @param {string} workspacePath - Workspace path
|
|
68
68
|
* @returns {Promise<boolean>}
|
|
69
69
|
*/
|
|
70
70
|
async isCompatible(workspacePath) {
|
|
@@ -72,8 +72,8 @@ export class BaseRuntime {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
76
|
-
* @param {string} workspacePath -
|
|
75
|
+
* Detects project information
|
|
76
|
+
* @param {string} workspacePath - Workspace path
|
|
77
77
|
* @returns {Promise<ProjectInfo>}
|
|
78
78
|
*/
|
|
79
79
|
async detectProject(workspacePath) {
|
|
@@ -85,9 +85,9 @@ export class BaseRuntime {
|
|
|
85
85
|
// ==========================================
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
89
|
-
* @param {string} workspacePath -
|
|
90
|
-
* @param {object} options -
|
|
88
|
+
* Installs the project dependencies
|
|
89
|
+
* @param {string} workspacePath - Workspace path
|
|
90
|
+
* @param {object} options - Additional options
|
|
91
91
|
* @returns {Promise<CommandResult>}
|
|
92
92
|
*/
|
|
93
93
|
async install(workspacePath, options = {}) {
|
|
@@ -95,9 +95,9 @@ export class BaseRuntime {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
99
|
-
* @param {string} workspacePath -
|
|
100
|
-
* @param {object} options -
|
|
98
|
+
* Compiles the project
|
|
99
|
+
* @param {string} workspacePath - Workspace path
|
|
100
|
+
* @param {object} options - Additional options
|
|
101
101
|
* @returns {Promise<CommandResult>}
|
|
102
102
|
*/
|
|
103
103
|
async build(workspacePath, options = {}) {
|
|
@@ -105,8 +105,8 @@ export class BaseRuntime {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
109
|
-
* @param {object} options -
|
|
108
|
+
* Returns the command to start the service
|
|
109
|
+
* @param {object} options - Options (port, profile, etc.)
|
|
110
110
|
* @returns {StartCommand} { cmd: string, args: string[], env?: object }
|
|
111
111
|
*/
|
|
112
112
|
getStartCommand(options = {}) {
|
|
@@ -114,7 +114,7 @@ export class BaseRuntime {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
*
|
|
117
|
+
* Regex pattern to detect when the service is ready
|
|
118
118
|
* @returns {RegExp}
|
|
119
119
|
*/
|
|
120
120
|
getReadyPattern() {
|
|
@@ -122,7 +122,7 @@ export class BaseRuntime {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
*
|
|
125
|
+
* Default port of the framework
|
|
126
126
|
* @returns {number}
|
|
127
127
|
*/
|
|
128
128
|
getDefaultPort() {
|
|
@@ -130,8 +130,8 @@ export class BaseRuntime {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
|
-
*
|
|
134
|
-
* @param {string} workspacePath -
|
|
133
|
+
* Detects the port configured in the project
|
|
134
|
+
* @param {string} workspacePath - Workspace path
|
|
135
135
|
* @returns {Promise<number|null>}
|
|
136
136
|
*/
|
|
137
137
|
async detectPort(workspacePath) {
|
|
@@ -143,9 +143,9 @@ export class BaseRuntime {
|
|
|
143
143
|
// ==========================================
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
|
-
*
|
|
147
|
-
* @param {string} workspacePath -
|
|
148
|
-
* @param {object} options -
|
|
146
|
+
* Runs the project tests
|
|
147
|
+
* @param {string} workspacePath - Workspace path
|
|
148
|
+
* @param {object} options - Options (testFile, coverage, etc.)
|
|
149
149
|
* @returns {Promise<TestResult>}
|
|
150
150
|
*/
|
|
151
151
|
async test(workspacePath, options = {}) {
|
|
@@ -153,9 +153,9 @@ export class BaseRuntime {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
*
|
|
157
|
-
* @param {string} workspacePath -
|
|
158
|
-
* @param {string} testFile -
|
|
156
|
+
* Runs a specific test
|
|
157
|
+
* @param {string} workspacePath - Workspace path
|
|
158
|
+
* @param {string} testFile - Test file
|
|
159
159
|
* @returns {Promise<TestResult>}
|
|
160
160
|
*/
|
|
161
161
|
async testSingle(workspacePath, testFile) {
|
|
@@ -167,16 +167,16 @@ export class BaseRuntime {
|
|
|
167
167
|
// ==========================================
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
*
|
|
171
|
-
* @returns {string[]}
|
|
170
|
+
* Supported file extensions
|
|
171
|
+
* @returns {string[]} e.g.: ['.java'], ['.js', '.ts']
|
|
172
172
|
*/
|
|
173
173
|
getFileExtensions() {
|
|
174
174
|
throw new Error('Method getFileExtensions() must be implemented');
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
|
-
*
|
|
179
|
-
* @param {string} workspacePath -
|
|
178
|
+
* Finds HTTP integrations in the code
|
|
179
|
+
* @param {string} workspacePath - Workspace path
|
|
180
180
|
* @returns {Promise<Integration[]>}
|
|
181
181
|
*/
|
|
182
182
|
async findIntegrations(workspacePath) {
|
|
@@ -184,9 +184,9 @@ export class BaseRuntime {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
|
-
*
|
|
188
|
-
* @param {string} filePath -
|
|
189
|
-
* @param {string} content -
|
|
187
|
+
* Analyzes the imports/dependencies of a file
|
|
188
|
+
* @param {string} filePath - File path
|
|
189
|
+
* @param {string} content - File content
|
|
190
190
|
* @returns {Promise<Dependency[]>}
|
|
191
191
|
*/
|
|
192
192
|
async analyzeImports(filePath, content) {
|
|
@@ -198,8 +198,8 @@ export class BaseRuntime {
|
|
|
198
198
|
// ==========================================
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
|
-
*
|
|
202
|
-
* @param {string} workspacePath -
|
|
201
|
+
* Validates whether a diff is applicable
|
|
202
|
+
* @param {string} workspacePath - Workspace path
|
|
203
203
|
* @param {string} diff - Unified diff
|
|
204
204
|
* @returns {Promise<ValidationResult>}
|
|
205
205
|
*/
|
|
@@ -208,13 +208,13 @@ export class BaseRuntime {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
*
|
|
212
|
-
* @param {string} workspacePath -
|
|
213
|
-
* @param {string[]} modifiedFiles -
|
|
211
|
+
* Post-patch actions (e.g.: reformat code)
|
|
212
|
+
* @param {string} workspacePath - Workspace path
|
|
213
|
+
* @param {string[]} modifiedFiles - Modified files
|
|
214
214
|
* @returns {Promise<void>}
|
|
215
215
|
*/
|
|
216
216
|
async postPatchActions(workspacePath, modifiedFiles) {
|
|
217
|
-
// Override
|
|
217
|
+
// Override in the specific implementations
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -224,12 +224,12 @@ export class BaseRuntime {
|
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
226
|
* @typedef {object} ProjectInfo
|
|
227
|
-
* @property {string} name -
|
|
228
|
-
* @property {string} version -
|
|
229
|
-
* @property {string} language -
|
|
230
|
-
* @property {string} framework -
|
|
227
|
+
* @property {string} name - Project name
|
|
228
|
+
* @property {string} version - Version
|
|
229
|
+
* @property {string} language - Language
|
|
230
|
+
* @property {string} framework - Detected framework
|
|
231
231
|
* @property {string} buildTool - Build tool
|
|
232
|
-
* @property {number} port -
|
|
232
|
+
* @property {number} port - Configured port
|
|
233
233
|
*/
|
|
234
234
|
|
|
235
235
|
/**
|
|
@@ -237,54 +237,54 @@ export class BaseRuntime {
|
|
|
237
237
|
* @property {number} code - Exit code
|
|
238
238
|
* @property {string} stdout - Standard output
|
|
239
239
|
* @property {string} stderr - Standard error
|
|
240
|
-
* @property {number} duration -
|
|
240
|
+
* @property {number} duration - Duration in ms
|
|
241
241
|
*/
|
|
242
242
|
|
|
243
243
|
/**
|
|
244
244
|
* @typedef {object} StartCommand
|
|
245
|
-
* @property {string} cmd -
|
|
246
|
-
* @property {string[]} args -
|
|
247
|
-
* @property {object} [env] -
|
|
245
|
+
* @property {string} cmd - Command
|
|
246
|
+
* @property {string[]} args - Arguments
|
|
247
|
+
* @property {object} [env] - Additional environment variables
|
|
248
248
|
*/
|
|
249
249
|
|
|
250
250
|
/**
|
|
251
251
|
* @typedef {object} TestResult
|
|
252
|
-
* @property {boolean} success -
|
|
253
|
-
* @property {number} passed -
|
|
254
|
-
* @property {number} failed -
|
|
255
|
-
* @property {number} skipped -
|
|
256
|
-
* @property {number} duration -
|
|
257
|
-
* @property {string} output -
|
|
258
|
-
* @property {TestFailure[]} failures -
|
|
252
|
+
* @property {boolean} success - Whether all tests passed
|
|
253
|
+
* @property {number} passed - Number of tests that passed
|
|
254
|
+
* @property {number} failed - Number of tests that failed
|
|
255
|
+
* @property {number} skipped - Number of skipped tests
|
|
256
|
+
* @property {number} duration - Duration in ms
|
|
257
|
+
* @property {string} output - Full output
|
|
258
|
+
* @property {TestFailure[]} failures - List of failures
|
|
259
259
|
*/
|
|
260
260
|
|
|
261
261
|
/**
|
|
262
262
|
* @typedef {object} TestFailure
|
|
263
|
-
* @property {string} testName -
|
|
264
|
-
* @property {string} message -
|
|
263
|
+
* @property {string} testName - Test name
|
|
264
|
+
* @property {string} message - Error message
|
|
265
265
|
* @property {string} stackTrace - Stack trace
|
|
266
266
|
*/
|
|
267
267
|
|
|
268
268
|
/**
|
|
269
269
|
* @typedef {object} Integration
|
|
270
|
-
* @property {string} type -
|
|
271
|
-
* @property {string} file -
|
|
272
|
-
* @property {number} line -
|
|
273
|
-
* @property {string} targetUrl - URL
|
|
274
|
-
* @property {string} method -
|
|
270
|
+
* @property {string} type - Type (rest-templates, feign, axios, etc.)
|
|
271
|
+
* @property {string} file - File where it was found
|
|
272
|
+
* @property {number} line - Line
|
|
273
|
+
* @property {string} targetUrl - Target URL (if detectable)
|
|
274
|
+
* @property {string} method - HTTP method (if detectable)
|
|
275
275
|
*/
|
|
276
276
|
|
|
277
277
|
/**
|
|
278
278
|
* @typedef {object} Dependency
|
|
279
|
-
* @property {string} name -
|
|
280
|
-
* @property {string} type -
|
|
281
|
-
* @property {boolean} isExternal -
|
|
279
|
+
* @property {string} name - Dependency name
|
|
280
|
+
* @property {string} type - Type (import, require, using)
|
|
281
|
+
* @property {boolean} isExternal - Whether it is external to the project
|
|
282
282
|
*/
|
|
283
283
|
|
|
284
284
|
/**
|
|
285
285
|
* @typedef {object} ValidationResult
|
|
286
|
-
* @property {boolean} valid -
|
|
287
|
-
* @property {string[]} errors -
|
|
286
|
+
* @property {boolean} valid - Whether the patch is valid
|
|
287
|
+
* @property {string[]} errors - List of errors
|
|
288
288
|
*/
|
|
289
289
|
|
|
290
290
|
export default BaseRuntime;
|
|
@@ -6,15 +6,15 @@ import { run } from '../../utils/exec-utils.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* GradleRuntime
|
|
8
8
|
*
|
|
9
|
-
* Runtime
|
|
10
|
-
*
|
|
9
|
+
* Runtime for Java projects using Gradle.
|
|
10
|
+
* Supports Spring Boot, Quarkus, and generic Gradle projects.
|
|
11
11
|
*/
|
|
12
12
|
export class GradleRuntime extends BaseRuntime {
|
|
13
13
|
|
|
14
14
|
constructor() {
|
|
15
15
|
super();
|
|
16
16
|
this.framework = null;
|
|
17
|
-
this.useWrapper = true; //
|
|
17
|
+
this.useWrapper = true; // Use ./gradlew by default
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// ==========================================
|
|
@@ -64,11 +64,11 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
64
64
|
buildContent = await readFile(kotlinPath, 'utf8');
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
//
|
|
67
|
+
// Check whether a wrapper exists
|
|
68
68
|
const wrapperPath = path.join(workspacePath, 'gradlew');
|
|
69
69
|
this.useWrapper = await exists(wrapperPath);
|
|
70
70
|
|
|
71
|
-
//
|
|
71
|
+
// Detect the project name
|
|
72
72
|
const settingsPath = path.join(workspacePath, 'settings.gradle');
|
|
73
73
|
const settingsKtsPath = path.join(workspacePath, 'settings.gradle.kts');
|
|
74
74
|
let name = path.basename(workspacePath);
|
|
@@ -83,15 +83,15 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
83
83
|
if (match) name = match[1];
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
//
|
|
86
|
+
// Detect the version
|
|
87
87
|
const versionMatch = buildContent.match(/version\s*=?\s*['"]([^'"]+)['"]/);
|
|
88
88
|
const version = versionMatch ? versionMatch[1] : '0.0.1';
|
|
89
89
|
|
|
90
|
-
//
|
|
90
|
+
// Detect the framework
|
|
91
91
|
const framework = this.detectFramework(buildContent);
|
|
92
92
|
this.framework = framework;
|
|
93
93
|
|
|
94
|
-
//
|
|
94
|
+
// Detect the port
|
|
95
95
|
const port = await this.detectPort(workspacePath);
|
|
96
96
|
|
|
97
97
|
return {
|
|
@@ -207,7 +207,7 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
async detectPort(workspacePath) {
|
|
210
|
-
//
|
|
210
|
+
// Same logic as Maven - the configs are identical
|
|
211
211
|
const propsPath = path.join(workspacePath, 'src/main/resources/application.properties');
|
|
212
212
|
if (await exists(propsPath)) {
|
|
213
213
|
const content = await readFile(propsPath, 'utf8');
|
|
@@ -233,10 +233,10 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
233
233
|
const startTime = Date.now();
|
|
234
234
|
const cmd = this.getGradleCommand();
|
|
235
235
|
|
|
236
|
-
// Clean
|
|
236
|
+
// Clean first
|
|
237
237
|
await run(cmd, ['clean', '--quiet'], workspacePath);
|
|
238
238
|
|
|
239
|
-
//
|
|
239
|
+
// Run the tests
|
|
240
240
|
const result = await run(cmd, ['test', '--quiet'], workspacePath);
|
|
241
241
|
|
|
242
242
|
const testResult = this.parseTestOutput(result, workspacePath);
|
|
@@ -249,7 +249,7 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
249
249
|
const startTime = Date.now();
|
|
250
250
|
const cmd = this.getGradleCommand();
|
|
251
251
|
|
|
252
|
-
//
|
|
252
|
+
// Extract the class name
|
|
253
253
|
const className = path.basename(testFile, '.java');
|
|
254
254
|
|
|
255
255
|
const result = await run(cmd, [
|
|
@@ -268,10 +268,10 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
268
268
|
async parseTestOutput(result, workspacePath) {
|
|
269
269
|
const output = result.stdout + result.stderr;
|
|
270
270
|
|
|
271
|
-
//
|
|
271
|
+
// Try to read the Gradle XML report if available
|
|
272
272
|
const reportDir = path.join(workspacePath, 'build/test-results/test');
|
|
273
273
|
|
|
274
|
-
//
|
|
274
|
+
// Basic parsing of the output
|
|
275
275
|
let passed = 0;
|
|
276
276
|
let failed = 0;
|
|
277
277
|
let skipped = 0;
|
|
@@ -303,7 +303,7 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
303
303
|
extractFailures(output) {
|
|
304
304
|
const failures = [];
|
|
305
305
|
|
|
306
|
-
// Gradle
|
|
306
|
+
// Gradle shows failures in a specific format
|
|
307
307
|
const failureRegex = /(\w+Test)\s+>\s+(\w+).*FAILED/g;
|
|
308
308
|
let match;
|
|
309
309
|
|
|
@@ -323,7 +323,7 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
323
323
|
// ==========================================
|
|
324
324
|
|
|
325
325
|
getFileExtensions() {
|
|
326
|
-
return ['.java', '.kt']; //
|
|
326
|
+
return ['.java', '.kt']; // Kotlin support as well
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
async findIntegrations(workspacePath) {
|
|
@@ -333,7 +333,7 @@ export class GradleRuntime extends BaseRuntime {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
async analyzeImports(filePath, content) {
|
|
336
|
-
//
|
|
336
|
+
// Same logic as Maven
|
|
337
337
|
const imports = [];
|
|
338
338
|
const importRegex = /import\s+(static\s+)?([a-zA-Z0-9_.]+)(?:\.\*)?;/g;
|
|
339
339
|
|