Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions packages/opencode/src/cli/cmd/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,40 @@ export const UpgradeCommand = {
return
}

// Validate version exists before attempting upgrade
if (args.target) {
const spinner = prompts.spinner()
spinner.start("Checking version availability...")
const exists = await Installation.versionExists(target, method)
if (!exists) {
spinner.stop("Version not found", 1)
if (method === "brew") {
prompts.log.error(`Version ${target} is not available via brew. Only the latest version can be installed.`)
const latestVersion = await Installation.latest(method).catch(() => null)
if (latestVersion) {
prompts.log.info(`Available version: ${latestVersion}`)
}
} else {
prompts.log.error(`Version ${target} was not found. Please check the version number and try again.`)
}
prompts.outro("Done")
return
}
spinner.stop("Version available")
}

prompts.log.info(`From ${Installation.VERSION} → ${target}`)
const spinner = prompts.spinner()
spinner.start("Upgrading...")
const upgradeSpinner = prompts.spinner()
upgradeSpinner.start("Upgrading...")
const err = await Installation.upgrade(method, target).catch((err) => err)
if (err) {
spinner.stop("Upgrade failed", 1)
upgradeSpinner.stop("Upgrade failed", 1)
if (err instanceof Installation.UpgradeFailedError) prompts.log.error(err.data.stderr)
else if (err instanceof Error) prompts.log.error(err.message)
prompts.outro("Done")
return
}
spinner.stop("Upgrade complete")
upgradeSpinner.stop("Upgrade complete")
prompts.outro("Done")
},
}
34 changes: 34 additions & 0 deletions packages/opencode/src/installation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,38 @@ export namespace Installation {
})
.then((data: any) => data.tag_name.replace(/^v/, ""))
}

export const VersionNotFoundError = NamedError.create(
"VersionNotFoundError",
z.object({
version: z.string(),
method: z.string(),
}),
)

export async function versionExists(version: string, installMethod?: Method): Promise<boolean> {
const detectedMethod = installMethod || (await method())

if (detectedMethod === "brew") {
// Brew doesn't support installing specific versions easily
// Only the current formula version is available
const latestVersion = await latest(detectedMethod).catch(() => null)
return version === latestVersion
}

if (detectedMethod === "npm" || detectedMethod === "bun" || detectedMethod === "pnpm") {
const registry = await iife(async () => {
const r = (await $`npm config get registry`.quiet().nothrow().text()).trim()
const reg = r || "https://registry.npmjs.org"
return reg.endsWith("/") ? reg.slice(0, -1) : reg
})
// Check if the specific version exists
const res = await fetch(`${registry}/opencode-ai/${version}`)
return res.ok
}

// For curl method, check GitHub releases
const res = await fetch(`https://api.github.com/repos/anomalyco/opencode/releases/tags/v${version}`)
return res.ok
}
}