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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bind-host.js — which interface the agent's HTTP server listens on.
|
|
3
|
+
*
|
|
4
|
+
* Fail-closed default (security review 2026-10-11, C1): the agent only listens
|
|
5
|
+
* on all interfaces (0.0.0.0) when it is meant to be reached remotely, i.e. the
|
|
6
|
+
* auth mode is `token` (LOCAL_AGENT_TOKEN / AGENT_SHARED_SECRET), Cloud Run
|
|
7
|
+
* `compat` (IAM is the perimeter) or an explicit opt-out. Otherwise (the
|
|
8
|
+
* implicit `loopback` mode) it binds 127.0.0.1, so the port is not even open
|
|
9
|
+
* to the LAN. LOCAL_AGENT_BIND_HOST always wins. BYOC Azure (ACI)
|
|
10
|
+
* container groups put a TLS-terminating Caddy sidecar in front of the agent
|
|
11
|
+
* and set LOCAL_AGENT_BIND_HOST=127.0.0.1 so the agent is reachable only
|
|
12
|
+
* through the sidecar (containers in an ACI group share localhost).
|
|
13
|
+
*/
|
|
14
|
+
import net from "node:net";
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_BIND_HOST = "0.0.0.0";
|
|
17
|
+
export const LOOPBACK_BIND_HOST = "127.0.0.1";
|
|
18
|
+
|
|
19
|
+
/** @param authCfg result of resolveAuthConfig(); without it the safe loopback default applies. */
|
|
20
|
+
export function resolveBindHost(env = process.env, authCfg = null) {
|
|
21
|
+
const raw = (env.LOCAL_AGENT_BIND_HOST || "").trim();
|
|
22
|
+
const remoteCapable = Boolean(authCfg) && ["token", "compat", "off"].includes(authCfg.mode);
|
|
23
|
+
const fallback = remoteCapable ? DEFAULT_BIND_HOST : LOOPBACK_BIND_HOST;
|
|
24
|
+
if (!raw) return fallback;
|
|
25
|
+
if (raw === "localhost") return "127.0.0.1";
|
|
26
|
+
if (net.isIP(raw)) return raw;
|
|
27
|
+
console.warn(`[bind-host] ignoring invalid LOCAL_AGENT_BIND_HOST=${JSON.stringify(raw)}; using ${fallback}`);
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BRANCH REFSPEC FIX - explicit ref fetch for /workspace/open REUSE paths.
|
|
3
|
+
*
|
|
4
|
+
* Problem (proven in QA, 2026-07-20, agent rev 00021-kd8): the initial clone
|
|
5
|
+
* uses `git clone --branch <b> --single-branch`, which restricts the remote
|
|
6
|
+
* refspec to that ONE branch. When a LATER /workspace/open of the SAME repo
|
|
7
|
+
* asks for a DIFFERENT branch (e.g. the auto-review of a PR branch created
|
|
8
|
+
* minutes earlier), the reuse path runs `git fetch origin` (which only
|
|
9
|
+
* fetches the restricted refspec), swallows the checkout error, and dies on
|
|
10
|
+
* `git reset --hard origin/<branch>` with "fatal: ambiguous argument ...
|
|
11
|
+
* unknown revision" -> HTTP 500 -> review FAILED (PR #13 row in
|
|
12
|
+
* ai_pr_reviews). The fix: BEFORE checkout/reset, fetch the requested ref
|
|
13
|
+
* explicitly with `+refs/heads/<b>:refs/remotes/origin/<b>` so single-branch
|
|
14
|
+
* clones can see branches born after the original clone.
|
|
15
|
+
*
|
|
16
|
+
* Kept pure/dependency-injected so guard tests can pin every decision
|
|
17
|
+
* without a running server (same style as utils/open-gate.js).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Shell-safety + git-ref sanity predicate for a requested branch name.
|
|
22
|
+
* The reuse path interpolates the branch into a shell command line, so we
|
|
23
|
+
* fail CLOSED on anything outside a conservative charset (no quotes, no
|
|
24
|
+
* whitespace, no $ ` ; & | > < ( ) { } \ ~ ^ : ? * [ @{ ), plus the git
|
|
25
|
+
* ref rules that matter here (no "..", no "//", no leading "-" or ".",
|
|
26
|
+
* no trailing "/" or ".lock").
|
|
27
|
+
*/
|
|
28
|
+
export function isSafeBranchName(branch) {
|
|
29
|
+
if (typeof branch !== "string") return false;
|
|
30
|
+
const b = branch.trim();
|
|
31
|
+
if (!b || b.length > 255) return false;
|
|
32
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(b)) return false;
|
|
33
|
+
if (b.includes("..") || b.includes("//")) return false;
|
|
34
|
+
if (b.endsWith("/") || b.endsWith(".lock")) return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The explicit refspec that makes a single-branch clone see the requested
|
|
40
|
+
* branch: +refs/heads/<b>:refs/remotes/origin/<b>. Caller MUST validate the
|
|
41
|
+
* branch with isSafeBranchName first; this builder throws on unsafe input
|
|
42
|
+
* as defense in depth (never silently emits an injectable string).
|
|
43
|
+
*/
|
|
44
|
+
export function explicitBranchRefspec(branch) {
|
|
45
|
+
if (!isSafeBranchName(branch)) {
|
|
46
|
+
throw new Error(`unsafe branch name for refspec: ${String(branch)}`);
|
|
47
|
+
}
|
|
48
|
+
const b = branch.trim();
|
|
49
|
+
return `+refs/heads/${b}:refs/remotes/origin/${b}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Does a `git fetch origin <refspec>` failure mean the branch does not
|
|
54
|
+
* exist on the remote (as opposed to auth/network/other errors)?
|
|
55
|
+
* git says: "fatal: couldn't find remote ref refs/heads/<b>" and clones say
|
|
56
|
+
* "Remote branch <b> not found in upstream origin".
|
|
57
|
+
*/
|
|
58
|
+
export function isRemoteBranchMissing(message) {
|
|
59
|
+
if (typeof message !== "string" || !message) return false;
|
|
60
|
+
const m = message.toLowerCase();
|
|
61
|
+
return m.includes("couldn't find remote ref")
|
|
62
|
+
|| m.includes("could not find remote ref")
|
|
63
|
+
|| (m.includes("remote branch") && m.includes("not found"));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fetch the requested ref explicitly on a REUSE path, with an injectable
|
|
68
|
+
* exec so guard tests can pin the exact command without git or a server.
|
|
69
|
+
*
|
|
70
|
+
* Throws tagged errors the HTTP handlers translate into CLEAR responses
|
|
71
|
+
* (never a generic 500):
|
|
72
|
+
* .code = "INVALID_BRANCH" .httpStatus = 400 (shell-unsafe branch name)
|
|
73
|
+
* .code = "BRANCH_NOT_FOUND" .httpStatus = 404 (branch absent on remote)
|
|
74
|
+
* Any other fetch failure is rethrown untouched (existing catch semantics).
|
|
75
|
+
*
|
|
76
|
+
* @param {(cmd:string, opts:object)=>Promise<any>} execFn e.g. execAsync
|
|
77
|
+
* @param {string} branch requested branch
|
|
78
|
+
* @param {string} cwd repo dir on disk
|
|
79
|
+
*/
|
|
80
|
+
export async function fetchRequestedRef(execFn, branch, cwd) {
|
|
81
|
+
if (!isSafeBranchName(branch)) {
|
|
82
|
+
const err = new Error(`invalid branch name: ${String(branch)}`);
|
|
83
|
+
err.code = "INVALID_BRANCH";
|
|
84
|
+
err.httpStatus = 400;
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
await execFn(`git fetch origin "${explicitBranchRefspec(branch)}"`, { cwd, timeout: 120000 });
|
|
89
|
+
} catch (fetchErr) {
|
|
90
|
+
if (isRemoteBranchMissing(fetchErr && fetchErr.message)) {
|
|
91
|
+
const err = new Error(`branch not found on remote: ${branch.trim()}`);
|
|
92
|
+
err.code = "BRANCH_NOT_FOUND";
|
|
93
|
+
err.httpStatus = 404;
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
throw fetchErr;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* child-env.js — environment handed to commands the MODEL asked us to run
|
|
3
|
+
* (/workspace/exec, /setup/run-command, /mcp/execute-command).
|
|
4
|
+
*
|
|
5
|
+
* WHY. Those routes spawned `{ ...process.env }`, so a command such as `env`
|
|
6
|
+
* or `cat /proc/self/environ` (or any prompt-injected one) read the agent's
|
|
7
|
+
* own credentials: AUTH_TOKEN / DEEPDEBUG_API_KEY (gateway), AGENT_SHARED_SECRET
|
|
8
|
+
* / LOCAL_AGENT_TOKEN (this API), git tokens. The child gets everything else
|
|
9
|
+
* (PATH, HOME, JAVA_HOME, proxies...) so builds and tests still work.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const SECRET_NAME = /(TOKEN|SECRET|PASSWORD|PASSWD|API[_-]?KEY|APIKEY|CREDENTIAL|PRIVATE[_-]?KEY|SIGNING[_-]?KEY)/i;
|
|
13
|
+
|
|
14
|
+
/** Names that match the pattern but carry no secret and are needed by git/tools. */
|
|
15
|
+
const KEEP = new Set([
|
|
16
|
+
"GIT_TERMINAL_PROMPT", "GIT_ASKPASS", "GIT_SSH_COMMAND", "SSH_AUTH_SOCK",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export function isSecretEnvName(name) {
|
|
20
|
+
return !KEEP.has(name) && SECRET_NAME.test(String(name));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Copy of `env` without secret-looking variables, plus `extra` (which is never
|
|
25
|
+
* filtered: the caller is explicit about what it adds).
|
|
26
|
+
*/
|
|
27
|
+
export function sanitizedChildEnv(env = process.env, extra = {}) {
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const [k, v] of Object.entries(env)) {
|
|
30
|
+
if (v === undefined || isSecretEnvName(k)) continue;
|
|
31
|
+
out[k] = v;
|
|
32
|
+
}
|
|
33
|
+
return { ...out, ...extra };
|
|
34
|
+
}
|
|
@@ -8,22 +8,22 @@ const execAsync = promisify(exec);
|
|
|
8
8
|
/**
|
|
9
9
|
* EnvironmentDiagnostics
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Automatically detects development tools installed on the system.
|
|
12
|
+
* Useful for debugging and environment setup.
|
|
13
13
|
*/
|
|
14
14
|
export class EnvironmentDiagnostics {
|
|
15
15
|
|
|
16
16
|
constructor() {
|
|
17
17
|
this.cache = null;
|
|
18
18
|
this.cacheTime = 0;
|
|
19
|
-
this.cacheTTL = 60000; // 1
|
|
19
|
+
this.cacheTTL = 60000; // 1 minute
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Runs a full environment diagnostic
|
|
24
24
|
*/
|
|
25
25
|
async diagnose() {
|
|
26
|
-
//
|
|
26
|
+
// Use the cache if still valid
|
|
27
27
|
if (this.cache && (Date.now() - this.cacheTime) < this.cacheTTL) {
|
|
28
28
|
return this.cache;
|
|
29
29
|
}
|
|
@@ -89,7 +89,7 @@ export class EnvironmentDiagnostics {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
92
|
+
* System information
|
|
93
93
|
*/
|
|
94
94
|
async getSystemInfo() {
|
|
95
95
|
return {
|
|
@@ -110,7 +110,7 @@ export class EnvironmentDiagnostics {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
113
|
+
* Detects Java
|
|
114
114
|
*/
|
|
115
115
|
async detectJava() {
|
|
116
116
|
const result = {
|
|
@@ -122,19 +122,19 @@ export class EnvironmentDiagnostics {
|
|
|
122
122
|
};
|
|
123
123
|
|
|
124
124
|
try {
|
|
125
|
-
//
|
|
125
|
+
// Try java -version
|
|
126
126
|
const { stderr } = await execAsync('java -version 2>&1');
|
|
127
127
|
const output = stderr || '';
|
|
128
128
|
|
|
129
129
|
result.installed = true;
|
|
130
130
|
|
|
131
|
-
//
|
|
131
|
+
// Extract the version (e.g.: "openjdk version "17.0.1"")
|
|
132
132
|
const versionMatch = output.match(/version\s+"([^"]+)"/);
|
|
133
133
|
if (versionMatch) {
|
|
134
134
|
result.version = versionMatch[1];
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
//
|
|
137
|
+
// Extract the vendor
|
|
138
138
|
if (output.includes('OpenJDK')) {
|
|
139
139
|
result.vendor = 'OpenJDK';
|
|
140
140
|
} else if (output.includes('Oracle')) {
|
|
@@ -147,7 +147,7 @@ export class EnvironmentDiagnostics {
|
|
|
147
147
|
result.vendor = 'Eclipse Temurin';
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
//
|
|
150
|
+
// Find the path
|
|
151
151
|
const { stdout: whichOutput } = await execAsync('which java').catch(() => ({ stdout: '' }));
|
|
152
152
|
result.path = whichOutput.trim() || null;
|
|
153
153
|
|
|
@@ -159,7 +159,7 @@ export class EnvironmentDiagnostics {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
/**
|
|
162
|
-
*
|
|
162
|
+
* Detects Maven
|
|
163
163
|
*/
|
|
164
164
|
async detectMaven() {
|
|
165
165
|
const result = {
|
|
@@ -174,19 +174,19 @@ export class EnvironmentDiagnostics {
|
|
|
174
174
|
|
|
175
175
|
result.installed = true;
|
|
176
176
|
|
|
177
|
-
//
|
|
177
|
+
// Extract the version (e.g.: "Apache Maven 3.9.0")
|
|
178
178
|
const versionMatch = stdout.match(/Apache Maven (\d+\.\d+\.\d+)/);
|
|
179
179
|
if (versionMatch) {
|
|
180
180
|
result.version = versionMatch[1];
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// Extract the Java version used by Maven
|
|
184
184
|
const javaMatch = stdout.match(/Java version: ([^\n,]+)/);
|
|
185
185
|
if (javaMatch) {
|
|
186
186
|
result.javaVersion = javaMatch[1].trim();
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
//
|
|
189
|
+
// Find the path
|
|
190
190
|
const { stdout: whichOutput } = await execAsync('which mvn').catch(() => ({ stdout: '' }));
|
|
191
191
|
result.path = whichOutput.trim() || null;
|
|
192
192
|
|
|
@@ -198,7 +198,7 @@ export class EnvironmentDiagnostics {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
|
-
*
|
|
201
|
+
* Detects Gradle
|
|
202
202
|
*/
|
|
203
203
|
async detectGradle() {
|
|
204
204
|
const result = {
|
|
@@ -213,13 +213,13 @@ export class EnvironmentDiagnostics {
|
|
|
213
213
|
|
|
214
214
|
result.installed = true;
|
|
215
215
|
|
|
216
|
-
//
|
|
216
|
+
// Extract the version (e.g.: "Gradle 8.0")
|
|
217
217
|
const versionMatch = stdout.match(/Gradle (\d+\.\d+\.?\d*)/);
|
|
218
218
|
if (versionMatch) {
|
|
219
219
|
result.version = versionMatch[1];
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
//
|
|
222
|
+
// Find the path
|
|
223
223
|
const { stdout: whichOutput } = await execAsync('which gradle').catch(() => ({ stdout: '' }));
|
|
224
224
|
result.path = whichOutput.trim() || null;
|
|
225
225
|
|
|
@@ -231,7 +231,7 @@ export class EnvironmentDiagnostics {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
234
|
-
*
|
|
234
|
+
* Detects Node.js
|
|
235
235
|
*/
|
|
236
236
|
async detectNode() {
|
|
237
237
|
const result = {
|
|
@@ -246,7 +246,7 @@ export class EnvironmentDiagnostics {
|
|
|
246
246
|
result.installed = true;
|
|
247
247
|
result.version = stdout.trim().replace('v', '');
|
|
248
248
|
|
|
249
|
-
//
|
|
249
|
+
// Find the path
|
|
250
250
|
const { stdout: whichOutput } = await execAsync('which node').catch(() => ({ stdout: '' }));
|
|
251
251
|
result.path = whichOutput.trim() || null;
|
|
252
252
|
|
|
@@ -258,7 +258,7 @@ export class EnvironmentDiagnostics {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
/**
|
|
261
|
-
*
|
|
261
|
+
* Detects npm
|
|
262
262
|
*/
|
|
263
263
|
async detectNpm() {
|
|
264
264
|
const result = {
|
|
@@ -278,7 +278,7 @@ export class EnvironmentDiagnostics {
|
|
|
278
278
|
const { stdout: prefixOutput } = await execAsync('npm config get prefix').catch(() => ({ stdout: '' }));
|
|
279
279
|
result.globalPrefix = prefixOutput.trim() || null;
|
|
280
280
|
|
|
281
|
-
//
|
|
281
|
+
// Find the path
|
|
282
282
|
const { stdout: whichOutput } = await execAsync('which npm').catch(() => ({ stdout: '' }));
|
|
283
283
|
result.path = whichOutput.trim() || null;
|
|
284
284
|
|
|
@@ -290,7 +290,7 @@ export class EnvironmentDiagnostics {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
|
-
*
|
|
293
|
+
* Detects Yarn
|
|
294
294
|
*/
|
|
295
295
|
async detectYarn() {
|
|
296
296
|
const result = {
|
|
@@ -305,7 +305,7 @@ export class EnvironmentDiagnostics {
|
|
|
305
305
|
result.installed = true;
|
|
306
306
|
result.version = stdout.trim();
|
|
307
307
|
|
|
308
|
-
//
|
|
308
|
+
// Find the path
|
|
309
309
|
const { stdout: whichOutput } = await execAsync('which yarn').catch(() => ({ stdout: '' }));
|
|
310
310
|
result.path = whichOutput.trim() || null;
|
|
311
311
|
|
|
@@ -317,7 +317,7 @@ export class EnvironmentDiagnostics {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
/**
|
|
320
|
-
*
|
|
320
|
+
* Detects Python
|
|
321
321
|
*/
|
|
322
322
|
async detectPython() {
|
|
323
323
|
const result = {
|
|
@@ -328,21 +328,21 @@ export class EnvironmentDiagnostics {
|
|
|
328
328
|
};
|
|
329
329
|
|
|
330
330
|
try {
|
|
331
|
-
//
|
|
331
|
+
// Try python3 first
|
|
332
332
|
const { stdout } = await execAsync('python3 --version 2>&1').catch(() =>
|
|
333
333
|
execAsync('python --version 2>&1')
|
|
334
334
|
);
|
|
335
335
|
|
|
336
336
|
result.installed = true;
|
|
337
337
|
|
|
338
|
-
//
|
|
338
|
+
// Extract the version (e.g.: "Python 3.11.0")
|
|
339
339
|
const versionMatch = stdout.match(/Python (\d+\.\d+\.?\d*)/);
|
|
340
340
|
if (versionMatch) {
|
|
341
341
|
result.version = versionMatch[1];
|
|
342
342
|
result.python3 = result.version.startsWith('3');
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
//
|
|
345
|
+
// Find the path
|
|
346
346
|
const { stdout: whichOutput } = await execAsync('which python3 || which python').catch(() => ({ stdout: '' }));
|
|
347
347
|
result.path = whichOutput.trim() || null;
|
|
348
348
|
|
|
@@ -354,7 +354,7 @@ export class EnvironmentDiagnostics {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
/**
|
|
357
|
-
*
|
|
357
|
+
* Detects pip
|
|
358
358
|
*/
|
|
359
359
|
async detectPip() {
|
|
360
360
|
const result = {
|
|
@@ -370,13 +370,13 @@ export class EnvironmentDiagnostics {
|
|
|
370
370
|
|
|
371
371
|
result.installed = true;
|
|
372
372
|
|
|
373
|
-
//
|
|
373
|
+
// Extract the version (e.g.: "pip 23.0.1")
|
|
374
374
|
const versionMatch = stdout.match(/pip (\d+\.\d+\.?\d*)/);
|
|
375
375
|
if (versionMatch) {
|
|
376
376
|
result.version = versionMatch[1];
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
-
//
|
|
379
|
+
// Find the path
|
|
380
380
|
const { stdout: whichOutput } = await execAsync('which pip3 || which pip').catch(() => ({ stdout: '' }));
|
|
381
381
|
result.path = whichOutput.trim() || null;
|
|
382
382
|
|
|
@@ -388,7 +388,7 @@ export class EnvironmentDiagnostics {
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
/**
|
|
391
|
-
*
|
|
391
|
+
* Detects Go
|
|
392
392
|
*/
|
|
393
393
|
async detectGo() {
|
|
394
394
|
const result = {
|
|
@@ -404,13 +404,13 @@ export class EnvironmentDiagnostics {
|
|
|
404
404
|
|
|
405
405
|
result.installed = true;
|
|
406
406
|
|
|
407
|
-
//
|
|
407
|
+
// Extract the version (e.g.: "go version go1.21.0 darwin/amd64")
|
|
408
408
|
const versionMatch = stdout.match(/go(\d+\.\d+\.?\d*)/);
|
|
409
409
|
if (versionMatch) {
|
|
410
410
|
result.version = versionMatch[1];
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
//
|
|
413
|
+
// Find the path
|
|
414
414
|
const { stdout: whichOutput } = await execAsync('which go').catch(() => ({ stdout: '' }));
|
|
415
415
|
result.path = whichOutput.trim() || null;
|
|
416
416
|
|
|
@@ -422,7 +422,7 @@ export class EnvironmentDiagnostics {
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
/**
|
|
425
|
-
*
|
|
425
|
+
* Detects .NET
|
|
426
426
|
*/
|
|
427
427
|
async detectDotnet() {
|
|
428
428
|
const result = {
|
|
@@ -447,7 +447,7 @@ export class EnvironmentDiagnostics {
|
|
|
447
447
|
const { stdout: runtimeOutput } = await execAsync('dotnet --list-runtimes 2>&1').catch(() => ({ stdout: '' }));
|
|
448
448
|
result.runtimes = runtimeOutput.trim().split('\n').filter(Boolean).slice(0, 5); // Limitar
|
|
449
449
|
|
|
450
|
-
//
|
|
450
|
+
// Find the path
|
|
451
451
|
const { stdout: whichOutput } = await execAsync('which dotnet').catch(() => ({ stdout: '' }));
|
|
452
452
|
result.path = whichOutput.trim() || null;
|
|
453
453
|
|
|
@@ -459,7 +459,7 @@ export class EnvironmentDiagnostics {
|
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
/**
|
|
462
|
-
*
|
|
462
|
+
* Detects Git
|
|
463
463
|
*/
|
|
464
464
|
async detectGit() {
|
|
465
465
|
const result = {
|
|
@@ -475,7 +475,7 @@ export class EnvironmentDiagnostics {
|
|
|
475
475
|
|
|
476
476
|
result.installed = true;
|
|
477
477
|
|
|
478
|
-
//
|
|
478
|
+
// Extract the version (e.g.: "git version 2.39.0")
|
|
479
479
|
const versionMatch = stdout.match(/git version (\d+\.\d+\.?\d*)/);
|
|
480
480
|
if (versionMatch) {
|
|
481
481
|
result.version = versionMatch[1];
|
|
@@ -488,7 +488,7 @@ export class EnvironmentDiagnostics {
|
|
|
488
488
|
const { stdout: emailOutput } = await execAsync('git config --global user.email').catch(() => ({ stdout: '' }));
|
|
489
489
|
result.email = emailOutput.trim() || null;
|
|
490
490
|
|
|
491
|
-
//
|
|
491
|
+
// Find the path
|
|
492
492
|
const { stdout: whichOutput } = await execAsync('which git').catch(() => ({ stdout: '' }));
|
|
493
493
|
result.path = whichOutput.trim() || null;
|
|
494
494
|
|
|
@@ -500,7 +500,7 @@ export class EnvironmentDiagnostics {
|
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
/**
|
|
503
|
-
*
|
|
503
|
+
* Detects Docker
|
|
504
504
|
*/
|
|
505
505
|
async detectDocker() {
|
|
506
506
|
const result = {
|
|
@@ -515,13 +515,13 @@ export class EnvironmentDiagnostics {
|
|
|
515
515
|
|
|
516
516
|
result.installed = true;
|
|
517
517
|
|
|
518
|
-
//
|
|
518
|
+
// Extract the version (e.g.: "Docker version 24.0.0, build xxx")
|
|
519
519
|
const versionMatch = stdout.match(/Docker version (\d+\.\d+\.?\d*)/);
|
|
520
520
|
if (versionMatch) {
|
|
521
521
|
result.version = versionMatch[1];
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
-
//
|
|
524
|
+
// Check whether it is running
|
|
525
525
|
try {
|
|
526
526
|
await execAsync('docker info 2>&1');
|
|
527
527
|
result.running = true;
|
|
@@ -529,7 +529,7 @@ export class EnvironmentDiagnostics {
|
|
|
529
529
|
result.running = false;
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
-
//
|
|
532
|
+
// Find the path
|
|
533
533
|
const { stdout: whichOutput } = await execAsync('which docker').catch(() => ({ stdout: '' }));
|
|
534
534
|
result.path = whichOutput.trim() || null;
|
|
535
535
|
|
|
@@ -541,7 +541,7 @@ export class EnvironmentDiagnostics {
|
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
/**
|
|
544
|
-
*
|
|
544
|
+
* Generates a capabilities summary
|
|
545
545
|
*/
|
|
546
546
|
generateSummary(tools) {
|
|
547
547
|
const capabilities = [];
|
|
@@ -579,47 +579,47 @@ export class EnvironmentDiagnostics {
|
|
|
579
579
|
}
|
|
580
580
|
|
|
581
581
|
/**
|
|
582
|
-
*
|
|
582
|
+
* Detects configuration problems
|
|
583
583
|
*/
|
|
584
584
|
detectIssues(tools) {
|
|
585
585
|
const issues = [];
|
|
586
586
|
|
|
587
|
-
// Java
|
|
587
|
+
// Java without JAVA_HOME
|
|
588
588
|
if (tools.java?.installed && !process.env.JAVA_HOME) {
|
|
589
589
|
issues.push({
|
|
590
590
|
severity: 'warning',
|
|
591
591
|
tool: 'java',
|
|
592
|
-
message: 'JAVA_HOME
|
|
592
|
+
message: 'JAVA_HOME is not configured',
|
|
593
593
|
fix: 'export JAVA_HOME=$(/usr/libexec/java_home)'
|
|
594
594
|
});
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
// Java
|
|
597
|
+
// Java installed but Maven not
|
|
598
598
|
if (tools.java?.installed && !tools.maven?.installed) {
|
|
599
599
|
issues.push({
|
|
600
600
|
severity: 'info',
|
|
601
601
|
tool: 'maven',
|
|
602
|
-
message: 'Maven
|
|
602
|
+
message: 'Maven is not installed (required for Maven projects)',
|
|
603
603
|
fix: 'brew install maven'
|
|
604
604
|
});
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
-
// Node
|
|
607
|
+
// Node without npm
|
|
608
608
|
if (tools.node?.installed && !tools.npm?.installed) {
|
|
609
609
|
issues.push({
|
|
610
610
|
severity: 'error',
|
|
611
611
|
tool: 'npm',
|
|
612
|
-
message: 'Node
|
|
613
|
-
fix: '
|
|
612
|
+
message: 'Node is installed but npm was not found',
|
|
613
|
+
fix: 'Reinstall Node.js'
|
|
614
614
|
});
|
|
615
615
|
}
|
|
616
616
|
|
|
617
|
-
// Python 2
|
|
617
|
+
// Python 2 instead of 3
|
|
618
618
|
if (tools.python?.installed && !tools.python?.python3) {
|
|
619
619
|
issues.push({
|
|
620
620
|
severity: 'warning',
|
|
621
621
|
tool: 'python',
|
|
622
|
-
message: 'Python 2
|
|
622
|
+
message: 'Python 2 detected. Using Python 3 is recommended',
|
|
623
623
|
fix: 'brew install python3'
|
|
624
624
|
});
|
|
625
625
|
}
|
|
@@ -628,7 +628,7 @@ export class EnvironmentDiagnostics {
|
|
|
628
628
|
}
|
|
629
629
|
|
|
630
630
|
/**
|
|
631
|
-
*
|
|
631
|
+
* Quick diagnostic for a single language only
|
|
632
632
|
*/
|
|
633
633
|
async diagnoseFor(language) {
|
|
634
634
|
switch (language) {
|