Files
thoughtsync/.forgejo/workflows/desktop.yml
T
Bryan Van Deusen f992439588
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 15s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m50s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m19s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Successful in 7m59s
version: every surface can say which build it is, and two of them were lying
Note 3127 §5 removed version tags, so an artifact's self-report is now the only
answer to "which build is this?" — and nothing exists to contradict it when it
is wrong. Three surfaces gain a dim build line: the foot of the web rail, the
login screen, and the foot of Sync on Android.

The login screen because "I can't sign in" is a bug report like any other, and
requiring an account to read a build number withholds it from exactly the people
who can't get past that page. `/api/config` is already public.

Two of the values it was going to show were wrong, which is the part worth
knowing about.

The DESKTOP reported `env!("CARGO_PKG_VERSION")` from `config_get` and from the
startup log. `cargo tauri build --config '{"version": ...}'` overrides
tauri.conf.json, not Cargo's own metadata — so both read the literal `0.2.0` in
Cargo.toml, on every build ever shipped. They now read a display version baked in
by the lane through `option_env!`, hoisted to the crate root because two readers
of one fact is how this repo keeps producing 2181-2183. Not the ordering key
either: `1.0.<minutes>` is the opaque value Tauri's updater compares and must
never be shown to a person, and `update.rs` still reads it because a comparator
is exactly what it is (rule 149).

The SERVER fell back to `__version__` when APP_VERSION was absent, so a server
run from a checkout reported `0.2.0` — a real-looking version naming no build
anybody could obtain. `__init__.py` already asserted the honest answer was
"APP_VERSION being missing, which app.py already handles"; it did not, and a
comment claiming a behaviour two files away is how that stayed true-sounding.
Now an explicit "unknown", with the packaging version left where "unknown" is
not a legal value.

Android reads the INSTALLED package's versionName rather than BuildConfig, so it
reports what is actually on the phone.

Everything renders "unknown" rather than blank when it cannot say. A blank looks
like a layout bug; a plausible default cannot be caught by anything.

build.rs gets `rerun-if-env-changed` for the baked value: cargo does not track an
`option_env!` variable on its own, and the desktop lane having no cache today is
what makes that easy to forget the day one is added.
2026-08-29 23:07:29 -04:00

480 lines
25 KiB
YAML

