import { constants } from "node:fs"; import { access } from "node:fs/promises"; import * as directory from "./directory"; import type { Diagnostics } from "./protocol"; import * as tailnet from "./tailnet"; async function git(): Promise { const path = Bun.which("git", { PATH: process.env.PATH }) || undefined; if (!path) return { name: "Git", error: "Install Git to use project folders." }; // Apple's git shim opens an installer; a diagnostic must only report missing tools. if (process.platform === "darwin" && path === "/usr/bin/git") { const tools = Bun.spawnSync(["/usr/bin/xcode-select", "--print-path"], { stderr: "ignore" }); if (!tools.success) { return { name: "Git", path, error: "Install Xcode Command Line Tools to use Git." }; } } const child = Bun.spawn([path, "--version"], { stdout: "pipe", stderr: "ignore", timeout: 3000 }); const [version, code] = await Promise.all([new Response(child.stdout).text(), child.exited]); return code === 0 ? { name: "Git", path, version: version.trim() } : { name: "Git", path, error: "Git could not start." }; } async function shell(): Promise { const path = process.env.SHELL || "/bin/sh"; try { await access(path, constants.X_OK); return { name: "Shell", path }; } catch { return { name: "Shell", path, error: "The login shell is not executable." }; } } export async function diagnostics(): Promise { const [tools, tailscale] = await Promise.all([ Promise.all([git(), shell()]), tailnet.diagnostics(), ]); return { tools, tailscale, directory: directory.status() }; }