Files
jellytau/scripts/set-version.sh
dtourolleandClaude Opus 5 3619f71aba
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 6m55s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 5m21s
Traceability Validation / Check Requirement Traces (push) Successful in 15s
Build & Release / Run Tests (push) Successful in 7m36s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 2m57s
Build & Release / Build Linux (push) Successful in 20m4s
Build & Release / Build Windows (push) Successful in 8m42s
Build & Release / Build Android (push) Successful in 30m30s
Build & Release / Create Release (push) Successful in 17s
build: make the git tag the single source of truth for the version (DR-153)
The version lived in four files — package.json, tauri.conf.json, Cargo.toml and
Cargo.lock — that had to be hand-edited in lockstep, and the release workflow
rewrote exactly one of them. A tagged build therefore produced an installer
named for the tag wrapped around package metadata naming the previous release,
and the Linux job, which had no version step at all, shipped whatever happened
to be committed.

scripts/set-version.sh now writes all four from one argument and is the only
thing that does. Every release job calls it with the tag, including the Linux
job that was missing one. The committed versions become a placeholder for dev
builds rather than something to maintain by hand.

The Android versionCode moves into the same script, unchanged in formula
(1000 + major*10000 + minor*100 + patch). It stays inline-documented because the
reasoning is not obvious: builds already in the field shipped code 1000, and
Android refuses an update whose code is lower than the installed one, so a
formula that can emit a smaller number for a newer release bricks updates
irreversibly. UT-150 asserts that property directly — monotonic across an
upgrade sequence, and always above the floor.

Two edge cases the previous inline version got wrong:

- A prerelease tag (v0.6.0-rc1) made $(( 0-rc1 )) abort the step under set -e.
  The suffix is stripped before the arithmetic; the manifests keep it.
- CI passes "${GITHUB_REF#refs/tags/}" unconditionally, which on a branch build
  is still a full ref. That reached the validator verbatim and would have failed
  every untagged Android build; a non-tag ref now falls back to git describe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 21:21:58 +02:00

112 lines
4.8 KiB
Bash
Executable File

#!/bin/bash
# Stamp the release version into every file that carries it.
#
# The git tag is the single source of truth for a release version. The versions
# committed in package.json / tauri.conf.json / Cargo.toml are a placeholder for
# dev builds; a tagged build overwrites all of them from the tag so they cannot
# disagree with each other or with the tag.
#
# Usage:
# ./scripts/set-version.sh 0.5.0 # explicit
# JELLYTAU_VERSION=0.5.0 ./scripts/set-version.sh
# ./scripts/set-version.sh # derive from git describe (dev builds)
#
# Accepts the version with or without a leading "v".
#
# Why a script and not four sed lines in CI: the version lived in four files and
# CI only ever rewrote one of them (tauri.conf.json), so a tagged release shipped
# a matching installer name and mismatched package metadata. Keeping the write in
# one place is what makes "the tag is authoritative" actually true.
set -euo pipefail
cd "$(dirname "$0")/.."
VERSION="${1:-${JELLYTAU_VERSION:-}}"
# CI passes "${GITHUB_REF#refs/tags/}" unconditionally, which on an untagged
# build is still a full ref ("refs/heads/master"). Treat anything that is not a
# bare version as "no version given" and fall through to git describe, so a
# branch build gets a sane dev version instead of failing the job.
case "$VERSION" in
refs/*) VERSION="" ;;
esac
if [ -z "$VERSION" ]; then
# No explicit version: derive from the most recent tag. Dev builds land on
# something like 0.5.0 (exact tag) or 0.5.0-3-gabc1234 (ahead of the tag).
VERSION="$(git describe --tags --always --match 'v*' 2>/dev/null || echo "0.0.0")"
fi
# Tags are written v0.5.0; the files carry a bare semver.
VERSION="${VERSION#v}"
# Validate before writing anything — a malformed version silently propagated
# into four files is far worse than a failed script.
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'; then
echo "❌ Not a valid semver: '$VERSION'" >&2
echo " Expected MAJOR.MINOR.PATCH with an optional -prerelease/+build suffix." >&2
exit 1
fi
echo "📌 Setting version to $VERSION"
# --- The three committed manifests -----------------------------------------
# Anchored to the first "version" key so a dependency's version is never hit.
# package.json — the top-level "version", which sits in the first few lines.
perl -0pi -e 's/("version"\s*:\s*)"[^"]*"/$1"'"$VERSION"'"/' package.json
# tauri.conf.json — likewise; this is the one the bundler reads for installer
# names, and the one CI used to patch alone.
perl -0pi -e 's/("version"\s*:\s*)"[^"]*"/$1"'"$VERSION"'"/' src-tauri/tauri.conf.json
# Cargo.toml — only the [package] version, never a dependency's. Restricted to
# the first occurrence of a line-anchored `version = "..."`.
perl -0pi -e 's/^(version\s*=\s*)"[^"]*"/$1"'"$VERSION"'"/m' src-tauri/Cargo.toml
# Cargo.lock — the jellytau entry. Left alone if the lock has not been generated
# yet; the next cargo invocation writes it. Cargo would otherwise rewrite the
# lock mid-build and dirty the tree.
if [ -f src-tauri/Cargo.lock ]; then
perl -0pi -e 's/(name = "jellytau"\nversion = )"[^"]*"/$1"'"$VERSION"'"/' src-tauri/Cargo.lock
fi
# --- Android versionCode ----------------------------------------------------
# Only when the generated Android project exists (i.e. after `tauri android
# init`); on Linux/Windows jobs there is nothing to stamp.
#
# `tauri android init` derives a versionCode from the semver (0.0.15 -> 15).
# That is both tiny and NOT monotonic across our history: earlier local/dev
# builds shipped versionCode 1000 (from a 0.1.0 config), so a plain 15 is a
# *downgrade* and Android refuses the update.
#
# code = 1000 + major*10000 + minor*100 + patch
# e.g. 0.0.14 -> 1014, 0.0.15 -> 1015, 0.1.0 -> 1100, 1.0.0 -> 11000.
PROPS="src-tauri/gen/android/app/tauri.properties"
if [ -f "$PROPS" ]; then
# Strip any -rc1/+build suffix first: it is not numeric, and feeding it to
# $(( )) would abort the script under `set -e`.
CORE="${VERSION%%-*}"
CORE="${CORE%%+*}"
MAJ=$(echo "$CORE" | cut -d. -f1)
MIN=$(echo "$CORE" | cut -d. -f2)
PAT=$(echo "$CORE" | cut -d. -f3)
: "${MAJ:=0}" "${MIN:=0}" "${PAT:=0}"
CODE=$(( 1000 + MAJ*10000 + MIN*100 + PAT ))
echo " versionCode=$CODE (from $CORE)"
if grep -q '^tauri.android.versionCode=' "$PROPS"; then
sed -i "s/^tauri.android.versionCode=.*/tauri.android.versionCode=$CODE/" "$PROPS"
else
echo "tauri.android.versionCode=$CODE" >> "$PROPS"
fi
fi
# --- Report -----------------------------------------------------------------
echo "✅ Version stamped:"
grep -m1 '"version"' package.json | sed 's/^/ package.json: /'
grep -m1 '"version"' src-tauri/tauri.conf.json | sed 's/^/ tauri.conf.json: /'
grep -m1 '^version' src-tauri/Cargo.toml | sed 's/^/ Cargo.toml: /'
[ -f "$PROPS" ] && grep '^tauri.android.versionCode=' "$PROPS" | sed 's/^/ tauri.properties: /'
exit 0