summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorBrendan Allan <[email protected]>2026-02-27 20:18:21 +0800
committerGitHub <[email protected]>2026-02-27 20:18:21 +0800
commit967313234a7908dca1f4c7bec75bb4af6c0d0935 (patch)
treeef3a056c70c31ed72f07e3083b8bc58edfbef7ef /packages
parentdfa02811178af1af602f59f8cdbcb3f3e319f103 (diff)
downloadopencode-967313234a7908dca1f4c7bec75bb4af6c0d0935.tar.gz
opencode-967313234a7908dca1f4c7bec75bb4af6c0d0935.zip
desktop: add latest.json finalizer script (#15335)
Diffstat (limited to 'packages')
-rw-r--r--packages/desktop/scripts/finalize-latest-json.ts157
1 files changed, 157 insertions, 0 deletions
diff --git a/packages/desktop/scripts/finalize-latest-json.ts b/packages/desktop/scripts/finalize-latest-json.ts
new file mode 100644
index 000000000..a6fe02a37
--- /dev/null
+++ b/packages/desktop/scripts/finalize-latest-json.ts
@@ -0,0 +1,157 @@
+#!/usr/bin/env bun
+
+import { Buffer } from "node:buffer"
+import { $ } from "bun"
+
+const { values } = parseArgs({
+ args: Bun.argv.slice(2),
+ options: {
+ "dry-run": { type: "boolean", default: false },
+ },
+})
+
+const dryRun = values["dry-run"]
+
+import { parseArgs } from "node:util"
+
+const repo = process.env.GH_REPO
+if (!repo) throw new Error("GH_REPO is required")
+
+const releaseId = process.env.OPENCODE_RELEASE
+if (!releaseId) throw new Error("OPENCODE_RELEASE is required")
+
+const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN
+if (!token) throw new Error("GH_TOKEN or GITHUB_TOKEN is required")
+
+const apiHeaders = {
+ Authorization: `token ${token}`,
+ Accept: "application/vnd.github+json",
+}
+
+const releaseRes = await fetch(`https://api.github.com/repos/${repo}/releases/${releaseId}`, {
+ headers: apiHeaders,
+})
+
+if (!releaseRes.ok) {
+ throw new Error(`Failed to fetch release: ${releaseRes.status} ${releaseRes.statusText}`)
+}
+
+type Asset = {
+ name: string
+ url: string
+ browser_download_url: string
+}
+
+type Release = {
+ tag_name?: string
+ assets?: Asset[]
+}
+
+const release = (await releaseRes.json()) as Release
+const assets = release.assets ?? []
+const assetByName = new Map(assets.map((asset) => [asset.name, asset]))
+
+const latestAsset = assetByName.get("latest.json")
+if (!latestAsset) throw new Error("latest.json asset not found")
+
+const latestRes = await fetch(latestAsset.url, {
+ headers: {
+ Authorization: `token ${token}`,
+ Accept: "application/octet-stream",
+ },
+})
+
+if (!latestRes.ok) {
+ throw new Error(`Failed to fetch latest.json: ${latestRes.status} ${latestRes.statusText}`)
+}
+
+const latestText = new TextDecoder().decode(await latestRes.arrayBuffer())
+const latest = JSON.parse(latestText)
+const base = { ...latest }
+delete base.platforms
+
+const fetchSignature = async (asset: Asset) => {
+ const res = await fetch(asset.url, {
+ headers: {
+ Authorization: `token ${token}`,
+ Accept: "application/octet-stream",
+ },
+ })
+
+ if (!res.ok) {
+ throw new Error(`Failed to fetch signature: ${res.status} ${res.statusText}`)
+ }
+
+ return Buffer.from(await res.arrayBuffer()).toString()
+}
+
+const entries: Record<string, { url: string; signature: string }> = {}
+const add = (key: string, asset: Asset, signature: string) => {
+ if (entries[key]) return
+ entries[key] = {
+ url: asset.browser_download_url,
+ signature,
+ }
+}
+
+const targets = [
+ { key: "linux-x86_64-deb", asset: "opencode-desktop-linux-amd64.deb" },
+ { key: "linux-x86_64-rpm", asset: "opencode-desktop-linux-x86_64.rpm" },
+ { key: "linux-aarch64-deb", asset: "opencode-desktop-linux-arm64.deb" },
+ { key: "linux-aarch64-rpm", asset: "opencode-desktop-linux-aarch64.rpm" },
+ { key: "windows-x86_64-nsis", asset: "opencode-desktop-windows-x64.exe" },
+ { key: "darwin-x86_64-app", asset: "opencode-desktop-darwin-x64.app.tar.gz" },
+ {
+ key: "darwin-aarch64-app",
+ asset: "opencode-desktop-darwin-aarch64.app.tar.gz",
+ },
+]
+
+for (const target of targets) {
+ const asset = assetByName.get(target.asset)
+ if (!asset) continue
+
+ const sig = assetByName.get(`${target.asset}.sig`)
+ if (!sig) continue
+
+ const signature = await fetchSignature(sig)
+ add(target.key, asset, signature)
+}
+
+const alias = (key: string, source: string) => {
+ if (entries[key]) return
+ const entry = entries[source]
+ if (!entry) return
+ entries[key] = entry
+}
+
+alias("linux-x86_64", "linux-x86_64-deb")
+alias("linux-aarch64", "linux-aarch64-deb")
+alias("windows-x86_64", "windows-x86_64-nsis")
+alias("darwin-x86_64", "darwin-x86_64-app")
+alias("darwin-aarch64", "darwin-aarch64-app")
+
+const platforms = Object.fromEntries(
+ Object.keys(entries)
+ .sort()
+ .map((key) => [key, entries[key]]),
+)
+const output = {
+ ...base,
+ platforms,
+}
+
+const dir = process.env.RUNNER_TEMP ?? "/tmp"
+const file = `${dir}/latest.json`
+await Bun.write(file, JSON.stringify(output, null, 2))
+
+const tag = release.tag_name
+if (!tag) throw new Error("Release tag not found")
+
+if (dryRun) {
+ console.log(`dry-run: wrote latest.json for ${tag} to ${file}`)
+ process.exit(0)
+}
+await $`gh release upload ${tag} ${file} --clobber --repo ${repo}`
+
+console.log(`finalized latest.json for ${tag}`)