const { execSync } = require("child_process");
function sanitizedEnv(base = process.env) {
const env = { ...base };
delete env.TMUX;
delete env.TMUX_PANE;
return env;
}
const SOCKET_FLAG_RE = /(?:^|\s)-(?:L|S)\s+(\S+)/;
function isDelegatedCommand(cmdPrefix) {
return !/^\s*tmux(\s|$)/.test(cmdPrefix);
}
function resolveSocketName(cmdPrefix) {
const match = cmdPrefix.match(SOCKET_FLAG_RE);
return match ? match[1] : null;
}
function assertIsolated(cmdPrefix, args) {
const delegated = isDelegatedCommand(cmdPrefix);
const socket = resolveSocketName(cmdPrefix);
if (delegated || (socket && socket !== "default")) return;
throw new Error(
`tmux-safety: refusing "${cmdPrefix} ${args}" — no explicit -L/-S test ` +
`socket and not a delegated runner (e.g. podman exec ... tmux). This ` +
`would target the host's default tmux server. See issue #183.`,
);
}
function buildInvocation(cmdPrefix, args) {
assertIsolated(cmdPrefix, args);
return {
command: `${cmdPrefix} ${args}`,
options: { stdio: "pipe", env: sanitizedEnv() },
};
}
function createTmuxRunner(
defaultSocket,
{ envVar = "MOBUX_TEST_TMUX", exec = execSync } = {},
) {
const cmdPrefix =
(envVar && process.env[envVar]) || `tmux -L ${defaultSocket}`;
return (args) => {
const { command, options } = buildInvocation(cmdPrefix, args);
return exec(command, options);
};
}
async function waitForClientAttached(tmux, session, { timeoutMs = 8000 } = {}) {
const deadline = Date.now() + timeoutMs;
for (;;) {
let out = "";
try {
out = tmux(`list-clients -t ${session} -F x`).toString().trim();
} catch (_) {
}
if (out) return;
if (Date.now() > deadline) {
throw new Error(
`tmux reported no client attached to '${session}' within ${timeoutMs}ms`,
);
}
await new Promise((res) => setTimeout(res, 50));
}
}
module.exports = {
createTmuxRunner,
sanitizedEnv,
assertIsolated,
buildInvocation,
waitForClientAttached,
};