Copy gitea's latest release to the GitHub mirror so Obtainium users on the GitHub url get updates
This commit is contained in:
parent
6ae38287cb
commit
e8bd6acc32
3 changed files with 111 additions and 1 deletions
|
|
@ -4,6 +4,10 @@ on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
tags: ["*"]
|
tags: ["*"]
|
||||||
|
# A release goes public whenever its last artifact lands, which can be from a local run hours
|
||||||
|
# after the tag, so its copy on GitHub is kept up to date by polling
|
||||||
|
schedule:
|
||||||
|
- cron: "*/30 * * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# Serialized rather than cancel-in-progress: aborting a push halfway leaves the
|
# Serialized rather than cancel-in-progress: aborting a push halfway leaves the
|
||||||
|
|
@ -14,7 +18,7 @@ concurrency:
|
||||||
jobs:
|
jobs:
|
||||||
push-to-github:
|
push-to-github:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'coracle/flotilla'
|
if: github.repository == 'coracle/flotilla' && github.event_name != 'schedule'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Push master and tags to GitHub
|
- name: Push master and tags to GitHub
|
||||||
|
|
@ -32,3 +36,24 @@ jobs:
|
||||||
"https://x-access-token:$GH_MIRROR_TOKEN@github.com/coracle-social/flotilla.git" \
|
"https://x-access-token:$GH_MIRROR_TOKEN@github.com/coracle-social/flotilla.git" \
|
||||||
'refs/heads/master:refs/heads/master' \
|
'refs/heads/master:refs/heads/master' \
|
||||||
'refs/tags/*:refs/tags/*'
|
'refs/tags/*:refs/tags/*'
|
||||||
|
|
||||||
|
# Obtainium reads releases from whichever url a user added, and many added the GitHub one
|
||||||
|
copy-release-to-github:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: push-to-github
|
||||||
|
if: ${{ !cancelled() && github.repository == 'coracle/flotilla' && needs.push-to-github.result != 'failure' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
|
||||||
|
- name: Copy gitea's latest release to GitHub
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GH_MIRROR_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }}
|
||||||
|
run: node scripts/release/github.mjs
|
||||||
|
|
|
||||||
83
scripts/release/github.mjs
Normal file
83
scripts/release/github.mjs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
// Copies gitea's latest published release to the GitHub mirror, whose releases are what Obtainium
|
||||||
|
// sees for anyone who added the app by its GitHub url. Run by .gitea/workflows/mirror.yml.
|
||||||
|
import {gitea} from "./lib/gitea.mjs"
|
||||||
|
import {fail} from "./lib/shell.mjs"
|
||||||
|
|
||||||
|
const mirror = "coracle-social/flotilla"
|
||||||
|
const repository = new URL(`${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`)
|
||||||
|
const token = process.env.GH_MIRROR_TOKEN
|
||||||
|
|
||||||
|
if (!token || !process.env.GITEA_TOKEN) {
|
||||||
|
fail("Set GITEA_TOKEN and GH_MIRROR_TOKEN")
|
||||||
|
}
|
||||||
|
|
||||||
|
const github = async (method, url, {body, allow404} = {}) => {
|
||||||
|
const binary = body instanceof Uint8Array
|
||||||
|
const response = await fetch(url.startsWith("https://") ? url : `https://api.github.com${url}`, {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
Accept: "application/vnd.github+json",
|
||||||
|
...(body ? {"Content-Type": binary ? "application/octet-stream" : "application/json"} : {}),
|
||||||
|
},
|
||||||
|
body: binary ? body : body && JSON.stringify(body),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 404 && allow404) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`${method} ${url} responded ${response.status}: ${await response.text()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.status === 204 ? undefined : response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = await gitea({repository, token: process.env.GITEA_TOKEN}).latestRelease()
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
console.log("gitea has no published release")
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = source.tag_name
|
||||||
|
|
||||||
|
// Creating a release for a tag GitHub doesn't have yet would tag its default branch instead
|
||||||
|
if (!(await github("GET", `/repos/${mirror}/git/ref/tags/${tag}`, {allow404: true}))) {
|
||||||
|
fail(`${mirror} has no ${tag} tag yet; the mirror job pushes it`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const release =
|
||||||
|
(await github("GET", `/repos/${mirror}/releases/tags/${tag}`, {allow404: true})) ??
|
||||||
|
(await github("POST", `/repos/${mirror}/releases`, {
|
||||||
|
body: {tag_name: tag, name: source.name, body: source.body, make_latest: "true"},
|
||||||
|
}))
|
||||||
|
|
||||||
|
for (const asset of source.assets) {
|
||||||
|
const existing = release.assets.find(({name}) => name === asset.name)
|
||||||
|
|
||||||
|
// A moved tag rebuilds its assets under the same names, which only the size gives away
|
||||||
|
if (existing?.size === asset.size) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
await github("DELETE", `/repos/${mirror}/releases/assets/${existing.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(asset.browser_download_url)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Downloading ${asset.name} responded ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await github(
|
||||||
|
"POST",
|
||||||
|
`https://uploads.github.com/repos/${mirror}/releases/${release.id}/assets?name=${encodeURIComponent(asset.name)}`,
|
||||||
|
{body: new Uint8Array(await response.arrayBuffer())},
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(`${tag}: ${asset.name}`)
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,8 @@ export const gitea = ({repository, token}) => {
|
||||||
return {
|
return {
|
||||||
hasTag: async tag => Boolean(await api("GET", `/tags/${tag}`, {allow404: true})),
|
hasTag: async tag => Boolean(await api("GET", `/tags/${tag}`, {allow404: true})),
|
||||||
|
|
||||||
|
latestRelease: () => api("GET", "/releases/latest", {allow404: true}),
|
||||||
|
|
||||||
upsertRelease: async (tag, notes) =>
|
upsertRelease: async (tag, notes) =>
|
||||||
(await api("GET", `/releases/tags/${tag}`, {allow404: true})) ??
|
(await api("GET", `/releases/tags/${tag}`, {allow404: true})) ??
|
||||||
(await api("POST", "/releases", {
|
(await api("POST", "/releases", {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue