Simpl-Resultat/scripts/build-latest-json.sh
le king fu da7129d529
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m54s
fix: give rpm installs their own key in latest.json
latest.json only had linux-x86_64, built from the .deb. An .rpm install
looks up linux-x86_64-rpm first, then fell back on linux-x86_64,
downloaded the .deb and refused it (InvalidUpdaterFormat).

- scripts/build-latest-json.sh writes linux-x86_64-deb,
  linux-x86_64-rpm, linux-x86_64 (the .deb, as a fallback) and
  windows-x86_64 from the release assets. It fails unless it finds
  exactly one .deb, one .rpm and one -setup.exe, each with a non-empty
  .sig. The JSON comes from jq alone, notes read with --rawfile, file
  names encoded with @uri like the upload step.
- scripts/build-latest-json.test.mjs (vitest) runs it under
  bash -e -o pipefail on a temporary assets dir; check-frontend
  installs jq.
- release.yml calls the script, writes the notes output under a random
  delimiter, checks the manifest with jq before the registry DELETE,
  and names the .rpm instead of an AppImage in the release notes.
- verify-release.sh requires the four keys, the extension of each URL
  and the minisign file: of each signature, and exits 1 on any failure.
- Docs: release skill, qa-update-cycle section 3 (rpm procedure, keys,
  reason and removal condition of the fallback), architecture, README,
  CHANGELOG, STATE.

Resolves #320

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:49:12 -04:00

114 lines
4.2 KiB
Bash
Executable file

#!/usr/bin/env bash
# Build latest.json, the updater manifest of a release, from its assets (#320).
#
# Usage: build-latest-json.sh <assets-dir> <version> <notes-file> <base-url>
#
# assets-dir directory holding the bundles and their .sig files
# version release version without the leading v, e.g. 0.17.1
# notes-file release notes, embedded verbatim as data (jq --rawfile)
# base-url download URL of the release, without a trailing slash, e.g.
# https://<forgejo>/<owner>/<repo>/releases/download/v0.17.1
#
# Writes the JSON on stdout and its log on stderr. release.yml redirects
# stdout to release-assets/latest.json; scripts/build-latest-json.test.mjs
# runs it under bash -e -o pipefail, as the CI runner does.
#
# Fails closed: unless the assets dir holds exactly one .deb, one .rpm and
# one -setup.exe, each with a non-empty "<asset>.sig" next to it, the script
# exits non-zero before writing anything on stdout. The JSON is built by jq
# alone: notes, file names and signatures enter it as jq values, never as
# text spliced into JSON.
#
# Keys. tauri-plugin-updater 2.x looks up "{os}-{arch}-{installer}" first,
# then "{os}-{arch}" (get_urls in its updater.rs). The installer is the
# bundle type that the bundler writes into the binary of each package.
#
# linux-x86_64-deb the .deb, for .deb installs.
# linux-x86_64-rpm the .rpm, for .rpm installs. Without this key they fell
# back on linux-x86_64, downloaded the .deb and refused it.
# linux-x86_64 the .deb again: the fallback of a binary without a
# bundle-type marker, which looks up this key alone.
# Remove it once no supported version predates the
# marker (docs/qa-update-cycle.md, "Les clés de
# latest.json").
# windows-x86_64 the NSIS installer. An NSIS install looks up
# windows-x86_64-nsis first, then this key.
#
# Each URL ends with the file name percent-encoded by jq's @uri, as the upload
# step of release.yml encodes it: the bundle names contain a space.
set -euo pipefail
die() {
printf 'build-latest-json: %s\n' "$*" >&2
exit 1
}
[ "$#" -eq 4 ] || die "usage: build-latest-json.sh <assets-dir> <version> <notes-file> <base-url>"
assets_dir=$1
version=$2
notes_file=$3
base_url=$4
[ -d "$assets_dir" ] || die "assets dir not found: $assets_dir"
[ -n "$version" ] || die "empty version"
[ -f "$notes_file" ] || die "notes file not found: $notes_file"
[ -n "$base_url" ] || die "empty base URL"
# read_asset <kind> <matches...>: the single bundle of a kind and its
# signature, in asset_name and asset_sig. Any other count of matches, or a
# missing or blank "<asset>.sig", stops the script.
asset_name=""
asset_sig=""
read_asset() {
local kind=$1
shift
if [ "$#" -ne 1 ]; then
printf 'build-latest-json: expected exactly one %s in %s, found %d\n' "$kind" "$assets_dir" "$#" >&2
[ "$#" -eq 0 ] || printf ' %s\n' "$@" >&2
exit 1
fi
local file=$1
[ -s "$file.sig" ] || die "missing or empty signature: $file.sig"
asset_name=${file##*/}
asset_sig=$(cat "$file.sig")
[ -n "$asset_sig" ] || die "blank signature: $file.sig"
printf 'build-latest-json: %s <- %s\n' "$kind" "$asset_name" >&2
}
shopt -s nullglob
read_asset .deb "$assets_dir"/*.deb
deb_name=$asset_name
deb_sig=$asset_sig
read_asset .rpm "$assets_dir"/*.rpm
rpm_name=$asset_name
rpm_sig=$asset_sig
read_asset -setup.exe "$assets_dir"/*-setup.exe
exe_name=$asset_name
exe_sig=$asset_sig
pub_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)
jq -n \
--arg version "$version" \
--rawfile notes "$notes_file" \
--arg pub_date "$pub_date" \
--arg base "$base_url" \
--arg deb "$deb_name" --arg deb_sig "$deb_sig" \
--arg rpm "$rpm_name" --arg rpm_sig "$rpm_sig" \
--arg exe "$exe_name" --arg exe_sig "$exe_sig" \
'def asset($name; $sig): {signature: $sig, url: ($base + "/" + ($name | @uri))};
{
version: $version,
notes: $notes,
pub_date: $pub_date,
platforms: {
"linux-x86_64": asset($deb; $deb_sig),
"linux-x86_64-deb": asset($deb; $deb_sig),
"linux-x86_64-rpm": asset($rpm; $rpm_sig),
"windows-x86_64": asset($exe; $exe_sig)
}
}'