#!/usr/bin/env bash # Build latest.json, the updater manifest of a release, from its assets (#320). # # Usage: build-latest-json.sh # # 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://///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 ".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. # windows-x86_64 the NSIS installer. An NSIS install looks up # windows-x86_64-nsis first, then this key. # # No generic linux-x86_64 key since 2026-10-11 (Max's decision on #320). Every # version that ships the updater (v0.2.1 and later, tauri-plugin-updater # 2.10.0) carries its bundle-type marker, so .deb and .rpm installs never # reach that key. Its only readers were the AppImage installs v0.4.3 to # v0.4.5 and binaries run outside a package, and the .deb it served overwrote # them: they now get TargetsNotFound when checking, nothing installed. See # docs/qa-update-cycle.md, "Les clés de latest.json". # # 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=$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 : 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 ".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-deb": asset($deb; $deb_sig), "linux-x86_64-rpm": asset($rpm; $rpm_sig), "windows-x86_64": asset($exe; $exe_sig) } }'