Add f-droid to the release pipeline

This commit is contained in:
Jon Staab 2026-09-18 11:57:25 -07:00
parent 1cbb862b33
commit 5bc499cf3a
2 changed files with 55 additions and 12 deletions

View file

@ -178,17 +178,23 @@ run and prints the command to pick up from there.
| --- | --- |
| `web` | `scripts/build.sh`: web bundle, `cap sync`, generated icons and splash screens |
| `apk` | `assembleRelease` signed with the distribution key, renamed to the path in `zapstore.yaml` |
| `fdroid` | reruns F-Droid's own preparation and build against the tag in a throwaway worktree |
| `play` | `bundleRelease` signed with the upload key, uploaded to a Play track as a draft |
| `ios` | `cap build ios` to an archive and IPA, uploaded with `altool` |
| `desktop` | `package:desktop:*` for this OS |
| `gitea` | creates the release for the tag from the changelog, attaches the APK and any desktop packages |
| `zapstore` | `zsp publish zapstore.yaml` |
| `fdroid` | nothing to upload; F-Droid builds from the tag, see [fdroid/README.md](fdroid/README.md) |
Release notes come from the `CHANGELOG.md` section matching `package.json`'s version, so every
store shows the same text. The APK and zapstore share one artifact, whose path lives in
`zapstore.yaml`.
F-Droid has nothing to upload — their servers build from the tag themselves — so the `fdroid` step
is a gate instead: it runs [their preparation and build](fdroid/README.md) against the tag in a
throwaway git worktree, and fails the release before anything is published if that build no longer
works. Preparation patches source with exact-match replacements, so it breaks quietly when the
files it rewrites change. Expect it to take a while; it installs and builds from scratch.
### Credentials
These go in `.env.local`, which is gitignored. `pnpm release --check` lists whichever are missing

View file

@ -1,6 +1,7 @@
#!/usr/bin/env node
import {existsSync} from "node:fs"
import {readFile, readdir, rename} from "node:fs/promises"
import {mkdtemp, readFile, readdir, rename, rm} from "node:fs/promises"
import {tmpdir} from "node:os"
import {dirname, join, resolve} from "node:path"
import {fileURLToPath} from "node:url"
import {parseArgs} from "node:util"
@ -143,6 +144,52 @@ const steps = [
await rename(join(dirname(apk), outputFile), apk)
},
},
{
name: "fdroid",
title: "Rebuild the tag the way F-Droid will",
missing: () =>
git("cat-file", "-e", `${version}:scripts/prepare-fdroid-source.sh`) === undefined
? [`F-Droid support in the ${version} tag`]
: [],
setup: [
`The ${version} tag is older than fdroid/, so there is nothing for F-Droid to build from it.`,
"Name the steps you do want, or release a tag that has it.",
],
run: async () => {
const parent = await mkdtemp(join(tmpdir(), "flotilla-fdroid-"))
const checkout = join(parent, "flotilla")
// Preparation rewrites source and dependencies in place, so it only runs against a checkout
// that can be thrown away
try {
await run("git", ["worktree", "add", "--detach", checkout, version], {cwd: root})
await run("./scripts/prepare-fdroid-source.sh", [], {cwd: checkout})
await run("./scripts/build-fdroid-assets.sh", [], {cwd: checkout})
// F-Droid signs its own builds, so keep the distribution key out of gradle's environment
const env = {...process.env}
delete env.ANDROID_KEYSTORE_PATH
await run("./gradlew", ["--no-daemon", "assembleFdroidRelease"], {
cwd: join(checkout, "android"),
env,
})
const built = join(
checkout,
"android/app/build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk",
)
if (!existsSync(built)) {
throw new Error(`the F-Droid build produced no apk at ${built}`)
}
} finally {
await rm(parent, {recursive: true, force: true})
await run("git", ["worktree", "prune"], {cwd: root})
}
},
},
{
name: "play",
title: "Build the AAB and upload it to Google Play",
@ -289,16 +336,6 @@ const steps = [
],
run: () => run("zsp", ["publish", "zapstore.yaml"], {cwd: root}),
},
{
name: "fdroid",
title: "F-Droid",
optional: true,
manual: [
"F-Droid builds from source on their own servers, so a release has nothing to upload: the",
"metadata tracks version tags, which makes pushing the tag the whole story. The fdroiddata",
"submission is still open — see fdroid/README.md.",
],
},
]
const unknownStep = chosen.find(step => !steps.some(({name}) => name === step))