shell.ts 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import { Flag } from "@/flag/flag"
  2. import { lazy } from "@/util/lazy"
  3. import path from "path"
  4. import { spawn, type ChildProcess } from "child_process"
  5. const SIGKILL_TIMEOUT_MS = 200
  6. export namespace Shell {
  7. export async function killTree(proc: ChildProcess, opts?: { exited?: () => boolean }): Promise<void> {
  8. const pid = proc.pid
  9. if (!pid || opts?.exited?.()) return
  10. if (process.platform === "win32") {
  11. await new Promise<void>((resolve) => {
  12. const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], { stdio: "ignore" })
  13. killer.once("exit", () => resolve())
  14. killer.once("error", () => resolve())
  15. })
  16. return
  17. }
  18. try {
  19. process.kill(-pid, "SIGTERM")
  20. await Bun.sleep(SIGKILL_TIMEOUT_MS)
  21. if (!opts?.exited?.()) {
  22. process.kill(-pid, "SIGKILL")
  23. }
  24. } catch (_e) {
  25. proc.kill("SIGTERM")
  26. await Bun.sleep(SIGKILL_TIMEOUT_MS)
  27. if (!opts?.exited?.()) {
  28. proc.kill("SIGKILL")
  29. }
  30. }
  31. }
  32. const BLACKLIST = new Set(["fish", "nu"])
  33. function fallback() {
  34. if (process.platform === "win32") {
  35. if (Flag.OPENCODE_GIT_BASH_PATH) return Flag.OPENCODE_GIT_BASH_PATH
  36. const git = Bun.which("git")
  37. if (git) {
  38. // git.exe is typically at: C:\Program Files\Git\cmd\git.exe
  39. // bash.exe is at: C:\Program Files\Git\bin\bash.exe
  40. const bash = path.join(git, "..", "..", "bin", "bash.exe")
  41. if (Bun.file(bash).size) return bash
  42. }
  43. return process.env.COMSPEC || "cmd.exe"
  44. }
  45. if (process.platform === "darwin") return "/bin/zsh"
  46. const bash = Bun.which("bash")
  47. if (bash) return bash
  48. return "/bin/sh"
  49. }
  50. export const preferred = lazy(() => {
  51. const s = process.env.SHELL
  52. if (s) return s
  53. return fallback()
  54. })
  55. export const acceptable = lazy(() => {
  56. const s = process.env.SHELL
  57. if (s && !BLACKLIST.has(process.platform === "win32" ? path.win32.basename(s) : path.basename(s))) return s
  58. return fallback()
  59. })
  60. }