diff --git a/docs/lib.md b/docs/lib.md index de79f7e..ab74c3a 100644 --- a/docs/lib.md +++ b/docs/lib.md @@ -45,6 +45,15 @@ have their own helpers outside that pair: `fetch_and_deploy_gh_tag`, Deploy modes: prebuilt release asset, source tarball, `.deb` package, or a plain URL through `fetch_and_deploy_from_url`. +Both entry points keep a version stamp in `~/.`. It is written when the +deploy finishes, so a build that fails afterwards leaves it at the new +version; the marker `~/..pending` beside it holds the version the run +started from and stays until a run that deployed exits cleanly. +`check_for_release` redoes the update while the marker exists, and both +helpers export `CS_PREVIOUS_VERSION`, the version from before the update, +which stays right on that redo. Prefer it over reading `~/.` when a +migration depends on the old version. + ## [`runtime.func`](../lib/runtime.func) Language runtimes and the application-level installers: `setup_nodejs`, diff --git a/lib/API.txt b/lib/API.txt index c9ba53e..551a4fe 100644 --- a/lib/API.txt +++ b/lib/API.txt @@ -22,6 +22,7 @@ _forge_highest_version _forge_http_get _forge_is_current _forge_keep_raw +_forge_previous_version _forge_release_json _forge_same_shape _forge_scan_older diff --git a/lib/forge.func b/lib/forge.func index df05be5..2e4ff79 100644 --- a/lib/forge.func +++ b/lib/forge.func @@ -1670,6 +1670,17 @@ _forge_release_json() { # still reads as an update. Version-aware ordering would change what every # app sees and belongs in its own change # ------------------------------------------------------------------------------ +# The version a run started from: the marker of an unfinished run, otherwise +# the stamp. Empty on a fresh install. +_forge_previous_version() { + local version_file="${1:?version file}" + if [[ -f "${version_file}.pending" ]]; then + printf '%s' "$(<"${version_file}.pending")" + elif [[ -f "$version_file" ]]; then + printf '%s' "$(<"$version_file")" + fi +} + check_for_release() { local forge="${1:?forge}" app="${2:?app}" source="${3:?repo}" local pinned_version_in="${4:-}" pin_reason="${5:-}" tag_prefix="${6:-}" @@ -1794,8 +1805,13 @@ check_for_release() { fi fi current="$(_forge_strip_v "$current")" + CS_PREVIOUS_VERSION="$(_forge_strip_v "$(_forge_previous_version "$current_file")")" + export CS_PREVIOUS_VERSION local unfinished="" - [[ -f "${current_file}.pending" ]] && unfinished=" (the last run did not finish, doing it again)" + if [[ -f "${current_file}.pending" ]]; then + unfinished=" (the last run did not finish, doing it again)" + current="$CS_PREVIOUS_VERSION" + fi if [[ -n "$pinned_version_in" ]]; then local pin_clean match_raw="" i @@ -2097,10 +2113,14 @@ fetch_and_deploy_release() { # throughout, which mangles tags like version-2. version="$(_forge_strip_v "$tag_name")" - # The stamp written at the end only says "deployed". This marker says the - # run around it still has to finish: on_exit removes it when the run exits 0, - # and check_for_release redoes the update while it exists (ProxmoxVE #18050). - touch "${version_file}.pending" 2>/dev/null || true + # The stamp written at the end only says "deployed". The marker beside it + # holds the version this update started from and stays until a run that + # deployed exits 0 (on_exit); check_for_release redoes the update while it + # exists, and CS_PREVIOUS_VERSION still names the real old version on that + # redo (ProxmoxVE #18050). + CS_PREVIOUS_VERSION="$(_forge_previous_version "$version_file")" + export CS_PREVIOUS_VERSION + [[ -f "${version_file}.pending" ]] || printf '%s' "$CS_PREVIOUS_VERSION" >"${version_file}.pending" 2>/dev/null || true _CS_DEPLOYED=1 local current_version="" diff --git a/tests/release-stamp-pending.sh b/tests/release-stamp-pending.sh index e76a26b..96ad12f 100644 --- a/tests/release-stamp-pending.sh +++ b/tests/release-stamp-pending.sh @@ -4,7 +4,7 @@ set -uo pipefail CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" eval "$(sed -n '/^_FORGE_JQ_VKEY_DEF=/,/^_FORGE_JQ_VKEY=/p' "$CORE_DIR/lib/forge.func")" eval "$(awk '/^_forge_(descriptor|strip_v|is_current|highest_version|same_shape|stable_filter)[(][)] [{]/,/^}/' "$CORE_DIR/lib/forge.func")" -eval "$(awk '/^check_for_release[(][)] [{]/,/^}/' "$CORE_DIR/lib/forge.func")" +eval "$(awk '/^(check_for_release|_forge_previous_version)[(][)] [{]/,/^}/' "$CORE_DIR/lib/forge.func")" eval "$(awk '/^on_exit[(][)] [{]/,/^}/' "$CORE_DIR/core/error_handler.func")" TEST_DIR="$(mktemp -d)" @@ -66,6 +66,21 @@ expect "no stamp at all: a normal install" 0 "$(check "" no)" expect "pinned and current: held back" 1 "$(check 2.16.0 no 2.16.0 "testing")" expect "pinned, current, marker: redone anyway" 0 "$(check 2.16.0 yes 2.16.0 "testing")" +# The marker carries the version the run started from; the redo reports it +: >"$LOG" +echo 2.16.0 >"$HOME/.npm" +printf '%s' 2.15.1 >"$HOME/.npm.pending" +rc=0 +check_for_release github NPM owner/repo >/dev/null 2>&1 || rc=$? +expect "a marker with the old version: redone" 0 "$rc" +expect "the message names the version the run started from" 1 "$(grep -c '2.15.1 -> 2.16.0' "$LOG")" +expect "CS_PREVIOUS_VERSION is the old version on the redo" 2.15.1 "$CS_PREVIOUS_VERSION" +rm -f "$HOME/.npm.pending" +check_for_release github NPM owner/repo >/dev/null 2>&1 || true +expect "without a marker it is the stamp" 2.16.0 "$CS_PREVIOUS_VERSION" +rm -f "$HOME/.npm" +expect "a fresh install has no previous version" "" "$(_forge_previous_version "$HOME/.npm")" + # on_exit: only a run that deployed and exits 0 removes the marker touch "$HOME/.npm.pending" (_CS_DEPLOYED=1; on_exit) 2>/dev/null