# Tauri desktop (Linux) build — SEPARATE from ci.yml on purpose: a Rust + AppImage
# build that shouldn't run on server-only pushes. Produces the .deb and .AppImage.
#
# It DOES run on frontend changes. tauri's generate_context! embeds the built
# frontend in the binary, so a frontend commit that never triggers this ships to
# the web and silently never reaches the desktop app — and desktop, web and Android
# are peer surfaces held to one quality bar, not a primary and its fallbacks. The
# filter was once narrowed to the adapter/bridge directories against a "~20-40 min"
# build; measured runs are 4-5 minutes, so the cost that justified the narrowing
# isn't there.
#
# Toolchain comes from the ci-tauri image (Rust + Node + WebKitGTK 4.1 + tauri-cli);
# runs-on is just a registered scheduling label (Label Model B), not a per-purpose
# runner. The frontend is built here because tauri's generate_context! embeds it.
name: Desktop (Tauri)
on:
push:
# NO `paths:` FILTER. It was a second, independent statement of this artifact's
# file set, hand-kept beside the one in `packaging/version.sh`, and it drifted
# from it within a day (85ead4d). The `decide` job below reads the real set and
# skips in seconds when nothing moved — one definition, one reader (§3).
#
# The cost is that this workflow starts on every push rather than on a matching
# one. That is a ~15s container for a decision, against a lane that cannot
# silently fail to run.
branches: [dev, main]
workflow_dispatch:
concurrency:
group: desktop-${{ github.ref }}
cancel-in-progress: true
permissions:
# write (not read) so the tag build can publish a Release with the bundles
# attached (the "Publish release" step). Read is enough for dev/main builds,
# but the token scope is per-workflow, so it's set once here.
contents: write
jobs:
# Does anything need building at all?
#
# ONE reader of ONE definition — the file sets in `packaging/version.sh` — replacing
# the `paths:` filters that used to state the same fact a second time. They drifted
# from it within a day: `packaging/` was added to the sets and not to the filters,
# so the commit fixing a derivation bug never ran on the two lanes it fixed
# (85ead4d). Note 3127 §3 warns about exactly that duplication.
#
# THE GUARD RUNS HERE, so it runs on every path INCLUDING the skip one (§6.3).
# Skipping because "the channel already serves this version" is indistinguishable
# from "we derived a stale value that happens to match" unless something checks.
decide:
name: Build, or is the channel already serving this?
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-python:3.14
outputs:
build: ${{ steps.d.outputs.build }}
steps:
- uses: actions/checkout@v6
with:
# Derives a version — depth-1 is silently wrong (§6.1).
fetch-depth: 0
- name: Decide
id: d
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
case "$GITHUB_REF_NAME" in
main) channel=stable ;;
*) channel=dev ;;
esac
sh packaging/guard-forward.sh desktop "$channel"
echo "build=$(sh packaging/should-build.sh desktop "$channel")" >> $GITHUB_OUTPUT
build:
name: Tauri desktop (Linux)
needs: [decide]
if: needs.decide.outputs.build == 'true'
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-tauri:1.97
env:
# AppImage tooling (linuxdeploy) FUSE-mounts itself by default; CI containers
# have no /dev/fuse, so tell it to extract-and-run instead. Without this the
# AppImage bundle step fails with a FUSE error.
APPIMAGE_EXTRACT_AND_RUN: "1"
steps:
- uses: actions/checkout@v6
with:
# DERIVES A VERSION -> needs the whole history. A depth-1 clone sees one
# commit and `git log -- <paths>` produces a too-LOW value, silently, with
# the lane green — note 3127 §6.1, and the direction you cannot recover
# from. `packaging/version.sh` fails loudly on an empty result rather than
# emitting something plausible, which is what turns this into a red lane
# if it is ever dropped.
fetch-depth: 0
# tauri's generate_context! embeds the built frontend at compile time, so the
# frontend must exist before any cargo compile (clippy/test/build), not just
# at bundle time.
- name: Build the shared frontend
run: npm ci && npm run build
working-directory: frontend
# --locked on the FIRST cargo invocation of the job is the lockfile gate: it
# fails the run if Cargo.toml and the committed Cargo.lock disagree, instead
# of silently re-resolving. Everything after it in this job then compiles the
# exact versions recorded in the lockfile, so the flag isn't repeated on the
# bundle build (issue 2102).
#
# Run from the REPO ROOT with --workspace, not from desktop/src-tauri.
#
# These three steps used to run inside the desktop crate, which was right when
# it was the only Rust in the repo. After the core was extracted (M12 step 1)
# it silently stopped being right: cargo scoped to the desktop PACKAGE, so the
# core's 89 tests stopped running and nothing lints the Android uniffi shim at
# all. Both crates are dependencies of the desktop, so they still COMPILED —
# which is exactly why the gap was invisible, and why a green run kept meaning
# less than it looked like it meant.
- name: Clippy
run: cargo clippy --locked --workspace --all-targets -- -D warnings
- name: Test
run: cargo test --locked --workspace
# Deliberately AFTER clippy + test, not before.
#
# It's the cheapest check, so fail-fast ordering would normally put it first —
# but there is no Rust toolchain on the workstation (the desktop lane is
# verified entirely here), so a formatting nit failing first SKIPS clippy and
# the tests, and one CI cycle teaches nothing but whitespace. Running it here
# means every push reports its real problems too. Still before the ~20-40 min
# bundle build, so a fmt failure doesn't burn that.
- name: Rust format check
run: cargo fmt --all --check
# Frontend already built above; skip the beforeBuildCommand rebuild.
#
# createUpdaterArtifacts is applied only when a signing key exists (M10.9):
# tauri FAILS the build if it's asked to produce updater artifacts with no key,
# so making it conditional is what lets the pipeline stay green before the
# operator has added the secret. With the key present, each bundle gets a
# `.sig` beside it — the file the updater actually verifies against.
- name: Tauri build (deb + AppImage)
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
updater='{}'
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
echo "Signing key present — producing updater artifacts."
updater='{"bundle":{"createUpdaterArtifacts":true}}'
else
echo "No TAURI_SIGNING_PRIVATE_KEY — building unsigned, no updater artifacts."
fi
# The ORDERING KEY, not the display version: this string is what Tauri's
# updater parses as semver, and what it stamps into bundle FILENAMES that
# `write-manifest.sh` then selects on. The human-readable version is a
# separate value and arrives with the UI that shows it (#3181).
version="$(sh ../../packaging/version.sh key desktop)"
echo "Building desktop ordering key $version"
# The DISPLAY version, baked into the binary by `option_env!` (#3181).
# A different value for a different audience: this is the one a person
# quotes in a bug report, the key above is the one only a comparator
# sees. Exported rather than passed as a flag because the macro that
# reads it is in Rust source, not in Tauri's config.
THOUGHTSYNC_DISPLAY_VERSION="$(sh ../../packaging/version.sh display desktop)"
export THOUGHTSYNC_DISPLAY_VERSION
echo "Baking display version $THOUGHTSYNC_DISPLAY_VERSION"
cargo tauri build \
--config '{"build":{"beforeBuildCommand":""}}' \
--config "{\"version\":\"$version\"}" \
--config "$updater"
working-directory: desktop/src-tauri
# Tauri's AppImage bundles the build host's graphics/display libs
# (libEGL/libGL/libdrm/libgbm/libwayland-*), which clash with end-user GPU
# drivers and abort to a black window (EGL_BAD_PARAMETER, issue 2021).
# Strip that host-coupled stack so the app uses the running system's
# graphics libs; webkit/gtk stay bundled. Runs from the repo root (the
# script resolves its own paths), overwriting the AppImage in place.
- name: De-bundle AppImage graphics libraries
run: bash desktop/packaging/appimage/debundle-graphics.sh
# MUST run after de-bundling, not before. The step above DELETES the AppImage
# and repackages it, so the signature tauri produced during the build now
# describes a file that no longer exists. Publishing that stale .sig would make
# every Linux update fail verification — and the error names a signature
# mismatch, which points nowhere near "a later build step rewrote the file".
# Windows needs no equivalent: nothing post-processes the NSIS installer.
- name: Re-sign the de-bundled AppImage
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
echo "No signing key — the build produced no signature to replace."
exit 0
fi
appimage="$(find target/release/bundle/appimage -name '*.AppImage' -type f | head -1)"
[ -n "$appimage" ] || { echo "ERROR: no AppImage found to re-sign" >&2; exit 1; }
rm -f "$appimage.sig"
cargo tauri signer sign "$appimage"
[ -s "$appimage.sig" ] || { echo "ERROR: re-signing produced no .sig" >&2; exit 1; }
echo "Re-signed $(basename "$appimage")"
# install.sh hands the .deb to every Debian/Ubuntu user, so the package's
# Depends must be right BEFORE a release exists. Prints the generated
# control file and cross-checks it against what the ELF actually needs
# (dpkg-shlibdeps).
#
# We deliberately do NOT set bundle.linux.deb.depends: run 2872 showed
# tauri already infers exactly libwebkit2gtk-4.1-0 + libgtk-3-0, so
# declaring them again only produced a control file listing each twice.
# This step is the guard instead — if tauri's inference ever stops
# covering what the binary links, the build fails here.
- name: Verify the .deb
run: bash desktop/packaging/deb/verify.sh
# Repackage the binary just built into a native pacman package, so Arch /
# CachyOS gets a real native install from install.sh instead of the AppImage
# fallback — without a second Rust build or an Arch CI image. Safe because
# nothing is bundled: the binary resolves webkit/gtk by soname, which is
# identical across the two distros. Can't be pacman-tested here (Debian
# runner), so the step logs .PKGINFO + the full file listing for audit.
- name: Package for Arch (pacman)
run: bash desktop/packaging/arch/package-prebuilt.sh
# Make the built .deb + .AppImage downloadable from the run (for hand-testing).
# Mirrored action, never actions/upload-artifact: @v4+ throws
# GHESNotSupportedError on the hostname before it connects, and @v3 uploads
# something Gitea stores but will never serve back (it returns artifacts only
# through the v4 API, which filters on content_encoding='application/zip').
# Pinned by SHA — the mirror auto-syncs, so a moved upstream tag would
# silently change what runs. See Scribe issues 2255 / 2270.
# No continue-on-error: a swallowed upload failure is exactly how 110
# unreachable artifacts accumulated here unnoticed. Fail loudly instead.
- name: Upload bundles
uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245
with:
name: thoughtsync-linux
path: |
target/release/bundle/appimage/*.AppImage
target/release/bundle/deb/*.deb
target/release/bundle/arch/*.pkg.tar.*
# error, not warn: a build that bundles nothing should report as a
# failure, not as a green run with an empty artifact.
if-no-files-found: error
# The rolling channel for this branch: `dev` from dev, `stable` from main. Both
# are releases whose tag never moves, so the updater has a permanent URL to
# read — Forgejo has no /releases/latest/download/<asset> route, so "newest"
# cannot be named in a URL.
#
# MAIN PUBLISHING HERE is what makes a `v*` tag optional (note 3127 §0). Until
# M314 step 3 this job built on main and published nothing, so the stable
# channel moved only when somebody cut a tag — that section's diagnostic
# failing outright: main publishing was not sufficient for a user to receive
# the build.
#
# Gated on the signing key INSIDE the script rather than with an `if:`, because
# the secrets context isn't reliably available to step conditions. Publishing
# bundles the app would then refuse to verify is worse than publishing nothing:
# it looks like a working feed.
- name: Publish to the channel for this branch
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
env:
GITHUB_TOKEN: ${{ github.token }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
echo "No TAURI_SIGNING_PRIVATE_KEY — skipping the channel publish."
exit 0
fi
# POSIX `case`, not bash `[[ ]]` — these run under busybox sh (rule 81).
# `prerelease` is true for dev so it does not read as a supported build,
# and false for stable, which is the real thing.
case "$GITHUB_REF_NAME" in
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
esac
export RELEASE_TAG RELEASE_PRERELEASE
echo "Publishing to the $RELEASE_TAG channel."
bash desktop/packaging/publish-release.sh
# Windows installer, CROSS-COMPILED from Linux — there is no Windows build host.
# A Windows container can't run on a Linux host (containers share the host
# kernel), so cross-compiling is the only route without Windows hardware:
# cargo-xwin + LLVM's lld-link + makensis are Linux programs that emit Windows
# PE output. That toolchain is why this needs its own image rather than ci-tauri.
#
# NSIS only. `.msi` needs WiX v3, which is a Windows program — Tauri: ".msi
# installers can only be created on Windows". It returns if a Windows node does.
#
# A separate job, so a Windows-side failure never blocks the Linux artifacts that
# are the primary product today. Tauri calls this path "not tested as much" and a
# last resort, and nothing here can LAUNCH a Windows binary — green means it
# built, not that it runs. A real-machine check stays mandatory before trusting it.
windows:
name: Windows installer (cross-compiled)
needs: [decide]
if: needs.decide.outputs.build == 'true'
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-tauri-win:1.97
steps:
- uses: actions/checkout@v6
with:
# DERIVES A VERSION -> needs the whole history. A depth-1 clone sees one
# commit and `git log -- <paths>` produces a too-LOW value, silently, with
# the lane green — note 3127 §6.1, and the direction you cannot recover
# from. `packaging/version.sh` fails loudly on an empty result rather than
# emitting something plausible, which is what turns this into a red lane
# if it is ever dropped.
fetch-depth: 0
# Same reason as the Linux job: generate_context! embeds the built frontend
# at compile time, so it must exist before cargo runs.
- name: Build the shared frontend
run: npm ci && npm run build
working-directory: frontend
# tauri-build generates a Windows Resource file and needs `icons/icon.ico`,
# which the repo doesn't carry — only the PNG set the Linux bundles use.
# Generating it from the committed 1024px source keeps one icon of record
# instead of a hand-made .ico that could silently drift from the brand art.
# Linux doesn't need this step, which is why it lives here and not in `build`.
- name: Generate the Windows icon set
run: cargo tauri icon app-icon.png
working-directory: desktop/src-tauri
# This lane's lockfile gate (the Linux job gets it from `cargo clippy
# --locked`). It has to be its own step here because the build is this job's
# only crate-graph command, and discovering the drift 30 minutes into a
# cross-compile is the expensive way to learn it. Fetching for the Windows
# target also pre-warms exactly the crates the build will want.
- name: Verify the lockfile and fetch dependencies
run: cargo fetch --locked --target x86_64-pc-windows-msvc
working-directory: desktop/src-tauri
# --runner cargo-xwin swaps cargo for the cross-compiling driver (it supplies
# the MSVC CRT/SDK, pre-warmed into the image, and links with lld-link).
# Frontend already built above; skip the beforeBuildCommand rebuild.
- name: Tauri build (NSIS installer)
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
# The ORDERING KEY, not the display version: this string is what Tauri's
# updater parses as semver, and what it stamps into bundle FILENAMES that
# `write-manifest.sh` then selects on. The human-readable version is a
# separate value and arrives with the UI that shows it (#3181).
version="$(sh ../../packaging/version.sh key desktop)"
echo "Building desktop ordering key $version"
# The DISPLAY version, baked into the binary by `option_env!` (#3181).
# A different value for a different audience: this is the one a person
# quotes in a bug report, the key above is the one only a comparator
# sees. Exported rather than passed as a flag because the macro that
# reads it is in Rust source, not in Tauri's config.
THOUGHTSYNC_DISPLAY_VERSION="$(sh ../../packaging/version.sh display desktop)"
export THOUGHTSYNC_DISPLAY_VERSION
echo "Baking display version $THOUGHTSYNC_DISPLAY_VERSION"
updater='{}'
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
updater='{"bundle":{"createUpdaterArtifacts":true}}'
fi
cargo tauri build \
--runner cargo-xwin \
--target x86_64-pc-windows-msvc \
--bundles nsis \
--config '{"build":{"beforeBuildCommand":""}}' \
--config "{\"version\":\"$version\"}" \
--config "$updater"
working-directory: desktop/src-tauri
# Mirrored action, never actions/upload-artifact — see the Linux job's
# Upload bundles step for the full reasoning. Pinned by SHA because the
# mirror auto-syncs.
- name: Upload installer
uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245
with:
name: thoughtsync-windows
path: target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
if-no-files-found: error
# The rolling channel for this branch: `dev` from dev, `stable` from main. Both
# are releases whose tag never moves, so the updater has a permanent URL to
# read — Forgejo has no /releases/latest/download/<asset> route, so "newest"
# cannot be named in a URL.
#
# MAIN PUBLISHING HERE is what makes a `v*` tag optional (note 3127 §0). Until
# M314 step 3 this job built on main and published nothing, so the stable
# channel moved only when somebody cut a tag — that section's diagnostic
# failing outright: main publishing was not sufficient for a user to receive
# the build.
#
# Gated on the signing key INSIDE the script rather than with an `if:`, because
# the secrets context isn't reliably available to step conditions. Publishing
# bundles the app would then refuse to verify is worse than publishing nothing:
# it looks like a working feed.
- name: Publish to the channel for this branch
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
env:
GITHUB_TOKEN: ${{ github.token }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
echo "No TAURI_SIGNING_PRIVATE_KEY — skipping the channel publish."
exit 0
fi
# POSIX `case`, not bash `[[ ]]` — these run under busybox sh (rule 81).
# `prerelease` is true for dev so it does not read as a supported build,
# and false for stable, which is the real thing.
case "$GITHUB_REF_NAME" in
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
esac
export RELEASE_TAG RELEASE_PRERELEASE
echo "Publishing to the $RELEASE_TAG channel."
bash desktop/packaging/publish-release.sh
# The updater manifest, written AFTER both bundle jobs — they run in separate
# workspaces and neither can see the other's output, but one latest.json has to
# describe both platforms. Building it inside either job would silently omit the
# other, and a missing platform reads to a user as "no update available" rather
# than as a broken feed.
#
# Reads what actually landed on the channel release, so it can never advertise a
# bundle that failed to upload.
manifest:
name: Update manifest
needs: [build, windows]
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-tauri:1.97
steps:
- uses: actions/checkout@v6
with:
# DERIVES A VERSION -> needs the whole history. A depth-1 clone sees one
# commit and `git log -- <paths>` produces a too-LOW value, silently, with
# the lane green — note 3127 §6.1, and the direction you cannot recover
# from. `packaging/version.sh` fails loudly on an empty result rather than
# emitting something plausible, which is what turns this into a red lane
# if it is ever dropped.
fetch-depth: 0
- name: Write and publish latest.json
env:
GITHUB_TOKEN: ${{ github.token }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
echo "No TAURI_SIGNING_PRIVATE_KEY — nothing was signed, so there is no"
echo "manifest to write. Add the secret to enable in-app updates."
exit 0
fi
# The SAME helper AND the same request the bundles were built with — a
# second derivation here could drift, and a manifest whose version doesn't
# match the binary it points at is an updater that never settles. It must
# be `key`: this value is matched against bundle filenames.
version="$(sh packaging/version.sh key desktop)"
# Both channels are rolling: the manifest lands on the same release that
# holds the bundles, and the previous build's bundles are dropped once it
# points at this one. Nothing can reach them, and they are ~100 MB a push.
#
# No tag arm any more. A `v*` tag does not reach this workflow at all — it
# triggers release.yml, which writes a changelog and builds nothing.
case "${GITHUB_REF_NAME}" in
main) export RELEASE_TAG=stable
export RELEASE_NOTES="Stable build from ${GITHUB_SHA}" ;;
*) export RELEASE_TAG=dev
export RELEASE_NOTES="Development build from ${GITHUB_SHA}" ;;
esac
export PRUNE_OLD_ASSETS=true
APP_VERSION="$version" bash desktop/packaging/write-manifest.sh