Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/scripts/release-artifacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Query on every attempt, including retries that did not rerun the meta job.
set -euo pipefail
: "${GH_REPO:?}" "${GITHUB_RUN_ID:?}" "${GITHUB_OUTPUT:?}"
artifacts=$(gh api --paginate "repos/$GH_REPO/actions/runs/$GITHUB_RUN_ID/artifacts" |
jq -s '[.[].artifacts[] | select(.name == "release-plan" or .name == "release-bundle")]')
if jq -e 'any(.[]; .expired)' <<< "$artifacts" > /dev/null; then
echo 'Release artifacts expired; do not recalculate or rebuild an existing release.' >&2
exit 1
fi
for name in plan bundle; do
exists=$(jq --arg name "release-$name" 'any(.[]; .name == $name)' <<< "$artifacts")
echo "$name=$exists" >> "$GITHUB_OUTPUT"
done
15 changes: 15 additions & 0 deletions .github/scripts/release-current.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Re-read the published index inside every downstream job, including failed-job retries.
set -euo pipefail
: "${VERSION:?}" "${GITHUB_REPOSITORY:?}" "${GH_TOKEN:?}"
index=$(curl --fail --silent --show-error \
--header "Authorization: Bearer $GH_TOKEN" \
--header 'Accept: application/vnd.github.raw+json' \
--header 'Cache-Control: no-cache' \
"${GITHUB_API_URL:-https://api.github.com}/repos/$GITHUB_REPOSITORY/contents/stable.json?ref=version")
if ! jq -e --arg version "$VERSION" --arg tag "${TAG:-v$VERSION}" --arg commit "${COMMIT:-}" \
'.version == $version and .details.tag == $tag and
($commit == "" or .details.commit == $commit)' <<< "$index" > /dev/null; then
echo 'This is no longer the indexed stable release; refusing to publish downstream.' >&2
exit 1
fi
82 changes: 82 additions & 0 deletions .github/scripts/release-files.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# Source-file operations shared by release preparation and its PR check.
set -euo pipefail

package_version() {
cargo metadata --locked --no-deps --format-version 1 |
jq -r '.packages[] | select(.name == "maa-cli") | .version'
}

release_notes() {
awk -v heading="## Release $1" '
/^## / { if (found) exit; if ($0 == heading) found = 1 }
found { print }
END { if (!found) exit 1 }
' CHANGELOG.md
}

case "${1:?expected prepare, check, notes, or prerelease-notes}" in
prepare)
: "${VERSION:?}" "${PREVIOUS_TAG:?}"
current_version=$(package_version)
if [[ "$current_version" != "$VERSION" ]]; then
# Versions have already been parsed and validated by release select-version.
sed -i.bak "s/^version = \"$current_version\"$/version = \"$VERSION\"/" \
crates/maa-cli/Cargo.toml
mv crates/maa-cli/Cargo.toml.bak "$RUNNER_TEMP/Cargo.toml.before-release"
cargo update -p maa-cli --precise "$VERSION" --offline
fi
[[ "$(package_version)" == "$VERSION" ]]

# A previous preparation may already be on main without a published tag.
# Remove only its section; git-cliff owns generation and prepending.
awk -v heading="## Release $VERSION" '
/^## / { skip = ($0 == heading) }
!skip { print }
' CHANGELOG.md > "$RUNNER_TEMP/changelog-base.md"
mv "$RUNNER_TEMP/changelog-base.md" CHANGELOG.md
git-cliff --config cliff.toml --tag "v$VERSION" \
--prepend CHANGELOG.md "$PREVIOUS_TAG..$(git rev-parse HEAD)"

