Skip to content
Merged
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
218 changes: 218 additions & 0 deletions .github/workflows/delete-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: Delete release

run-name: Delete release v${{ inputs.version }}

on:
workflow_dispatch:
inputs:
version:
description: Release version without the leading v, for example 0.10.1 or 0.10.1-rc.1
required: true
confirm:
description: Confirm deletion of the GitHub release and tag
required: true
type: boolean
default: false

permissions:
contents: write

concurrency:
group: delete-release-${{ inputs.version }}
cancel-in-progress: false

jobs:
delete-release:
runs-on: ubuntu-latest
env:
VERSION: ${{ inputs.version }}
CONFIRM: ${{ inputs.confirm }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ env.RELEASE_TOKEN }}

- name: Validate deletion input
id: release
shell: bash
run: |
set -euo pipefail

version="${VERSION}"

if [ "${CONFIRM}" != "true" ]; then
echo "Deletion must be confirmed before continuing." >&2
exit 1
fi

if [[ "${version}" == v* ]]; then
echo "Version input must not include the leading v: ${version}" >&2
exit 1
fi

if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then
echo "Version must be SemVer without a leading v, for example 0.10.1 or 0.10.1-rc.1." >&2
exit 1
fi

echo "tag=v${version}" >> "${GITHUB_OUTPUT}"

- name: Delete GitHub release
env:
GH_TOKEN: ${{ env.RELEASE_TOKEN }}
shell: bash
run: |
set -euo pipefail

tag="${{ steps.release.outputs.tag }}"
error_file="$(mktemp)"

if gh release view "${tag}" >/dev/null 2>"${error_file}"; then
gh release delete "${tag}" --yes
echo "Deleted GitHub release: ${tag}"
elif grep -qiE "not found|HTTP 404" "${error_file}"; then
echo "GitHub release does not exist, skipping: ${tag}"
else
cat "${error_file}" >&2
exit 1
fi

- name: Delete git tag
shell: bash
run: |
set -euo pipefail

tag="${{ steps.release.outputs.tag }}"

set +e
git ls-remote --exit-code --tags --refs origin "refs/tags/${tag}" >/dev/null 2>&1
status=$?
set -e

if [ "${status}" -eq 0 ]; then
git push origin ":refs/tags/${tag}"
echo "Deleted git tag: ${tag}"
elif [ "${status}" -eq 2 ]; then
echo "Git tag does not exist, skipping: ${tag}"
else
echo "Failed to check remote tag: ${tag}" >&2
exit "${status}"
fi

- name: Update channel tags
shell: bash
run: |
set -euo pipefail

refs_file="$(mktemp)"
trap 'rm -f "${refs_file}"' EXIT

git fetch origin --tags --force
git ls-remote --tags --refs origin "refs/tags/v*" > "${refs_file}"

selection="$(python3 - "${refs_file}" <<'PY'
import functools
import re
import sys
from pathlib import Path

tag_re = re.compile(r"^refs/tags/v(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z][0-9A-Za-z.-]*))?$")

def parse_identifier(value):
return (0, int(value)) if value.isdigit() else (1, value)

def compare_identifier(left, right):
left_kind, left_value = parse_identifier(left)
right_kind, right_value = parse_identifier(right)
if left_kind != right_kind:
return -1 if left_kind < right_kind else 1
return (left_value > right_value) - (left_value < right_value)

def compare_version(left, right):
if left["core"] != right["core"]:
return (left["core"] > right["core"]) - (left["core"] < right["core"])

left_pre = left["prerelease"]
right_pre = right["prerelease"]
if left_pre == right_pre:
return 0
if left_pre is None:
return 1
if right_pre is None:
return -1

left_parts = left_pre.split(".")
right_parts = right_pre.split(".")
for left_part, right_part in zip(left_parts, right_parts):
result = compare_identifier(left_part, right_part)
if result:
return result
return (len(left_parts) > len(right_parts)) - (len(left_parts) < len(right_parts))

versions = []
for line in Path(sys.argv[1]).read_text(encoding="utf-8").splitlines():
parts = line.split("\t", 1)
if len(parts) != 2:
continue
match = tag_re.match(parts[1])
if not match:
continue
versions.append({
"tag": parts[1].rsplit("/", 1)[-1],
"core": tuple(int(match.group(index)) for index in (1, 2, 3)),
"prerelease": match.group(4),
})

sort_key = functools.cmp_to_key(compare_version)
stable = [version for version in versions if version["prerelease"] is None]
latest = max(stable, key=sort_key, default=None)
prereleases = [version for version in versions if version["prerelease"] is not None]
if latest is not None:
prereleases = [version for version in prereleases if compare_version(version, latest) > 0]
next_version = max(prereleases, key=sort_key, default=None)

