flotilla/scripts/publish-android-release.mjs

87 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
import {readFile} from "node:fs/promises"
const read = path => readFile(new URL(path, import.meta.url), "utf-8")
const token = process.env.GITEA_TOKEN
const {name, version} = JSON.parse(await read("../package.json"))
const changelog = await read("../CHANGELOG.md")
const zapstore = await read("../zapstore.yaml")
const zapstoreField = key => {
const match = zapstore.match(new RegExp(`^${key}:\\s*(\\S+)\\s*$`, "m"))
if (!match) {
throw new Error(`zapstore.yaml is missing ${key}`)
}
return match[1]
}
const repository = new URL(zapstoreField("repository"))
const base = `${repository.origin}/api/v1/repos${repository.pathname}`
const api = async (method, path, {body, allow404} = {}) => {
const multipart = body instanceof FormData
const response = await fetch(base + path, {
method,
headers: {
Authorization: `token ${token}`,
...(body && !multipart ? {"Content-Type": "application/json"} : {}),
},
body: multipart ? body : body && JSON.stringify(body),
})
if (response.status === 404 && allow404) {
return undefined
}
if (!response.ok) {
throw new Error(`${method} ${path} responded ${response.status}: ${await response.text()}`)
}
return response.status === 204 ? undefined : response.json()
}
if (!token) {
throw new Error("Set GITEA_TOKEN to a token with write access to the repository")
}
const lines = changelog.split("\n")
const heading = lines.indexOf(`# ${version}`)
if (heading < 0) {
throw new Error(`CHANGELOG.md has no "# ${version}" section`)
}
const remainder = lines.slice(heading + 1)
const nextHeading = remainder.findIndex(line => line.startsWith("# "))
const notes = (nextHeading < 0 ? remainder : remainder.slice(0, nextHeading)).join("\n").trim()
const apk = await readFile(new URL(`../${zapstoreField("release_source")}`, import.meta.url))
if (!(await api("GET", `/tags/${version}`, {allow404: true}))) {
throw new Error(`${repository} has no ${version} tag; push it before publishing`)
}
const release =
(await api("GET", `/releases/tags/${version}`, {allow404: true})) ??
(await api("POST", "/releases", {body: {tag_name: version, name: version, body: notes}}))
const filename = `${name}-${version}.apk`
const existing = release.assets?.find(asset => asset.name === filename)
if (existing) {
await api("DELETE", `/releases/${release.id}/assets/${existing.id}`)
}
const form = new FormData()
form.append("attachment", new Blob([apk]), filename)
const asset = await api(
"POST",
`/releases/${release.id}/assets?name=${encodeURIComponent(filename)}`,
{body: form},
)
console.log(asset.browser_download_url)