| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- type Artifact = {
- channel: string
- name: string
- distribution: string
- version: string
- metadata: Record<string, unknown>
- }
- type DesktopFile = {
- url: string
- sha512: string
- size: number
- blockMapSize?: number
- }
- export namespace UpdateArtifact {
- export async function publish(artifact: Artifact) {
- if (process.env.GITHUB_ACTIONS !== "true") {
- console.log("skipped update artifact publication outside GitHub Actions")
- return
- }
- const requestURL = process.env.ACTIONS_ID_TOKEN_REQUEST_URL
- const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
- if (!requestURL || !requestToken) throw new Error("GitHub Actions OIDC is unavailable")
- const url = new URL(requestURL)
- url.searchParams.set("audience", "https://update.opencode.ai")
- const tokenResponse = await fetch(url, { headers: { Authorization: `Bearer ${requestToken}` } })
- if (!tokenResponse.ok) throw new Error(`Failed to request GitHub OIDC token: ${tokenResponse.status}`)
- const token: unknown = await tokenResponse.json()
- if (!isRecord(token) || typeof token.value !== "string") throw new Error("GitHub OIDC response did not include a token")
- const response = await fetch("https://update.opencode.ai/api/publish", {
- method: "POST",
- headers: {
- Authorization: `Bearer ${token.value}`,
- "Content-Type": "application/json",
- },
- body: JSON.stringify(artifact),
- })
- if (response.ok) return
- throw new Error(`Failed to publish update artifact: ${response.status} ${await response.text()}`)
- }
- export async function desktopMetadata(version: string, repo: string) {
- const directory = process.env.RUNNER_TEMP ?? "/tmp"
- const entries = await Promise.all(
- [
- ["desktop.yml", "latest.yml"],
- ["desktop-mac.yml", "latest-mac.yml"],
- ["desktop-linux.yml", "latest-linux.yml"],
- ["desktop-linux-arm64.yml", "latest-linux-arm64.yml"],
- ].map(async ([name, source]) => {
- const file = Bun.file(`${directory}/${source}`)
- if (!(await file.exists())) return
- return [name, parseDesktop(await file.text(), version, repo)] as const
- }),
- )
- const manifests = Object.fromEntries(entries.filter((entry) => entry !== undefined))
- if (!Object.keys(manifests).length) throw new Error("No desktop update metadata found")
- return { manifests }
- }
- }
- function parseDesktop(content: string, version: string, repo: string) {
- const lines = content.split("\n")
- const found = lines.find((line) => line.startsWith("version:"))?.slice("version:".length).trim()
- if (found !== version) throw new Error(`Desktop metadata version mismatch: expected ${version}, got ${found}`)
- const releaseDate = lines
- .find((line) => line.startsWith("releaseDate:"))
- ?.slice("releaseDate:".length)
- .trim()
- .replace(/^['"]|['"]$/g, "")
- if (!releaseDate) throw new Error("Desktop metadata did not include a release date")
- const files: DesktopFile[] = []
- lines.forEach((line) => {
- const value = line.trim()
- if (value.startsWith("- url:")) {
- const name = value.slice("- url:".length).trim()
- files.push({
- url: name.startsWith("http")
- ? name
- : `https://github.com/${repo}/releases/download/v${version}/${encodeURIComponent(name)}`,
- sha512: "",
- size: 0,
- })
- return
- }
- const current = files.at(-1)
- if (!current) return
- if (value.startsWith("sha512:")) current.sha512 = value.slice("sha512:".length).trim()
- if (value.startsWith("size:")) current.size = Number(value.slice("size:".length).trim())
- if (value.startsWith("blockMapSize:")) current.blockMapSize = Number(value.slice("blockMapSize:".length).trim())
- })
- if (!files.length || files.some((file) => !file.sha512 || !file.size)) {
- throw new Error("Desktop metadata contained an incomplete file")
- }
- return { files, releaseDate }
- }
- function isRecord(input: unknown): input is Record<string, unknown> {
- return typeof input === "object" && input !== null && !Array.isArray(input)
- }
|