Skip to content

Commit 38879de

Browse files
committed
beginning of upgrade command
1 parent c4ff8dd commit 38879de

4 files changed

Lines changed: 189 additions & 196 deletions

File tree

packages/opencode/AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# OpenCode Agent Guidelines
1+
# opencode agent guidelines
22

33
## Build/Test Commands
44

@@ -19,7 +19,7 @@
1919

2020
## IMPORTANT
2121

22-
- Try to keep things in one function unless composable or reusable
22+
- Try to keep things in one function unless composable or reusablte
2323
- DO NOT do unnecessary destructuring of variables
2424
- DO NOT use else statements unless necessary
2525
- DO NOT use try catch if it can be avoided
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import type { Argv } from "yargs"
2+
import { UI } from "../ui"
3+
import { VERSION } from "../version"
4+
import path from "path"
5+
import fs from "fs/promises"
6+
import os from "os"
7+
import * as prompts from "@clack/prompts"
8+
import { Global } from "../../global"
9+
10+
const API = "https://api.github.com/repos/sst/opencode"
11+
12+
interface Release {
13+
tag_name: string
14+
name: string
15+
assets: Array<{
16+
name: string
17+
browser_download_url: string
18+
}>
19+
}
20+
21+
function asset(): string {
22+
const platform = os.platform()
23+
const arch = os.arch()
24+
25+
if (platform === "darwin") {
26+
return arch === "arm64"
27+
? "opencode-darwin-arm64.zip"
28+
: "opencode-darwin-x64.zip"
29+
}
30+
if (platform === "linux") {
31+
return arch === "arm64"
32+
? "opencode-linux-arm64.zip"
33+
: "opencode-linux-x64.zip"
34+
}
35+
if (platform === "win32") {
36+
return "opencode-windows-x64.zip"
37+
}
38+
39+
throw new Error(`Unsupported platform: ${platform}-${arch}`)
40+
}
41+
42+
function compare(current: string, latest: string): number {
43+
const a = current.replace(/^v/, "")
44+
const b = latest.replace(/^v/, "")
45+
46+
const aParts = a.split(".").map(Number)
47+
const bParts = b.split(".").map(Number)
48+
49+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
50+
const aPart = aParts[i] || 0
51+
const bPart = bParts[i] || 0
52+
53+
if (aPart < bPart) return -1
54+
if (aPart > bPart) return 1
55+
}
56+
57+
return 0
58+
}
59+
60+
async function latest(): Promise<Release> {
61+
const response = await fetch(`${API}/releases/latest`)
62+
if (!response.ok) {
63+
throw new Error(`Failed to fetch latest release: ${response.statusText}`)
64+
}
65+
return response.json()
66+
}
67+
68+
async function specific(version: string): Promise<Release> {
69+
const tag = version.startsWith("v") ? version : `v${version}`
70+
const response = await fetch(`${API}/releases/tags/${tag}`)
71+
if (!response.ok) {
72+
throw new Error(`Failed to fetch release ${tag}: ${response.statusText}`)
73+
}
74+
return response.json()
75+
}
76+
77+
async function download(url: string): Promise<string> {
78+
const response = await fetch(url)
79+
if (!response.ok) {
80+
throw new Error(`Failed to download: ${response.statusText}`)
81+
}
82+
83+
const buffer = await response.arrayBuffer()
84+
const temp = path.join(Global.Path.cache, `opencode-update-${Date.now()}.zip`)
85+
86+
await Bun.write(temp, buffer)
87+
88+
const extractDir = path.join(
89+
Global.Path.cache,
90+
`opencode-extract-${Date.now()}`,
91+
)
92+
await fs.mkdir(extractDir, { recursive: true })
93+
94+
const proc = Bun.spawn(["unzip", "-o", temp, "-d", extractDir], {
95+
stdout: "pipe",
96+
stderr: "pipe",
97+
})
98+
99+
const result = await proc.exited
100+
if (result !== 0) {
101+
throw new Error("Failed to extract update")
102+
}
103+
104+
await fs.unlink(temp)
105+
106+
const binary = path.join(extractDir, "opencode")
107+
await fs.chmod(binary, 0o755)
108+
109+
return binary
110+
}
111+
112+
export const UpgradeCommand = {
113+
command: "upgrade [target]",
114+
describe: "Upgrade opencode to the latest version or a specific version",
115+
builder: (yargs: Argv) => {
116+
return yargs.positional("target", {
117+
describe: "Specific version to upgrade to (e.g., '0.1.48' or 'v0.1.48')",
118+
type: "string",
119+
})
120+
},
121+
handler: async (args: { target?: string }) => {
122+
UI.empty()
123+
UI.println(UI.logo(" "))
124+
UI.empty()
125+
prompts.intro("upgrade")
126+
127+
if (!process.execPath.includes(path.join(".opencode", "bin")) && false) {
128+
prompts.log.error(
129+
`opencode is installed to ${process.execPath} and seems to be managed by a package manager`,
130+
)
131+
prompts.outro("Done")
132+
return
133+
}
134+
135+
const release = args.target ? await specific(args.target) : await latest()
136+
const target = release.tag_name
137+
138+
prompts.log.info(`Upgrade ${VERSION}${target}`)
139+
140+
if (VERSION !== "dev" && compare(VERSION, target) >= 0) {
141+
prompts.log.success(`Already up to date`)
142+
prompts.outro("Done")
143+
return
144+
}
145+
146+
const name = asset()
147+
const found = release.assets.find((a) => a.name === name)
148+
149+
if (!found) {
150+
prompts.log.error(`No binary found for platform: ${name}`)
151+
prompts.outro("Done")
152+
return
153+
}
154+
155+
const spinner = prompts.spinner()
156+
spinner.start("Downloading update...")
157+
158+
let downloadPath: string
159+
try {
160+
downloadPath = await download(found.browser_download_url)
161+
spinner.stop("Download complete")
162+
} catch (downloadError) {
163+
spinner.stop("Download failed")
164+
prompts.log.error(
165+
`Download failed: ${downloadError instanceof Error ? downloadError.message : String(downloadError)}`,
166+
)
167+
prompts.outro("Done")
168+
return
169+
}
170+
171+
try {
172+
await fs.rename(downloadPath, process.execPath)
173+
prompts.log.success(`Successfully upgraded to ${target}`)
174+
} catch (installError) {
175+
prompts.log.error(
176+
`Install failed: ${installError instanceof Error ? installError.message : String(installError)}`,
177+
)
178+
// Clean up downloaded file
179+
await fs.unlink(downloadPath).catch(() => {})
180+
}
181+
182+
prompts.outro("Done")
183+
},
184+
}

packages/opencode/src/cli/router.ts

Lines changed: 0 additions & 193 deletions
This file was deleted.

0 commit comments

Comments
 (0)