# A separately bumped Cargo version leaves only CHANGELOG.md to change.
git diff --name-only | while IFS= read -r file; do
case "$file" in
CHANGELOG.md|Cargo.lock|crates/maa-cli/Cargo.toml) ;;
*) echo "Unexpected release preparation change: $file" >&2; exit 1 ;;
esac
done
if git diff --quiet; then
echo 'Release files already match; no preparation PR is needed.' >&2
exit 1
fi
;;
check)
: "${PREVIOUS_TAG:?}"
version=$(package_version)
release_notes "$version" > "$RUNNER_TEMP/actual-release-notes.md"
git-cliff --config cliff.toml --tag "v$version" --strip all \
--output "$RUNNER_TEMP/expected-release-notes.md" \
"$PREVIOUS_TAG..$(git rev-parse HEAD)"
perl -0pi -e 's/\s+\z/\n/' "$RUNNER_TEMP/"{actual,expected}-release-notes.md
if ! diff -u "$RUNNER_TEMP/"{actual,expected}-release-notes.md; then
echo 'Release PR is stale. Run Prepare Stable Release again before merging.' >&2
exit 1
fi
;;
notes)
release_notes "${2:?expected version}"
;;
prerelease-notes)
: "${VERSION:?}" "${TAG:?}"
# Use an explicit range: on a dev branch that merges main, git-cliff's
# --unreleased also lists fixes already published in the merged stable tags.
previous=$(git tag --merged HEAD --list 'v*' |
awk '/^v[0-9]+\.[0-9]+\.[0-9]+$/' | sort -V | tail -n 1)
if [[ -z "$previous" ]]; then
echo 'No reachable stable vX.Y.Z tag found' >&2
exit 1
fi
git-cliff --config cliff.toml --strip all "$previous..$(git rev-parse HEAD)"
;;
*) echo "Unknown operation: $1" >&2; exit 1 ;;
esac
15 changes: 15 additions & 0 deletions .github/scripts/release-is-newer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Exit 0 when stable version $1 is newer than packaged version $2.
# Used to avoid moving a beta package back to an older stable release.
set -euo pipefail
stable=${1:?expected stable version}
current=${2:?expected current version}
core() { grep -oE '^[0-9]+\.[0-9]+\.[0-9]+' <<< "$1"; }
stable_core=$(core "$stable")
current_core=$(core "$current")
if [[ "$stable_core" == "$current_core" ]]; then
# The stable release supersedes a prerelease of the same version.
[[ "$current" != "$current_core" ]]
exit
fi
[[ "$(printf '%s\n%s\n' "$stable_core" "$current_core" | sort -V | tail -n 1)" == "$stable_core" ]]
46 changes: 46 additions & 0 deletions .github/scripts/release-readiness.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Decide whether a PR or merge-queue entry carries a release, then verify its files.
set -euo pipefail
: "${EVENT_NAME:?}" "${RUNNER_TEMP:?}"
files="$(cd "$(dirname "$0")" && pwd)/release-files.sh"
read -ra xtask <<< "${XTASK:-cargo x --}"

case "$EVENT_NAME" in
pull_request)
: "${LABELED:?}" "${HEAD_REF:?}" "${BASE_SHA:?}" "${HEAD_SHA:?}"
if [[ "$LABELED" != true ]]; then
if [[ "$HEAD_REF" == release-prep/* ]]; then
echo 'This release-prep PR has no release label; merging it would not publish.' >&2
exit 1
fi
echo 'Not a release PR.'
exit 0
fi
changed=$(git diff --name-only "$BASE_SHA...$HEAD_SHA")
;;
merge_group)
# Each queue entry is checked at its own head. Later entries contain earlier
# releases, so only the entry that lands the release commit is checked.
subject=$(git log -1 --format=%s HEAD)
prepare='^chore\(release\): prepare for v'
merge='^Merge pull request #[0-9]+ from [^ ]+/release-prep/v'
if [[ ! "$subject" =~ $prepare && ! "$subject" =~ $merge ]]; then
echo 'Not a release queue entry.'
exit 0
fi
changed=$(git diff --name-only HEAD^1 HEAD)
;;
*) echo "Unsupported event: $EVENT_NAME" >&2; exit 1 ;;
esac

while IFS= read -r file; do
case "$file" in
CHANGELOG.md|Cargo.lock|crates/maa-cli/Cargo.toml|'') ;;
*) echo "Unexpected file in release PR: $file" >&2; exit 1 ;;
esac
done <<< "$changed"

: > "$RUNNER_TEMP/readiness-candidate"
GITHUB_OUTPUT="$RUNNER_TEMP/readiness-candidate" "${xtask[@]}" release select-version
PREVIOUS_TAG=$(sed -n 's/^previous_tag=//p' "$RUNNER_TEMP/readiness-candidate") \
bash "$files" check
38 changes: 38 additions & 0 deletions .github/scripts/release-tag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
: "${TAG:?}" "${COMMIT:?}" "${GH_REPO:?}" "${RUNNER_TEMP:?}"
refs=$(git ls-remote --tags origin "refs/tags/$TAG" "refs/tags/$TAG^{}")
existing=$(awk '/\^\{\}$/ { peeled=$1 } { direct=$1 } END { print peeled ? peeled : direct }' <<< "$refs")
if [[ -z "$existing" || "$existing" == "$COMMIT" ]]; then
exit 0
fi
if [[ "$TAG" != nightly ]]; then
echo "Tag $TAG targets $existing instead of $COMMIT" >&2
exit 1
fi

if gh api "repos/$GH_REPO/releases/tags/nightly" --silent 2> "$RUNNER_TEMP/nightly-error"; then
gh release delete nightly --yes --cleanup-tag
elif grep -q 'HTTP 404' "$RUNNER_TEMP/nightly-error"; then
# The tag can survive a failed release creation or manual deletion of the release.
gh api --method DELETE "repos/$GH_REPO/git/refs/tags/nightly"
else
cat "$RUNNER_TEMP/nightly-error" >&2
exit 1
fi

# Recreating a release while GitHub still sees the old tag can leave it as a draft.
for _ in {1..20}; do
status=0
git ls-remote --exit-code --tags origin "refs/tags/$TAG" > /dev/null || status=$?
case "$status" in
2)
sleep "${TAG_SETTLE_SECONDS:-5}"
exit 0
;;
0) sleep "${TAG_POLL_SECONDS:-3}" ;;
*) exit "$status" ;;
esac
done
echo "Tag $TAG is still visible after deleting it" >&2
exit 1
Loading
Loading