diff --git a/packages/opencode/src/cli/cmd/upgrade.ts b/packages/opencode/src/cli/cmd/upgrade.ts index 65f3bab4d46..f405da59c5c 100644 --- a/packages/opencode/src/cli/cmd/upgrade.ts +++ b/packages/opencode/src/cli/cmd/upgrade.ts @@ -42,6 +42,41 @@ export const UpgradeCommand = { } } prompts.log.info("Using method: " + method) + + if (method === "brew") { + const formula = await Installation.getBrewFormula() + const commands = Installation.getMigrationCommands(formula) + + if (commands) { + const reason = + formula === "sst/tap/opencode" + ? "You have the old sst/tap formula installed. The tap has been renamed to anomalyco/tap." + : "You are on the homebrew core formula, which updates every ~10 versions.\nThe anomalyco/tap formula updates on every release." + + prompts.log.warn(`${reason}\n\nTo migrate: ${commands.join(" && ")}`) + + const migrate = await prompts.confirm({ + message: "Would you like to migrate to the anomalyco/tap? (You can run these commands manually later)", + initialValue: true, + }) + if (migrate) { + for (const cmd of commands) { + const migrationSpinner = prompts.spinner() + migrationSpinner.start(cmd) + try { + await Installation.executeMigration([cmd]) + migrationSpinner.stop(cmd) + } catch (err) { + migrationSpinner.stop(`Failed: ${cmd}`, 1) + if (err instanceof Error) prompts.log.error(err.message) + prompts.outro("Done") + return + } + } + } + } + } + const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest() if (Installation.VERSION === target) { diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 86f2a781c83..32aedac55ff 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -110,14 +110,43 @@ export namespace Installation { }), ) - async function getBrewFormula() { - const tapFormula = await $`brew list --formula anomalyco/tap/opencode`.throws(false).quiet().text() - if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode" + export type BrewFormula = "anomalyco/tap/opencode" | "sst/tap/opencode" | "opencode" + + export async function getBrewFormula(): Promise { + const anomalycoTap = await $`brew list --formula anomalyco/tap/opencode`.throws(false).quiet().text() + if (anomalycoTap.includes("opencode")) return "anomalyco/tap/opencode" + + const sstTap = await $`brew list --formula sst/tap/opencode`.throws(false).quiet().text() + if (sstTap.includes("opencode")) return "sst/tap/opencode" + const coreFormula = await $`brew list --formula opencode`.throws(false).quiet().text() if (coreFormula.includes("opencode")) return "opencode" + return "opencode" } + export function getMigrationCommands(from: BrewFormula): string[] | null { + if (from === "anomalyco/tap/opencode") return null + if (from === "sst/tap/opencode") { + return ["brew uninstall sst/tap/opencode", "brew install anomalyco/tap/opencode"] + } + if (from === "opencode") { + return ["brew uninstall opencode", "brew install anomalyco/tap/opencode"] + } + return null + } + + export async function executeMigration(commands: string[]) { + for (const cmd of commands) { + const parts = cmd.split(" ") + if (parts[0] === "brew" && parts[1] === "uninstall") { + await $`brew uninstall ${parts[2]}`.env({ HOMEBREW_NO_AUTO_UPDATE: "1", ...process.env }).quiet() + } else if (parts[0] === "brew" && parts[1] === "install") { + await $`brew install ${parts[2]}`.env({ HOMEBREW_NO_AUTO_UPDATE: "1", ...process.env }).quiet() + } + } + } + export async function upgrade(method: Method, target: string) { let cmd switch (method) { diff --git a/packages/opencode/test/installation/brew-migration.test.ts b/packages/opencode/test/installation/brew-migration.test.ts new file mode 100644 index 00000000000..d266be53211 --- /dev/null +++ b/packages/opencode/test/installation/brew-migration.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from "bun:test" +import { Installation } from "../../src/installation" + +describe("installation.brew migration", () => { + test("no migration when already on anomalyco tap", () => { + expect(Installation.getMigrationCommands("anomalyco/tap/opencode")).toBeNull() + }) + + test("sst tap suggests migrate to anomalyco tap", () => { + expect(Installation.getMigrationCommands("sst/tap/opencode")).toEqual([ + "brew uninstall sst/tap/opencode", + "brew install anomalyco/tap/opencode", + ]) + }) + + test("core formula suggests migrate to anomalyco tap", () => { + expect(Installation.getMigrationCommands("opencode")).toEqual([ + "brew uninstall opencode", + "brew install anomalyco/tap/opencode", + ]) + }) +})