fix(cli): disable non-interactive auto-install for required tools
This commit is contained in:
parent
c5cc0620e2
commit
c19aaf6967
|
|
@ -606,6 +606,72 @@ describe("start command — URL argument", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("start command — non-interactive install safety", () => {
|
||||
function hasPrivilegedInstallAttempt(): boolean {
|
||||
return mockExec.mock.calls.some((call) => {
|
||||
const cmd = String(call[0]);
|
||||
const args = Array.isArray(call[1]) ? (call[1] as string[]) : [];
|
||||
const joined = `${cmd} ${args.join(" ")}`;
|
||||
return joined.includes(" install ") && (cmd === "sudo" || cmd === "brew" || cmd === "winget");
|
||||
});
|
||||
}
|
||||
|
||||
it("does not auto-install tmux when missing in non-interactive mode", async () => {
|
||||
const { isHumanCaller } = await import("../../src/lib/caller-context.js");
|
||||
vi.mocked(isHumanCaller).mockReturnValue(false);
|
||||
|
||||
mockConfigRef.current = makeConfig({ "my-app": makeProject() });
|
||||
mockExecSilent.mockImplementation(async (cmd: string, args: string[] = []) => {
|
||||
if (cmd === "git" && args[0] === "--version") return "git version 2.43.0";
|
||||
if (cmd === "tmux" && args[0] === "-V") return null;
|
||||
if (cmd === "gh" && args[0] === "--version") return null;
|
||||
if (cmd === "gh" && args[0] === "auth" && args[1] === "status") return null;
|
||||
return null;
|
||||
});
|
||||
|
||||
await expect(
|
||||
program.parseAsync(["node", "test", "start", "--no-dashboard", "--no-orchestrator"]),
|
||||
).rejects.toThrow("process.exit(1)");
|
||||
|
||||
expect(hasPrivilegedInstallAttempt()).toBe(false);
|
||||
expect(mockExec.mock.calls.some((call) => String(call[0]) === "tmux")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not auto-install git when missing in non-interactive URL start", async () => {
|
||||
const { isHumanCaller } = await import("../../src/lib/caller-context.js");
|
||||
vi.mocked(isHumanCaller).mockReturnValue(false);
|
||||
|
||||
mockCwd(tmpDir);
|
||||
mockExecSilent.mockImplementation(async (cmd: string, args: string[] = []) => {
|
||||
if (cmd === "git" && args[0] === "--version") return null;
|
||||
if (cmd === "tmux" && args[0] === "-V") return "tmux 3.4";
|
||||
if (cmd === "gh" && args[0] === "--version") return null;
|
||||
if (cmd === "gh" && args[0] === "auth" && args[1] === "status") return null;
|
||||
return null;
|
||||
});
|
||||
|
||||
await expect(
|
||||
program.parseAsync([
|
||||
"node",
|
||||
"test",
|
||||
"start",
|
||||
"https://github.com/owner/nonexistent",
|
||||
"--no-dashboard",
|
||||
"--no-orchestrator",
|
||||
]),
|
||||
).rejects.toThrow("process.exit(1)");
|
||||
|
||||
expect(hasPrivilegedInstallAttempt()).toBe(false);
|
||||
expect(
|
||||
mockExec.mock.calls.some((call) => {
|
||||
const cmd = String(call[0]);
|
||||
const args = Array.isArray(call[1]) ? (call[1] as string[]) : [];
|
||||
return cmd === "git" && args[0] === "clone";
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// waitForPortAndOpen — port polling logic
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -148,8 +148,12 @@ function canPromptForInstall(): boolean {
|
|||
return isHumanCaller() && IS_TTY;
|
||||
}
|
||||
|
||||
async function askYesNo(question: string, defaultYes = true): Promise<boolean> {
|
||||
if (!canPromptForInstall()) return defaultYes;
|
||||
async function askYesNo(
|
||||
question: string,
|
||||
defaultYes = true,
|
||||
nonInteractiveDefault = defaultYes,
|
||||
): Promise<boolean> {
|
||||
if (!canPromptForInstall()) return nonInteractiveDefault;
|
||||
|
||||
const { createInterface } = await import("node:readline/promises");
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
|
|
@ -238,7 +242,7 @@ async function ensureGit(context: string): Promise<void> {
|
|||
if (hasGit) return;
|
||||
|
||||
console.log(chalk.yellow(`⚠ Git is required for ${context}.`));
|
||||
const shouldInstall = await askYesNo("Install Git now?", true);
|
||||
const shouldInstall = await askYesNo("Install Git now?", true, false);
|
||||
if (shouldInstall) {
|
||||
const installed = await tryInstallWithAttempts(
|
||||
gitInstallAttempts(),
|
||||
|
|
@ -748,7 +752,7 @@ async function ensureTmux(): Promise<void> {
|
|||
if (hasTmux) return;
|
||||
|
||||
console.log(chalk.yellow("⚠ tmux is required for runtime \"tmux\"."));
|
||||
const shouldInstall = await askYesNo("Install tmux now?", true);
|
||||
const shouldInstall = await askYesNo("Install tmux now?", true, false);
|
||||
if (shouldInstall) {
|
||||
const installed = await tryInstallWithAttempts(
|
||||
tmuxInstallAttempts(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue