diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index e434550..cf4892c 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -32,6 +32,7 @@ jobs: sudo apt-get update sudo apt-get install -y hyperfine zsh eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + env -u HOMEBREW_ASK brew update brew install --no-ask atuin fnm fzf zoxide mapfile -t insecure_dirs < <( diff --git a/install.sh b/install.sh index 48e781b..f2338b2 100644 --- a/install.sh +++ b/install.sh @@ -83,6 +83,10 @@ install_with_manager() { case "${manager}" in brew) + if [[ "${HOMEBREW_UPDATED:-0}" != "1" ]]; then + env -u HOMEBREW_ASK brew update + HOMEBREW_UPDATED=1 + fi brew install --no-ask "$@" ;; apt-get) diff --git a/src/steps/bootstrap.js b/src/steps/bootstrap.js index f9f39de..81ed911 100644 --- a/src/steps/bootstrap.js +++ b/src/steps/bootstrap.js @@ -135,7 +135,7 @@ async function installZshViaManager(manager) { p.log.step("Installing Zsh..."); if (manager === "brew") { - await runStream("brew install --no-ask zsh"); + await runStream("env -u HOMEBREW_ASK brew update && brew install --no-ask zsh"); } else if (manager === "apt-get") { await runStream("sudo apt-get update && sudo apt-get install -y zsh"); } else if (manager === "dnf") { diff --git a/src/utils/shell.js b/src/utils/shell.js index 49d3d60..d96ce57 100644 --- a/src/utils/shell.js +++ b/src/utils/shell.js @@ -17,6 +17,23 @@ export class ShellCommandError extends Error { } } +let homebrewUpdated = false; + +export function brewUpdate() { + if (homebrewUpdated) return; + + const cmd = "env -u HOMEBREW_ASK brew update"; + try { + execSync(cmd, { stdio: "inherit" }); + homebrewUpdated = true; + } catch (error) { + throw new ShellCommandError(cmd, { + code: error.status, + signal: error.signal, + }); + } +} + /** * Run a shell command synchronously. Returns stdout as string. * Throws on non-zero exit. @@ -57,13 +74,15 @@ export function brewInstalled(name) { * Install a Homebrew formula or cask. Returns true on success. */ export function brewInstall(name, { cask = false } = {}) { + brewUpdate(); const args = cask ? ["install", "--no-ask", "--cask", name] : ["install", "--no-ask", name]; + const cmd = `brew ${args.join(" ")}`; try { - execSync(`brew ${args.join(" ")}`, { stdio: "inherit" }); + execSync(cmd, { stdio: "inherit" }); return true; } catch (error) { if (error.signal === "SIGINT" || error.status === 130) { - throw new ShellCommandError(`brew ${args.join(" ")}`, { + throw new ShellCommandError(cmd, { code: error.status, signal: error.signal, }); diff --git a/tests/bootstrap.test.js b/tests/bootstrap.test.js index 194975f..27c3c7f 100644 --- a/tests/bootstrap.test.js +++ b/tests/bootstrap.test.js @@ -84,7 +84,9 @@ describe("bootstrap step", () => { await bootstrap({ platform: "darwin" }); - expect(runStream).toHaveBeenCalledWith(expect.stringContaining("brew install --no-ask zsh")); + expect(runStream).toHaveBeenCalledWith( + "env -u HOMEBREW_ASK brew update && brew install --no-ask zsh" + ); }); test("supports Linux package manager selection", async () => { diff --git a/tests/shell.test.js b/tests/shell.test.js index 8e00c1b..514fb44 100644 --- a/tests/shell.test.js +++ b/tests/shell.test.js @@ -47,10 +47,12 @@ describe("shell utilities", () => { test("installs Homebrew packages without prompting", () => { expect(brewInstall("jq")).toBe(true); + expect(execSync).toHaveBeenCalledWith("env -u HOMEBREW_ASK brew update", { stdio: "inherit" }); expect(execSync).toHaveBeenCalledWith("brew install --no-ask jq", { stdio: "inherit" }); expect(brewInstall("ghostty", { cask: true })).toBe(true); expect(execSync).toHaveBeenCalledWith("brew install --no-ask --cask ghostty", { stdio: "inherit" }); + expect(execSync.mock.calls.filter(([command]) => command.includes("brew update"))).toHaveLength(1); }); test("propagates Ctrl-C from Homebrew installs", () => {