mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-21 01:23:59 +00:00
chore(release): drop the separate opencode-beta release repo (#56083)
Co-authored-by: thdxr <826656+thdxr@users.noreply.github.com>
This commit is contained in:
parent
c5681f4e15
commit
ed772f5009
4 changed files with 3 additions and 419 deletions
8
.github/workflows/publish.yml
vendored
8
.github/workflows/publish.yml
vendored
|
|
@ -68,12 +68,10 @@ jobs:
|
|||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_VERSION: ${{ inputs.version }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
release: ${{ steps.version.outputs.release }}
|
||||
tag: ${{ steps.version.outputs.tag }}
|
||||
repo: ${{ steps.version.outputs.repo }}
|
||||
|
||||
build-cli:
|
||||
needs: version
|
||||
|
|
@ -102,7 +100,7 @@ jobs:
|
|||
env:
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
|
||||
- name: Build preview CLI
|
||||
|
|
@ -113,7 +111,7 @@ jobs:
|
|||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
OPENCODE_RELEASE_NOTES: ${{ (github.ref_name == 'dev' && inputs.release_notes) || '' }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
|
|
@ -650,7 +648,7 @@ jobs:
|
|||
OPENCODE_RELEASE_NOTES: ${{ (github.ref_name == 'dev' && inputs.release_notes) || '' }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NPM_CONFIG_PROVENANCE: false
|
||||
LATEST_YML_DIR: /tmp/latest-yml
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
|
|
|
|||
|
|
@ -1,270 +0,0 @@
|
|||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun"
|
||||
import path from "node:path"
|
||||
import { parseArgs } from "node:util"
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv.slice(2),
|
||||
options: {
|
||||
"dry-run": { type: "boolean", default: false },
|
||||
},
|
||||
})
|
||||
|
||||
const dryRun = values["dry-run"]
|
||||
|
||||
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 version = process.env.OPENCODE_VERSION
|
||||
|
||||
if (!version) throw new Error("OPENCODE_VERSION is required")
|
||||
|
||||
const dir = process.env.LATEST_YML_DIR
|
||||
|
||||
if (!dir) throw new Error("LATEST_YML_DIR is required")
|
||||
|
||||
const root = dir
|
||||
|
||||
const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN
|
||||
|
||||
if (!token) throw new Error("GH_TOKEN or GITHUB_TOKEN is required")
|
||||
|
||||
const rel = await fetch(`https://api.github.com/repos/${repo}/releases/${releaseId}`, {
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
},
|
||||
})
|
||||
|
||||
if (!rel.ok) {
|
||||
throw new Error(`Failed to fetch release: ${rel.status} ${rel.statusText}`)
|
||||
}
|
||||
|
||||
type Asset = {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
type Release = {
|
||||
assets?: Asset[]
|
||||
}
|
||||
|
||||
const assets = ((await rel.json()) as Release).assets ?? []
|
||||
|
||||
const amap = new Map(assets.map((item) => [item.name, item]))
|
||||
|
||||
type Item = {
|
||||
url: string
|
||||
}
|
||||
|
||||
type Yml = {
|
||||
version: string
|
||||
files: Item[]
|
||||
}
|
||||
|
||||
function parse(text: string): Yml {
|
||||
const lines = text.split("\n")
|
||||
let version = ""
|
||||
const files: Item[] = []
|
||||
let url = ""
|
||||
|
||||
const flush = () => {
|
||||
if (!url) return
|
||||
files.push({ url })
|
||||
url = ""
|
||||
}
|
||||
|
||||
for (const line of lines) {
|
||||
const trim = line.trim()
|
||||
|
||||
if (line.startsWith("version:")) {
|
||||
version = line.slice("version:".length).trim()
|
||||
continue
|
||||
}
|
||||
|
||||
if (trim.startsWith("- url:")) {
|
||||
flush()
|
||||
url = trim.slice("- url:".length).trim()
|
||||
continue
|
||||
}
|
||||
|
||||
const indented = line.startsWith(" ") || line.startsWith("\t")
|
||||
|
||||
if (!indented) flush()
|
||||
}
|
||||
|
||||
flush()
|
||||
|
||||
return { version, files }
|
||||
}
|
||||
|
||||
async function read(sub: string, file: string) {
|
||||
const item = Bun.file(path.join(root, sub, file))
|
||||
|
||||
if (!(await item.exists())) return undefined
|
||||
|
||||
return parse(await item.text())
|
||||
}
|
||||
|
||||
function pick(list: Item[], exts: string[]) {
|
||||
for (const ext of exts) {
|
||||
const found = list.find((item) => item.url.split("?")[0]?.toLowerCase().endsWith(ext))
|
||||
|
||||
if (found) return found.url
|
||||
}
|
||||
}
|
||||
|
||||
function link(raw: string) {
|
||||
if (raw.startsWith("https://") || raw.startsWith("http://")) return raw
|
||||
|
||||
return `https://github.com/${repo}/releases/download/v${version}/${raw}`
|
||||
}
|
||||
|
||||
async function sign(url: string, key: string) {
|
||||
const name = decodeURIComponent(new URL(url).pathname.split("/").pop() ?? key)
|
||||
const asset = amap.get(name)
|
||||
|
||||
const res = await fetch(asset?.url ?? url, {
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
...(asset ? { Accept: "application/octet-stream" } : {}),
|
||||
},
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch file ${name}: ${res.status} ${res.statusText} (${asset?.url ?? url})`)
|
||||
}
|
||||
|
||||
const tmp = process.env.RUNNER_TEMP ?? "/tmp"
|
||||
const file = path.join(tmp, name)
|
||||
await Bun.write(file, await res.arrayBuffer())
|
||||
await $`bunx @tauri-apps/cli signer sign ${file}`
|
||||
const sigFile = Bun.file(`${file}.sig`)
|
||||
|
||||
if (!(await sigFile.exists())) throw new Error(`Signature file not found for ${name}`)
|
||||
|
||||
return (await sigFile.text()).trim()
|
||||
}
|
||||
|
||||
const add = async (data: Record<string, { url: string; signature: string }>, key: string, raw: string | undefined) => {
|
||||
if (!raw) return
|
||||
|
||||
if (data[key]) return
|
||||
const url = link(raw)
|
||||
data[key] = { url, signature: await sign(url, key) }
|
||||
}
|
||||
|
||||
const alias = (data: Record<string, { url: string; signature: string }>, key: string, src: string) => {
|
||||
if (data[key]) return
|
||||
|
||||
if (!data[src]) return
|
||||
data[key] = data[src]
|
||||
}
|
||||
|
||||
const winx = await read("latest-yml-x86_64-pc-windows-msvc", "latest.yml")
|
||||
|
||||
const wina = await read("latest-yml-aarch64-pc-windows-msvc", "latest.yml")
|
||||
|
||||
const macx = await read("latest-yml-x86_64-apple-darwin", "latest-mac.yml")
|
||||
|
||||
const maca = await read("latest-yml-aarch64-apple-darwin", "latest-mac.yml")
|
||||
|
||||
const linx = await read("latest-yml-x86_64-unknown-linux-gnu", "latest-linux.yml")
|
||||
|
||||
const lina = await read("latest-yml-aarch64-unknown-linux-gnu", "latest-linux-arm64.yml")
|
||||
|
||||
const yver = winx?.version ?? wina?.version ?? macx?.version ?? maca?.version ?? linx?.version ?? lina?.version
|
||||
|
||||
if (yver && yver !== version) throw new Error(`latest.yml version mismatch: expected ${version}, got ${yver}`)
|
||||
|
||||
const out: Record<string, { url: string; signature: string }> = {}
|
||||
|
||||
const winxexe = pick(winx?.files ?? [], [".exe"])
|
||||
|
||||
const winaexe = pick(wina?.files ?? [], [".exe"])
|
||||
|
||||
const macxTarGz = "opencode-desktop-mac-x64.app.tar.gz"
|
||||
|
||||
const macaTarGz = "opencode-desktop-mac-arm64.app.tar.gz"
|
||||
|
||||
const linxDeb = pick(linx?.files ?? [], [".deb"])
|
||||
|
||||
const linxRpm = pick(linx?.files ?? [], [".rpm"])
|
||||
|
||||
const linxAppImage = pick(linx?.files ?? [], [".appimage"])
|
||||
|
||||
const linaDeb = pick(lina?.files ?? [], [".deb"])
|
||||
|
||||
const linaRpm = pick(lina?.files ?? [], [".rpm"])
|
||||
|
||||
const linaAppImage = pick(lina?.files ?? [], [".appimage"])
|
||||
|
||||
await add(out, "windows-x86_64-nsis", winxexe)
|
||||
|
||||
await add(out, "windows-aarch64-nsis", winaexe)
|
||||
|
||||
await add(out, "darwin-x86_64-app", macxTarGz)
|
||||
|
||||
await add(out, "darwin-aarch64-app", macaTarGz)
|
||||
|
||||
await add(out, "linux-x86_64-deb", linxDeb)
|
||||
|
||||
await add(out, "linux-x86_64-rpm", linxRpm)
|
||||
|
||||
await add(out, "linux-x86_64-appimage", linxAppImage)
|
||||
|
||||
await add(out, "linux-aarch64-deb", linaDeb)
|
||||
|
||||
await add(out, "linux-aarch64-rpm", linaRpm)
|
||||
|
||||
await add(out, "linux-aarch64-appimage", linaAppImage)
|
||||
|
||||
alias(out, "windows-x86_64", "windows-x86_64-nsis")
|
||||
|
||||
alias(out, "windows-aarch64", "windows-aarch64-nsis")
|
||||
|
||||
alias(out, "darwin-x86_64", "darwin-x86_64-app")
|
||||
|
||||
alias(out, "darwin-aarch64", "darwin-aarch64-app")
|
||||
|
||||
alias(out, "linux-x86_64", "linux-x86_64-deb")
|
||||
|
||||
alias(out, "linux-aarch64", "linux-aarch64-deb")
|
||||
|
||||
const platforms = Object.fromEntries(
|
||||
Object.keys(out)
|
||||
.sort()
|
||||
.map((key) => [key, out[key]]),
|
||||
)
|
||||
|
||||
if (!Object.keys(platforms).length) throw new Error("No updater files found in latest.yml artifacts")
|
||||
|
||||
const data = {
|
||||
version,
|
||||
notes: "",
|
||||
pub_date: new Date().toISOString(),
|
||||
platforms,
|
||||
}
|
||||
|
||||
const tmp = process.env.RUNNER_TEMP ?? "/tmp"
|
||||
|
||||
const file = path.join(tmp, "latest.json")
|
||||
|
||||
await Bun.write(file, JSON.stringify(data, null, 2))
|
||||
|
||||
const tag = `v${version}`
|
||||
|
||||
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}`)
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
#!/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: merge arm64 + x64 into single file
|
||||
const winX64 = await read("latest-yml-x86_64-pc-windows-msvc", "latest.yml")
|
||||
|
||||
const winArm64 = await read("latest-yml-aarch64-pc-windows-msvc", "latest.yml")
|
||||
|
||||
if (winX64 || winArm64) {
|
||||
const base = winArm64 ?? winX64!
|
||||
output["latest.yml"] = serialize({
|
||||
version: base.version,
|
||||
files: [...(winArm64?.files ?? []), ...(winX64?.files ?? [])],
|
||||
releaseDate: base.releaseDate,
|
||||
})
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
|
@ -9,8 +9,6 @@ if (!Script.preview || Script.channel === "beta") {
|
|||
output.push(`tag=v${Script.version}`)
|
||||
}
|
||||
|
||||
output.push(`repo=${process.env.GH_REPO}`)
|
||||
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
await Bun.write(process.env.GITHUB_OUTPUT, output.join("\n"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue