diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4ac390e4af78..909658a8ba84 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 }} diff --git a/packages/desktop/scripts/finalize-latest-json.ts b/packages/desktop/scripts/finalize-latest-json.ts deleted file mode 100644 index 8e106bc10b37..000000000000 --- a/packages/desktop/scripts/finalize-latest-json.ts +++ /dev/null @@ -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, 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, 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 = {} - -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}`) diff --git a/packages/desktop/scripts/finalize-latest-yml.ts b/packages/desktop/scripts/finalize-latest-yml.ts deleted file mode 100644 index 0873b0f58484..000000000000 --- a/packages/desktop/scripts/finalize-latest-yml.ts +++ /dev/null @@ -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 | 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 { - const file = Bun.file(path.join(dir, subdir, filename)) - - if (!(await file.exists())) return undefined - - return parse(await file.text()) -} - -const output: Record = {} - -// 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") diff --git a/script/version.ts b/script/version.ts index f9e950d89586..ef56553598da 100755 --- a/script/version.ts +++ b/script/version.ts @@ -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")) }