summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop-electron/scripts
diff options
context:
space:
mode:
authorBrendan Allan <[email protected]>2026-03-04 15:12:34 +0800
committerGitHub <[email protected]>2026-03-04 15:12:34 +0800
commit5cf235fa6cf7b4c890c68f8ff68a96fcae992abf (patch)
tree25fdfd8ce95ad048fb097822995dcf060e8d6d8b /packages/desktop-electron/scripts
parente4f0825c56300286ec0aa82b1006e4006a17e1e1 (diff)
downloadopencode-5cf235fa6cf7b4c890c68f8ff68a96fcae992abf.tar.gz
opencode-5cf235fa6cf7b4c890c68f8ff68a96fcae992abf.zip
desktop: add electron version (#15663)
Diffstat (limited to 'packages/desktop-electron/scripts')
-rw-r--r--packages/desktop-electron/scripts/copy-bundles.ts12
-rw-r--r--packages/desktop-electron/scripts/copy-icons.ts12
-rw-r--r--packages/desktop-electron/scripts/finalize-latest-yml.ts116
-rw-r--r--packages/desktop-electron/scripts/predev.ts17
-rwxr-xr-xpackages/desktop-electron/scripts/prepare.ts24
-rw-r--r--packages/desktop-electron/scripts/utils.ts69
6 files changed, 250 insertions, 0 deletions
diff --git a/packages/desktop-electron/scripts/copy-bundles.ts b/packages/desktop-electron/scripts/copy-bundles.ts
new file mode 100644
index 000000000..6ef3335eb
--- /dev/null
+++ b/packages/desktop-electron/scripts/copy-bundles.ts
@@ -0,0 +1,12 @@
+import { $ } from "bun"
+import * as path from "node:path"
+
+import { RUST_TARGET } from "./utils"
+
+if (!RUST_TARGET) throw new Error("RUST_TARGET not defined")
+
+const BUNDLE_DIR = "dist"
+const BUNDLES_OUT_DIR = path.join(process.cwd(), "dist/bundles")
+
+await $`mkdir -p ${BUNDLES_OUT_DIR}`
+await $`cp -r ${BUNDLE_DIR}/* ${BUNDLES_OUT_DIR}`
diff --git a/packages/desktop-electron/scripts/copy-icons.ts b/packages/desktop-electron/scripts/copy-icons.ts
new file mode 100644
index 000000000..400f42757
--- /dev/null
+++ b/packages/desktop-electron/scripts/copy-icons.ts
@@ -0,0 +1,12 @@
+import { $ } from "bun"
+import { resolveChannel } from "./utils"
+
+const arg = process.argv[2]
+const channel = arg === "dev" || arg === "beta" || arg === "prod" ? arg : resolveChannel()
+
+const src = `./icons/${channel}`
+const dest = "resources/icons"
+
+await $`rm -rf ${dest}`
+await $`cp -R ${src} ${dest}`
+console.log(`Copied ${channel} icons from ${src} to ${dest}`)
diff --git a/packages/desktop-electron/scripts/finalize-latest-yml.ts b/packages/desktop-electron/scripts/finalize-latest-yml.ts
new file mode 100644
index 000000000..42ec23b64
--- /dev/null
+++ b/packages/desktop-electron/scripts/finalize-latest-yml.ts
@@ -0,0 +1,116 @@
+#!/usr/bin/env bun
+
+import { $ } from "bun"
+import path from "path"
+
+const dir = process.env.LATEST_YML_DIR!
+if (!dir) throw new Error("LATEST_YML_DIR is required")
+
+const repo = process.env.GH_REPO
+if (!repo) throw new Error("GH_REPO is required")
+
+const version = process.env.OPENCODE_VERSION
+if (!version) throw new Error("OPENCODE_VERSION is required")
+
+type FileEntry = {
+ url: string
+ sha512: string
+ size: number
+ blockMapSize?: number
+}
+
+type LatestYml = {
+ version: string
+ files: FileEntry[]
+ releaseDate: string
+}
+
+function parse(content: string): LatestYml {
+ const lines = content.split("\n")
+ let version = ""
+ let releaseDate = ""
+ const files: FileEntry[] = []
+ let current: Partial<FileEntry> | undefined
+
+ const flush = () => {
+ if (current?.url && current.sha512 && current.size) files.push(current as FileEntry)
+ current = undefined
+ }
+
+ for (const line of lines) {
+ const indented = line.startsWith(" ") || line.startsWith(" -")
+ if (line.startsWith("version:")) version = line.slice("version:".length).trim()
+ else if (line.startsWith("releaseDate:"))
+ releaseDate = line.slice("releaseDate:".length).trim().replace(/^'|'$/g, "")
+ else if (line.trim().startsWith("- url:")) {
+ flush()
+ current = { url: line.trim().slice("- url:".length).trim() }
+ } else if (indented && current && line.trim().startsWith("sha512:"))
+ current.sha512 = line.trim().slice("sha512:".length).trim()
+ else if (indented && current && line.trim().startsWith("size:"))
+ current.size = Number(line.trim().slice("size:".length).trim())
+ else if (indented && current && line.trim().startsWith("blockMapSize:"))
+ current.blockMapSize = Number(line.trim().slice("blockMapSize:".length).trim())
+ else if (!indented && current) flush()
+ }
+ flush()
+
+ return { version, files, releaseDate }
+}
+
+function serialize(data: LatestYml) {
+ const lines = [`version: ${data.version}`, "files:"]
+ for (const file of data.files) {
+ lines.push(` - url: ${file.url}`)
+ lines.push(` sha512: ${file.sha512}`)
+ lines.push(` size: ${file.size}`)
+ if (file.blockMapSize) lines.push(` blockMapSize: ${file.blockMapSize}`)
+ }
+ lines.push(`releaseDate: '${data.releaseDate}'`)
+ return lines.join("\n") + "\n"
+}
+
+async function read(subdir: string, filename: string): Promise<LatestYml | undefined> {
+ const file = Bun.file(path.join(dir, subdir, filename))
+ if (!(await file.exists())) return undefined
+ return parse(await file.text())
+}
+
+const output: Record<string, string> = {}
+
+// Windows: single arch, pass through
+const win = await read("latest-yml-x86_64-pc-windows-msvc", "latest.yml")
+if (win) output["latest.yml"] = serialize(win)
+
+// Linux x64: pass through
+const linuxX64 = await read("latest-yml-x86_64-unknown-linux-gnu", "latest-linux.yml")
+if (linuxX64) output["latest-linux.yml"] = serialize(linuxX64)
+
+// Linux arm64: pass through
+const linuxArm64 = await read("latest-yml-aarch64-unknown-linux-gnu", "latest-linux-arm64.yml")
+if (linuxArm64) output["latest-linux-arm64.yml"] = serialize(linuxArm64)
+
+// macOS: merge arm64 + x64 into single file
+const macX64 = await read("latest-yml-x86_64-apple-darwin", "latest-mac.yml")
+const macArm64 = await read("latest-yml-aarch64-apple-darwin", "latest-mac.yml")
+if (macX64 || macArm64) {
+ const base = macArm64 ?? macX64!
+ output["latest-mac.yml"] = serialize({
+ version: base.version,
+ files: [...(macArm64?.files ?? []), ...(macX64?.files ?? [])],
+ releaseDate: base.releaseDate,
+ })
+}
+
+// Upload to release
+const tag = `v${version}`
+const tmp = process.env.RUNNER_TEMP ?? "/tmp"
+
+for (const [filename, content] of Object.entries(output)) {
+ const filepath = path.join(tmp, filename)
+ await Bun.write(filepath, content)
+ await $`gh release upload ${tag} ${filepath} --clobber --repo ${repo}`
+ console.log(`uploaded ${filename}`)
+}
+
+console.log("finalized latest yml files")
diff --git a/packages/desktop-electron/scripts/predev.ts b/packages/desktop-electron/scripts/predev.ts
new file mode 100644
index 000000000..a688d0e7f
--- /dev/null
+++ b/packages/desktop-electron/scripts/predev.ts
@@ -0,0 +1,17 @@
+import { $ } from "bun"
+
+import { copyBinaryToSidecarFolder, getCurrentSidecar, windowsify } from "./utils"
+
+await $`bun ./scripts/copy-icons.ts ${process.env.OPENCODE_CHANNEL ?? "dev"}`
+
+const RUST_TARGET = Bun.env.RUST_TARGET
+
+const sidecarConfig = getCurrentSidecar(RUST_TARGET)
+
+const binaryPath = windowsify(`../opencode/dist/${sidecarConfig.ocBinary}/bin/opencode`)
+
+await (sidecarConfig.ocBinary.includes("-baseline")
+ ? $`cd ../opencode && bun run build --single --baseline`
+ : $`cd ../opencode && bun run build --single`)
+
+await copyBinaryToSidecarFolder(binaryPath, RUST_TARGET)
diff --git a/packages/desktop-electron/scripts/prepare.ts b/packages/desktop-electron/scripts/prepare.ts
new file mode 100755
index 000000000..3764db921
--- /dev/null
+++ b/packages/desktop-electron/scripts/prepare.ts
@@ -0,0 +1,24 @@
+#!/usr/bin/env bun
+import { $ } from "bun"
+
+import { Script } from "@opencode-ai/script"
+import { copyBinaryToSidecarFolder, getCurrentSidecar, resolveChannel, windowsify } from "./utils"
+
+const channel = resolveChannel()
+await $`bun ./scripts/copy-icons.ts ${channel}`
+
+const pkg = await Bun.file("./package.json").json()
+pkg.version = Script.version
+await Bun.write("./package.json", JSON.stringify(pkg, null, 2) + "\n")
+console.log(`Updated package.json version to ${Script.version}`)
+
+const sidecarConfig = getCurrentSidecar()
+
+const dir = "resources/opencode-binaries"
+
+await $`mkdir -p ${dir}`
+await $`gh run download ${Bun.env.GITHUB_RUN_ID} -n opencode-cli`.cwd(dir)
+
+await copyBinaryToSidecarFolder(windowsify(`${dir}/${sidecarConfig.ocBinary}/bin/opencode`))
+
+await $`rm -rf ${dir}`
diff --git a/packages/desktop-electron/scripts/utils.ts b/packages/desktop-electron/scripts/utils.ts
new file mode 100644
index 000000000..4c9af1fc7
--- /dev/null
+++ b/packages/desktop-electron/scripts/utils.ts
@@ -0,0 +1,69 @@
+import { $ } from "bun"
+
+export type Channel = "dev" | "beta" | "prod"
+
+export function resolveChannel(): Channel {
+ const raw = Bun.env.OPENCODE_CHANNEL
+ if (raw === "dev" || raw === "beta" || raw === "prod") return raw
+ return "dev"
+}
+
+export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; assetExt: string }> = [
+ {
+ rustTarget: "aarch64-apple-darwin",
+ ocBinary: "opencode-darwin-arm64",
+ assetExt: "zip",
+ },
+ {
+ rustTarget: "x86_64-apple-darwin",
+ ocBinary: "opencode-darwin-x64-baseline",
+ assetExt: "zip",
+ },
+ {
+ rustTarget: "x86_64-pc-windows-msvc",
+ ocBinary: "opencode-windows-x64-baseline",
+ assetExt: "zip",
+ },
+ {
+ rustTarget: "x86_64-unknown-linux-gnu",
+ ocBinary: "opencode-linux-x64-baseline",
+ assetExt: "tar.gz",
+ },
+ {
+ rustTarget: "aarch64-unknown-linux-gnu",
+ ocBinary: "opencode-linux-arm64",
+ assetExt: "tar.gz",
+ },
+]
+
+export const RUST_TARGET = Bun.env.RUST_TARGET
+
+function nativeTarget() {
+ const { platform, arch } = process
+ if (platform === "darwin") return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin"
+ if (platform === "win32") return "x86_64-pc-windows-msvc"
+ if (platform === "linux") return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu"
+ throw new Error(`Unsupported platform: ${platform}/${arch}`)
+}
+
+export function getCurrentSidecar(target = RUST_TARGET ?? nativeTarget()) {
+ const binaryConfig = SIDECAR_BINARIES.find((b) => b.rustTarget === target)
+ if (!binaryConfig) throw new Error(`Sidecar configuration not available for Rust target '${target}'`)
+
+ return binaryConfig
+}
+
+export async function copyBinaryToSidecarFolder(source: string) {
+ const dir = `resources`
+ await $`mkdir -p ${dir}`
+ const dest = windowsify(`${dir}/opencode-cli`)
+ await $`cp ${source} ${dest}`
+ if (process.platform === "darwin") await $`codesign --force --sign - ${dest}`
+
+ console.log(`Copied ${source} to ${dest}`)
+}
+
+export function windowsify(path: string) {
+ if (path.endsWith(".exe")) return path
+ return `${path}${process.platform === "win32" ? ".exe" : ""}`
+}