Fix tmux detection for non-POSIX shells (#1030)

This commit is contained in:
ZacharyZcR
2026-07-13 22:59:42 +08:00
committed by GitHub
parent 1c18620629
commit 6f6908bdf4
3 changed files with 40 additions and 7 deletions
+35 -3
View File
@@ -1,20 +1,52 @@
import { EventEmitter } from "node:events";
import type { Client } from "ssh2";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { tmuxCommand, withTmuxPath } from "./tmux-helper.js"; import { detectTmux, tmuxCommand, withTmuxPath } from "./tmux-helper.js";
describe("tmux command path handling", () => { describe("tmux command path handling", () => {
it("adds common non-login shell tmux paths", () => { it("adds common non-login shell tmux paths", () => {
const command = withTmuxPath("command -v tmux"); const command = withTmuxPath("command -v tmux");
expect(command).toMatch(/^\/bin\/sh -c '/);
expect(command).toContain("/opt/homebrew/bin"); expect(command).toContain("/opt/homebrew/bin");
expect(command).toContain("/usr/local/bin"); expect(command).toContain("/usr/local/bin");
expect(command).toContain("/opt/bin"); expect(command).toContain("/opt/bin");
expect(command).toContain("/usr/pkg/bin"); expect(command).toContain("/usr/pkg/bin");
expect(command).toContain(":$PATH; command -v tmux"); expect(command).toContain(":$PATH; export PATH; command -v tmux");
}); });
it("wraps tmux invocations with the same path", () => { it("wraps tmux invocations with the same path", () => {
expect(tmuxCommand("list-sessions")).toMatch( expect(tmuxCommand("list-sessions")).toMatch(
/^PATH=.*:\$PATH; tmux list-sessions$/, /^\/bin\/sh -c 'PATH=.*:\$PATH; export PATH; tmux list-sessions'$/,
); );
}); });
it("detects suffixed tmux versions without parsing the version number", async () => {
const commands: string[] = [];
const conn = {
exec(command: string, callback: (error: null, stream: never) => void) {
commands.push(command);
const stream = new EventEmitter() as EventEmitter & {
stderr: EventEmitter;
};
stream.stderr = new EventEmitter();
callback(null, stream as never);
queueMicrotask(() => {
if (commands.length === 1) {
stream.emit("data", Buffer.from("tmux 3.7b\n"));
stream.emit("close", 0);
return;
}
stream.emit("close", 1);
});
},
} as unknown as Client;
await expect(detectTmux(conn)).resolves.toEqual({
available: true,
sessions: [],
});
expect(commands[0]).toContain("tmux -V");
});
}); });
+3 -2
View File
@@ -22,7 +22,8 @@ const TMUX_PATH_DIRS = [
]; ];
export function withTmuxPath(command: string): string { export function withTmuxPath(command: string): string {
return `PATH=${TMUX_PATH_DIRS.join(":")}:$PATH; ${command}`; const script = `PATH=${TMUX_PATH_DIRS.join(":")}:$PATH; export PATH; ${command}`;
return `/bin/sh -c ${shellEscape(script)}`;
} }
export function tmuxCommand(args: string): string { export function tmuxCommand(args: string): string {
@@ -69,7 +70,7 @@ export function execCommand(conn: Client, command: string): Promise<string> {
*/ */
export async function detectTmux(conn: Client): Promise<TmuxDetectionResult> { export async function detectTmux(conn: Client): Promise<TmuxDetectionResult> {
try { try {
await execCommand(conn, withTmuxPath("command -v tmux")); await execCommand(conn, tmuxCommand("-V"));
} catch { } catch {
return { available: false, sessions: [] }; return { available: false, sessions: [] };
} }
+2 -2
View File
@@ -21,7 +21,7 @@ import {
} from "../utils/socks5-helper.js"; } from "../utils/socks5-helper.js";
import { withConnection } from "./ssh-connection-pool.js"; import { withConnection } from "./ssh-connection-pool.js";
import { resolveSshConnectConfigHost } from "./ssh-dns.js"; import { resolveSshConnectConfigHost } from "./ssh-dns.js";
import { execCommand, tmuxCommand, withTmuxPath } from "./tmux-helper.js"; import { execCommand, tmuxCommand } from "./tmux-helper.js";
import { import {
SEP, SEP,
parseSessions, parseSessions,
@@ -246,7 +246,7 @@ async function withHostConnection<T>(
async function tmuxAvailable(conn: Client): Promise<boolean> { async function tmuxAvailable(conn: Client): Promise<boolean> {
try { try {
await execCommand(conn, withTmuxPath("command -v tmux")); await execCommand(conn, tmuxCommand("-V"));
return true; return true;
} catch { } catch {
return false; return false;