Files
Meshtastic-Android/.github/workflows/post-release-cleanup.yml
T

201 lines
9.2 KiB
YAML

name: Post-Release Cleanup
on:
workflow_dispatch:
inputs:
base_version:
description: 'The base version to clean up (e.g., 2.3.0)'
required: true
type: string
confirm_deletion:
description: 'WARNING: This is a destructive action. Set to true to perform deletion. Defaults to a dry run.'
required: true
type: boolean
default: false
permissions:
contents: write
# Destructive (deletes releases + tags): serialize dispatches so two cleanups
# can never interleave.
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
cleanup_prereleases:
# ubuntu-slim: gh + git + jq + stdlib python is the whole toolchain, and the gh-pages
# worktree needs no privileges. Slim is single-CPU (the full-history clone below is slower
# than on a VM) and its 15-minute platform cap is hard, matching the timeout already set
# here. Each step is independently re-runnable, so a capped run is retryable.
runs-on: ubuntu-slim
timeout-minutes: 15
# Dispatch inputs reach the shell as environment variables rather than being
# interpolated into the script text: base_version is free-form and feeds a
# regex that drives `rm -rf` and tag deletion, so a value containing quotes,
# $(...) or a newline must not be able to break out of the assignment.
env:
BASE_VERSION: ${{ github.event.inputs.base_version }}
CONFIRM_DELETION: ${{ github.event.inputs.confirm_deletion }}
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
fetch-depth: 0
# Shared by every step below: reject anything that is not a bare X.Y.Z
# before it reaches a regex, a tag deletion or an rm -rf.
- name: Validate base_version
run: |
set -euo pipefail
if [[ ! "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "base_version must be a bare X.Y.Z version (got '$BASE_VERSION')." >&2
exit 1
fi
echo "Cleaning up pre-releases for $BASE_VERSION."
- name: Cleanup pre-releases and their tags
id: cleanup_releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Internal/open/closed pre-releases are cut continuously across the whole
# dev cycle, including before the version-bump commit lands — so some of
# them still carry the PRIOR release's version number (e.g. a
# v2.7.14-internal.N draft built while working toward 2.8.0). Scoping this
# sweep to "v${BASE_VERSION}-*" misses exactly those, leaving stale drafts
# behind forever. Once base_version has shipped as a stable release, every
# numbered internal/open/closed pre-release — regardless of which version
# it was tagged under — is superseded, so match on the pre-release SHAPE
# instead of a version prefix.
TAG_PATTERN='^v[0-9]+\.[0-9]+\.[0-9]+-(internal|open|closed)\.[0-9]+$'
echo "Searching for pre-releases matching pattern '$TAG_PATTERN'."
RELEASES_TO_DELETE=$(gh release list --json tagName,isPrerelease,isDraft --limit 1000 | jq -r --arg pattern "$TAG_PATTERN" '.[] | select((.isPrerelease == true or .isDraft == true) and .tagName != null and (.tagName | test($pattern))) | .tagName')
if [ -z "$RELEASES_TO_DELETE" ]; then
echo "No stale internal/open/closed pre-releases found."
else
if [[ "$CONFIRM_DELETION" == "true" ]]; then
echo "!!! DELETING RELEASES AND TAGS !!!"
echo "The following pre-releases and their tags will be deleted:"
echo "$RELEASES_TO_DELETE"
echo "$RELEASES_TO_DELETE" | xargs -n 1 gh release delete --cleanup-tag --yes
else
echo "DRY RUN: The following pre-releases and their tags would be deleted:"
echo "$RELEASES_TO_DELETE"
fi
fi
# The docs site keeps a per-tag snapshot for every open/closed testing tag
# in a version cycle (see docs-release.yml). Once vX.Y.Z ships, /vX.Y.Z/
# supersedes them all, so reap them to stop gh-pages growing without bound.
- name: Cleanup pre-release docs snapshots on gh-pages
run: |
set -euo pipefail
DRY_RUN=true
[[ "$CONFIRM_DELETION" == "true" ]] && DRY_RUN=false
if ! git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1; then
echo "No gh-pages branch; nothing to clean."
exit 0
fi
work="$(mktemp -d)"
rmdir "$work"
git fetch --quiet origin gh-pages
git worktree add --quiet -B gh-pages "$work" FETCH_HEAD
trap 'git worktree remove --force "$work" 2>/dev/null || true' EXIT
# Reaping is only sound because /vX.Y.Z/ supersedes these snapshots.
# If it is missing, the production docs never published (or this was
# dispatched for the wrong version) and deleting the snapshots would
# destroy the only docs for this version — and whatever the site root
# currently falls back to. Fail closed instead.
if [ ! -d "$work/v${BASE_VERSION}" ]; then
echo "Refusing to reap docs snapshots: /v${BASE_VERSION}/ is not published on gh-pages." >&2
echo "Run Docs Release against the v${BASE_VERSION} tag first." >&2
exit 1
fi
# Open/closed test-track snapshots are cut continuously across the whole
# dev cycle, including before the version-bump commit lands, so some may
# still be named after the PRIOR release (e.g. v2.7.14-open.3 built while
# working toward 2.8.0). Once /v${BASE_VERSION}/ is confirmed published
# above, every numbered open/closed snapshot is superseded regardless of
# which version it was tagged under — so match the snapshot-dir SHAPE
# instead of a version prefix.
mapfile -t stale < <(
find "$work" -maxdepth 1 -mindepth 1 -type d \
-regextype posix-extended \
-regex ".*/v[0-9]+\.[0-9]+\.[0-9]+-(open|closed)\.[0-9]+" -printf '%f\n' | sort
)
if [ ${#stale[@]} -eq 0 ]; then
echo "No pre-release docs snapshots found."
exit 0
fi
printf 'Pre-release docs snapshots to reap:\n'
printf ' %s\n' "${stale[@]}"
if [ "$DRY_RUN" = true ]; then
echo "DRY RUN: the directories above would be removed from gh-pages."
exit 0
fi
for d in "${stale[@]}"; do
rm -rf "${work:?}/$d"
done
# Rebuild versions.json (and the root placeholder) from what remains,
# using the same generator the publisher runs.
python3 scripts/docs/regenerate-versions.py "$work"
cd "$work"
git add -A
if git diff --cached --quiet; then
echo "gh-pages already clean."
exit 0
fi
git -c user.name='github-actions[bot]' \
-c user.email='41898282+github-actions[bot]@users.noreply.github.com' \
commit -q -m "docs: reap pre-release snapshots for ${BASE_VERSION}"
git push --quiet origin HEAD:gh-pages
echo "Removed ${#stale[@]} pre-release docs snapshot(s) from gh-pages."
- name: Cleanup dangling pre-release tags
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# Same rationale as the release-cleanup step above: match the
# internal/open/closed pre-release tag SHAPE across all versions, not just
# tags prefixed with this dispatch's base_version.
TAG_PATTERN='^v[0-9]+\.[0-9]+\.[0-9]+-(internal|open|closed)\.[0-9]+$'
echo "Searching for any remaining remote pre-release tags matching pattern '$TAG_PATTERN'."
# This finds all remote tags. Look them up on their own first so a
# failure here (e.g. a network blip) fails the step loudly, instead of
# being swallowed by the trailing `|| true` a downstream grep needs
# (grep exits non-zero on zero matches, which is not itself an error).
REMOTE_TAGS=$(git ls-remote --tags origin "refs/tags/v*" | awk '{print $2}' | sed 's|refs/tags/||')
# Some tags may have been deleted already by the previous 'release delete' step.
TAGS_TO_DELETE=$(grep -E "$TAG_PATTERN" <<<"$REMOTE_TAGS" || true)
if [ -z "$TAGS_TO_DELETE" ]; then
echo "No dangling pre-release tags found."
else
if [[ "$CONFIRM_DELETION" == "true" ]]; then
echo "!!! DELETING DANGLING TAGS !!!"
echo "The following pre-release tags will be deleted:"
# We pipe to xargs which will run the command for each tag.
# If a tag was already deleted by the previous 'release delete' step, this will fail for that tag.
# We add '|| true' to ignore any errors and ensure the workflow doesn't fail.
echo "$TAGS_TO_DELETE" | xargs -n 1 -I {} sh -c 'git push --delete origin {} || true'
else
echo "DRY RUN: The following dangling pre-release tags would be deleted:"
echo "$TAGS_TO_DELETE"
fi
fi