Skip to content

Commit d1f38bd

Browse files
Apply PR #15335: desktop: add latest.json finalizer script
2 parents 42a2226 + 6ec9ad6 commit d1f38bd

File tree

2 files changed

+160
-1
lines changed

2 files changed

+160
-1
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/env bun
2+
3+
import { Buffer } from "node:buffer"
4+
import { $ } from "bun"
5+
6+
const { values } = parseArgs({
7+
args: Bun.argv.slice(2),
8+
options: {
9+
"dry-run": { type: "boolean", default: false },
10+
},
11+
})
12+
13+
const dryRun = values["dry-run"]
14+
15+
import { parseArgs } from "node:util"
16+
17+
const repo = process.env.GH_REPO
18+
if (!repo) throw new Error("GH_REPO is required")
19+
20+
const releaseId = process.env.OPENCODE_RELEASE
21+
if (!releaseId) throw new Error("OPENCODE_RELEASE is required")
22+
23+
const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN
24+
if (!token) throw new Error("GH_TOKEN or GITHUB_TOKEN is required")
25+
26+
const apiHeaders = {
27+
Authorization: `token ${token}`,
28+
Accept: "application/vnd.github+json",
29+
}
30+
31+
const releaseRes = await fetch(`https://api.github.com/repos/${repo}/releases/${releaseId}`, {
32+
headers: apiHeaders,
33+
})
34+
35+
if (!releaseRes.ok) {
36+
throw new Error(`Failed to fetch release: ${releaseRes.status} ${releaseRes.statusText}`)
37+
}
38+
39+
type Asset = {
40+
name: string
41+
url: string
42+
browser_download_url: string
43+
}
44+
45+
type Release = {
46+
tag_name?: string
47+
assets?: Asset[]
48+
}
49+
50+
const release = (await releaseRes.json()) as Release
51+
const assets = release.assets ?? []
52+
const assetByName = new Map(assets.map((asset) => [asset.name, asset]))
53+
54+
const latestAsset = assetByName.get("latest.json")
55+
if (!latestAsset) throw new Error("latest.json asset not found")
56+
57+
const latestRes = await fetch(latestAsset.url, {
58+
headers: {
59+
Authorization: `token ${token}`,
60+
Accept: "application/octet-stream",
61+
},
62+
})
63+
64+
if (!latestRes.ok) {
65+
throw new Error(`Failed to fetch latest.json: ${latestRes.status} ${latestRes.statusText}`)
66+
}
67+
68+
const latestText = new TextDecoder().decode(await latestRes.arrayBuffer())
69+
const latest = JSON.parse(latestText)
70+
const base = { ...latest }
71+
delete base.platforms
72+
73+
const fetchSignature = async (asset: Asset) => {
74+
const res = await fetch(asset.url, {
75+
headers: {
76+
Authorization: `token ${token}`,
77+
Accept: "application/octet-stream",
78+
},
79+
})
80+
81+
if (!res.ok) {
82+
throw new Error(`Failed to fetch signature: ${res.status} ${res.statusText}`)
83+
}
84+
85+
return Buffer.from(await res.arrayBuffer()).toString()
86+
}
87+
88+
const entries: Record<string, { url: string; signature: string }> = {}
89+
const add = (key: string, asset: Asset, signature: string) => {
90+
if (entries[key]) return
91+
entries[key] = {
92+
url: asset.browser_download_url,
93+
signature,
94+
}
95+
}
96+
97+
const targets = [
98+
{ key: "linux-x86_64-deb", asset: "opencode-desktop-linux-amd64.deb" },
99+
{ key: "linux-x86_64-rpm", asset: "opencode-desktop-linux-x86_64.rpm" },
100+
{ key: "linux-aarch64-deb", asset: "opencode-desktop-linux-arm64.deb" },
101+
{ key: "linux-aarch64-rpm", asset: "opencode-desktop-linux-aarch64.rpm" },
102+
{ key: "windows-x86_64-nsis", asset: "opencode-desktop-windows-x64.exe" },
103+
{ key: "darwin-x86_64-app", asset: "opencode-desktop-darwin-x64.app.tar.gz" },
104+
{
105+
key: "darwin-aarch64-app",
106+
asset: "opencode-desktop-darwin-aarch64.app.tar.gz",
107+
},
108+
]
109+
110+
for (const target of targets) {
111+
const asset = assetByName.get(target.asset)
112+
if (!asset) continue
113+
114+
const sig = assetByName.get(`${target.asset}.sig`)
115+
if (!sig) continue
116+
117+
const signature = await fetchSignature(sig)
118+
add(target.key, asset, signature)
119+
}
120+
121+
const alias = (key: string, source: string) => {
122+
if (entries[key]) return
123+
const entry = entries[source]
124+
if (!entry) return
125+
entries[key] = entry
126+
}
127+
128+
alias("linux-x86_64", "linux-x86_64-deb")
129+
alias("linux-aarch64", "linux-aarch64-deb")
130+
alias("windows-x86_64", "windows-x86_64-nsis")
131+
alias("darwin-x86_64", "darwin-x86_64-app")
132+
alias("darwin-aarch64", "darwin-aarch64-app")
133+
134+
const platforms = Object.fromEntries(
135+
Object.keys(entries)
136+
.sort()
137+
.map((key) => [key, entries[key]]),
138+
)
139+
const output = {
140+
...base,
141+
platforms,
142+
}
143+
144+
const dir = process.env.RUNNER_TEMP ?? "/tmp"
145+
const file = `${dir}/latest.json`
146+
await Bun.write(file, JSON.stringify(output, null, 2))
147+
148+
const tag = release.tag_name
149+
if (!tag) throw new Error("Release tag not found")
150+
151+
if (dryRun) {
152+
console.log(`dry-run: wrote latest.json for ${tag} to ${file}`)
153+
process.exit(0)
154+
}
155+
await $`gh release upload ${tag} ${file} --clobber --repo ${repo}`
156+
157+
console.log(`finalized latest.json for ${tag}`)

script/publish.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env bun
22

3-
import { $ } from "bun"
43
import { Script } from "@opencode-ai/script"
4+
import { $ } from "bun"
55
import { fileURLToPath } from "url"
66

77
const highlightsTemplate = `
@@ -67,6 +67,8 @@ if (Script.release) {
6767
await new Promise((resolve) => setTimeout(resolve, 5_000))
6868
}
6969

70+
await import(`../packages/desktop/scripts/finalize-latest-json.ts`)
71+
7072
await $`gh release edit v${Script.version} --draft=false --repo ${process.env.GH_REPO}`
7173
}
7274

0 commit comments

Comments
 (0)