CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 17s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m21s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 6m36s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 9m24s
Step 3 of M314. Note 3127 §0's diagnostic is "is `main` publishing sufficient for a user to receive the build" — and here it was not. The desktop and Android lanes BUILT on main and published nothing: `Publish release` was gated on `refs/tags/v*`, the channel publishes on `refs/heads/dev`, the manifest job on dev-or-tag. So the stable channel moved only when somebody cut a tag, which made a `v*` tag load-bearing rather than the optional bookmark the model wants. Both channels are rolling fixed-tag releases now. `dev` from dev, `stable` from main, same machinery — `publish-release.sh` already took RELEASE_TAG, `write-manifest.sh` already pruned, and both already PATCHed a stale description on 409 (#2182). This is wiring, not new mechanism. ## The defect this carried `ci.yml`'s "Fetch the Android client to bake in" read `releases/download/dev` UNCONDITIONALLY, on every branch. Every image baked in the dev APK — `:latest` included — so a stable server served a dev-channel client to anyone who downloaded it from there. That has nothing to do with versioning; it is fixed here because this is the step that finally gives `stable` an APK to point at. It also means Android needs no channel machinery of its own. The APK is served FROM the image, so the channel is already a property of which image you run — note 3127 §7's "nothing to hand off" shape, arrived at here by accident. One branch-conditional line, not a second channel in `client_dist.py` as this milestone first assumed. ## The break this nearly shipped `install.sh --channel stable` read the version out of `stable/latest.json` and then fetched `releases/tags/v<version>` for the bundles — correct while stable was a manifest-only pointer, and broken the moment stable holds its own. Stable is the DEFAULT channel, so `curl … | sh` would have failed for everyone between this commit and the first merge to main. Both channels are one lookup now: fetch the fixed-tag release, install what is on it. A transitional fallback covers the window where `stable` still has no bundles, marked for deletion in step 7 — without it the default channel is broken for however long it takes to merge, and that window is gated on an operator request rather than on this lane. ## The two writers problem `stable`'s manifest was written by tag builds. It is written by main now, and the tag path stops writing it — two writers for one channel is a race with no winner worth having. A `v*` tag still writes its own versioned manifest; its build consequence goes entirely in step 7. Also corrected: `update.rs`'s header still described stable as following `v*` tags. Nothing in that file moved — it only ever read `<channel>/latest.json` — but the comment was a lie, and it is the file somebody reads to understand the feed. #3143 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
439 lines
23 KiB
YAML
439 lines
23 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:
|
|
branches: [dev, main]
|
|
tags: ["v*"]
|
|
paths:
|
|
- "desktop/**"
|
|
# The shared client core (store + sync engine) the desktop wraps. Its own
|
|
# crate since the Android client binds the same code, so a change there is a
|
|
# change to this app even though nothing under desktop/ moved.
|
|
- "core/**"
|
|
# The Android uniffi shim. It builds no desktop artifact, but it is a
|
|
# workspace member, so this lane's `cargo clippy --all-targets` is what
|
|
# compiles and lints it — and until the Android lane exists (M12 step 5),
|
|
# it is the ONLY thing that does.
|
|
- "android/**"
|
|
# The workspace manifest and lockfile, which now live at the repo root.
|
|
- "Cargo.toml"
|
|
- "Cargo.lock"
|
|
# The whole frontend, not just the adapter/bridge seam: it is compiled INTO
|
|
# the desktop binary, so any part of it changing means the shipped app is out
|
|
# of date. Config and lockfile included — a dependency bump changes the bundle
|
|
# as surely as a component does.
|
|
- "frontend/**"
|
|
- ".forgejo/workflows/desktop.yml"
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: desktop-${{ github.ref }}
|
|
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
|
|
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:
|
|
build:
|
|
name: Tauri desktop (Linux)
|
|
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
|
|
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
|
|
|
|
# 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
|
|
version="$(sh ../packaging/build-version.sh)"
|
|
echo "Building version $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
|
|
|
|
# Tag builds only: publish a real, versioned Fabled-Git Release with the
|
|
# AppImage + .deb attached — the stable fetch target the install script and
|
|
# the in-app updater consume (Actions artifacts above are ephemeral/test).
|
|
# Cutting the tag is the operator's action (rule 2); this only publishes a
|
|
# Release for a tag that already exists. Dormant on dev/main pushes.
|
|
- name: Publish release
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: bash desktop/packaging/publish-release.sh
|
|
|
|
# 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)
|
|
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
|
|
runs-on: python-ci
|
|
container:
|
|
image: git.fabledsword.com/bvandeusen/ci-tauri-win:1.97
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
# 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: |
|
|
version="$(sh ../packaging/build-version.sh)"
|
|
echo "Building version $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
|
|
|
|
# Publishes to the SAME release as the Linux job. Safe to run twice: the
|
|
# script reuses an existing release (409) and nullglob means each job uploads
|
|
# only the bundles present in its own workspace.
|
|
- name: Publish release
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: bash desktop/packaging/publish-release.sh
|
|
|
|
# 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' || startsWith(github.ref, 'refs/tags/v')
|
|
runs-on: python-ci
|
|
container:
|
|
image: git.fabledsword.com/bvandeusen/ci-tauri:1.97
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- 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 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.
|
|
version="$(sh desktop/packaging/build-version.sh)"
|
|
# POSIX `case` (rule 81). For a tag, GITHUB_REF_NAME is the tag name, so
|
|
# the default arm is the tag path.
|
|
case "${GITHUB_REF_NAME}" in
|
|
dev|main)
|
|
# Both are rolling channels: 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.
|
|
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
|
|
;;
|
|
*)
|
|
# A versioned release gets its own manifest and NOTHING ELSE. It used
|
|
# to also write the `stable` pointer — that moved to main above, and
|
|
# two writers for one channel is a race with no winner worth having.
|
|
# The tag's build consequence goes entirely in M314 step 7; this only
|
|
# stops it fighting over `stable`.
|
|
export RELEASE_TAG="${GITHUB_REF_NAME}"
|
|
export RELEASE_NOTES="ThoughtSync ${GITHUB_REF_NAME}"
|
|
APP_VERSION="$version" bash desktop/packaging/write-manifest.sh
|
|
;;
|
|
esac
|