print(f"latest={latest['tag'] if latest else ''}")
print(f"next={next_version['tag'] if next_version else ''}")
PY
)"

latest_target=""
next_target=""
while IFS="=" read -r channel target; do
case "${channel}" in
latest) latest_target="${target}" ;;
next) next_target="${target}" ;;
esac
done <<< "${selection}"

remote_tag_exists() {
local tag="$1"
git ls-remote --exit-code --tags --refs origin "refs/tags/${tag}" >/dev/null 2>&1
}

update_channel_tag() {
local channel="$1"
local target="$2"

if [ -n "${target}" ]; then
git fetch origin "refs/tags/${target}:refs/tags/${target}" --force
target_sha="$(git rev-list -n 1 "${target}")"
git -c tag.gpgSign=false tag -f "${channel}" "${target_sha}"
git push origin "refs/tags/${channel}" --force
echo "Updated ${channel} tag to ${target} (${target_sha})."
return
fi

if remote_tag_exists "${channel}"; then
git push origin ":refs/tags/${channel}"
echo "Deleted ${channel} tag because no eligible release tag exists."
else
echo "${channel} tag does not exist and no eligible release tag exists."
fi
}

update_channel_tag latest "${latest_target}"
update_channel_tag next "${next_target}"
89 changes: 81 additions & 8 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,73 @@ name: publish manifest
on:
push:
tags: [ 'v*' ]
workflow_dispatch:
inputs:
version:
description: Release version without the leading v, for example 0.10.1 or 0.10.1-rc.1
required: true
update_manifest_release:
description: Also update the shared manifest release asset
required: true
type: boolean
default: false

permissions:
contents: write

jobs:
publish-manifest:
runs-on: ubuntu-latest
env:
VERSION_INPUT: ${{ inputs.version }}
UPDATE_MANIFEST_RELEASE_INPUT: ${{ inputs.update_manifest_release }}
steps:
- name: Set release version
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Resolve release
id: release
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
version="${tag#v}"
echo "VERSION=${version}" >> "${GITHUB_ENV}"

- uses: actions/checkout@v4
if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
tag="${GITHUB_REF_NAME}"
version="${tag#v}"
update_manifest_release="true"
else
version="${VERSION_INPUT}"
update_manifest_release="${UPDATE_MANIFEST_RELEASE_INPUT}"

if [[ "${version}" == v* ]]; then
echo "Version input must not include the leading v: ${version}" >&2
exit 1
fi

tag="v${version}"
fi

if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then
echo "Version must be SemVer without a leading v, for example 0.10.1 or 0.10.1-rc.1." >&2
exit 1
fi

git fetch origin --tags --force

if ! git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
echo "Tag does not exist: ${tag}" >&2
exit 1
fi

git show "${tag}:metadata.lua" > "${RUNNER_TEMP}/metadata.lua"

echo "VERSION=${version}" >> "${GITHUB_ENV}"
echo "TAG=${tag}" >> "${GITHUB_ENV}"
echo "METADATA_PATH=${RUNNER_TEMP}/metadata.lua" >> "${GITHUB_ENV}"
echo "UPDATE_MANIFEST_RELEASE=${update_manifest_release}" >> "${GITHUB_ENV}"
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
echo "update-manifest-release=${update_manifest_release}" >> "${GITHUB_OUTPUT}"

- name: Generate manifest
shell: bash
Expand All @@ -32,7 +82,8 @@ jobs:
import os
import re

text = Path("metadata.lua").read_text(encoding="utf-8")
metadata_path = Path(os.environ.get("METADATA_PATH", "metadata.lua"))
text = metadata_path.read_text(encoding="utf-8")

def string_field(name):
match = re.search(rf'PLUGIN\.{name}\s*=\s*"([^"]*)"', text)
Expand All @@ -46,7 +97,7 @@ jobs:
notes = re.findall(r'"([^"]*)"', notes_match.group(1))

version = os.environ["VERSION"]
tag = f"v{version}"
tag = os.environ.get("TAG", f"v{version}")
repo = os.environ["GITHUB_REPOSITORY"]

manifest = {
Expand All @@ -66,7 +117,29 @@ jobs:

cat manifest.json

- name: Publish manifest
- name: Publish version release manifest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail

tag="${{ steps.release.outputs.tag }}"

if gh release view "${tag}" >/dev/null 2>&1; then
gh release upload "${tag}" manifest.json --clobber
else
args=(release create "${tag}" manifest.json --title "${tag}" --generate-notes)

if [[ "${VERSION}" == *-* ]]; then
args+=(--prerelease)
fi

gh "${args[@]}"
fi

- name: Publish shared manifest
if: steps.release.outputs.update-manifest-release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
Expand Down
Loading