| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- import type { Argv } from "yargs"
- import { UI } from "../ui"
- import { VERSION } from "../version"
- import path from "path"
- import fs from "fs/promises"
- import os from "os"
- import * as prompts from "@clack/prompts"
- import { Global } from "../../global"
- const API = "https://api.github.com/repos/sst/opencode"
- interface Release {
- tag_name: string
- name: string
- assets: Array<{
- name: string
- browser_download_url: string
- }>
- }
- function asset(): string {
- const platform = os.platform()
- const arch = os.arch()
- if (platform === "darwin") {
- return arch === "arm64"
- ? "opencode-darwin-arm64.zip"
- : "opencode-darwin-x64.zip"
- }
- if (platform === "linux") {
- return arch === "arm64"
- ? "opencode-linux-arm64.zip"
- : "opencode-linux-x64.zip"
- }
- if (platform === "win32") {
- return "opencode-windows-x64.zip"
- }
- throw new Error(`Unsupported platform: ${platform}-${arch}`)
- }
- function compare(current: string, latest: string): number {
- const a = current.replace(/^v/, "")
- const b = latest.replace(/^v/, "")
- const aParts = a.split(".").map(Number)
- const bParts = b.split(".").map(Number)
- for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
- const aPart = aParts[i] || 0
- const bPart = bParts[i] || 0
- if (aPart < bPart) return -1
- if (aPart > bPart) return 1
- }
- return 0
- }
- async function latest(): Promise<Release> {
- const response = await fetch(`${API}/releases/latest`)
- if (!response.ok) {
- throw new Error(`Failed to fetch latest release: ${response.statusText}`)
- }
- return response.json()
- }
- async function specific(version: string): Promise<Release> {
- const tag = version.startsWith("v") ? version : `v${version}`
- const response = await fetch(`${API}/releases/tags/${tag}`)
- if (!response.ok) {
- throw new Error(`Failed to fetch release ${tag}: ${response.statusText}`)
- }
- return response.json()
- }
- async function download(url: string): Promise<string> {
- const response = await fetch(url)
- if (!response.ok) {
- throw new Error(`Failed to download: ${response.statusText}`)
- }
- const buffer = await response.arrayBuffer()
- const temp = path.join(Global.Path.cache, `opencode-update-${Date.now()}.zip`)
- await Bun.write(temp, buffer)
- const extractDir = path.join(
- Global.Path.cache,
- `opencode-extract-${Date.now()}`,
- )
- await fs.mkdir(extractDir, { recursive: true })
- const proc = Bun.spawn(["unzip", "-o", temp, "-d", extractDir], {
- stdout: "pipe",
- stderr: "pipe",
- })
- const result = await proc.exited
- if (result !== 0) {
- throw new Error("Failed to extract update")
- }
- await fs.unlink(temp)
- const binary = path.join(extractDir, "opencode")
- await fs.chmod(binary, 0o755)
- return binary
- }
- export const UpgradeCommand = {
- command: "upgrade [target]",
- describe: "Upgrade opencode to the latest version or a specific version",
- builder: (yargs: Argv) => {
- return yargs.positional("target", {
- describe: "Specific version to upgrade to (e.g., '0.1.48' or 'v0.1.48')",
- type: "string",
- })
- },
- handler: async (args: { target?: string }) => {
- UI.empty()
- UI.println(UI.logo(" "))
- UI.empty()
- prompts.intro("Upgrade")
- if (!process.execPath.includes(path.join(".opencode", "bin")) && false) {
- prompts.log.error(
- `opencode is installed to ${process.execPath} and seems to be managed by a package manager`,
- )
- prompts.outro("Done")
- return
- }
- const release = args.target
- ? await specific(args.target).catch(() => {})
- : await latest().catch(() => {})
- if (!release) {
- prompts.log.error("Failed to fetch release information")
- prompts.outro("Done")
- return
- }
- const target = release.tag_name
- if (VERSION !== "dev" && compare(VERSION, target) >= 0) {
- prompts.log.success(`Already up to date`)
- prompts.outro("Done")
- return
- }
- prompts.log.info(`From ${VERSION} → ${target}`)
- const name = asset()
- const found = release.assets.find((a) => a.name === name)
- if (!found) {
- prompts.log.error(`No binary found for platform: ${name}`)
- prompts.outro("Done")
- return
- }
- const spinner = prompts.spinner()
- spinner.start("Downloading update...")
- const downloadPath = await download(found.browser_download_url).catch(
- () => {},
- )
- if (!downloadPath) {
- spinner.stop("Download failed")
- prompts.log.error("Download failed")
- prompts.outro("Done")
- return
- }
- spinner.stop("Download complete")
- const renamed = await fs
- .rename(downloadPath, process.execPath)
- .catch(() => {})
- if (renamed === undefined) {
- prompts.log.error("Install failed")
- await fs.unlink(downloadPath).catch(() => {})
- prompts.outro("Done")
- return
- }
- prompts.log.success(`Successfully upgraded to ${target}`)
- prompts.outro("Done")
- },
- }
|