Skip to content

Recover v0.3.2 from multiple artifact runs #1

Recover v0.3.2 from multiple artifact runs

Recover v0.3.2 from multiple artifact runs #1

name: Recover v0.3.2 Release From Artifacts
run-name: Recover ${{ inputs.tag_name }} from multiple artifact runs
on:
workflow_dispatch:
inputs:
tag_name:
description: "Release tag to publish"
required: true
default: v0.3.2
source_ref:
description: "Branch containing the release commit"
required: true
default: v0.3.2
source_sha:
description: "Exact release commit SHA"
required: true
default: 6914c6ec9067054d5084f4e8a6e12a0851deabe0
primary_run_id:
description: "First run to try for every artifact"
required: true
default: "27012650429"
fallback_run_ids:
description: "Fallback run IDs, space or newline separated"
required: true
default: "27010245377 26995768946"
publish_docker:
description: "Build and publish Docker images from recovered Linux packages"
required: true
default: true
type: boolean
publish_release:
description: "Create or update the GitHub release"
required: true
default: true
type: boolean
permissions:
actions: read
contents: write
packages: write
concurrency:
group: recover-release-${{ inputs.tag_name }}
cancel-in-progress: false
jobs:
recover-assets:
name: Recover release assets
runs-on: ubuntu-24.04
outputs:
source_sha: ${{ steps.meta.outputs.source_sha }}
image_repo: ${{ steps.meta.outputs.image_repo }}
package_url: ${{ steps.meta.outputs.package_url }}
steps:
- name: Clone source
uses: actions/checkout@v6
with:
ref: ${{ inputs.source_sha }}
fetch-depth: 0
- name: Verify source and artifact runs
id: meta
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ inputs.tag_name }}
SOURCE_REF: ${{ inputs.source_ref }}
SOURCE_SHA: ${{ inputs.source_sha }}
PRIMARY_RUN_ID: ${{ inputs.primary_run_id }}
FALLBACK_RUN_IDS: ${{ inputs.fallback_run_ids }}
run: |
set -euo pipefail
if [[ ! "${TAG_NAME}" =~ ^v[0-9] ]]; then
echo "tag_name must be a v* release tag, got ${TAG_NAME}" >&2
exit 1
fi
if [[ -z "${SOURCE_SHA}" || ! "${SOURCE_SHA}" =~ ^[0-9a-f]{40}$ ]]; then
echo "source_sha must be a full 40-character commit SHA." >&2
exit 1
fi
git fetch origin +refs/heads/"${SOURCE_REF}":refs/remotes/origin/"${SOURCE_REF}" --no-tags
branch_sha="$(git rev-parse refs/remotes/origin/"${SOURCE_REF}")"
if [[ "${branch_sha}" != "${SOURCE_SHA}" ]]; then
echo "${SOURCE_REF} points to ${branch_sha}, not requested source ${SOURCE_SHA}." >&2
exit 1
fi
fallback_text="${FALLBACK_RUN_IDS//$'\n'/ }"
read -r -a fallback_ids <<< "${fallback_text}"
run_ids=("${PRIMARY_RUN_ID}" "${fallback_ids[@]}")
mkdir -p recovery
: > recovery/run-provenance.tsv
for run_id in "${run_ids[@]}"; do
[[ -z "${run_id}" ]] && continue
run_json="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}")"
run_status="$(jq -r '.status' <<< "${run_json}")"
run_head_sha="$(jq -r '.head_sha' <<< "${run_json}")"
run_url="$(jq -r '.html_url' <<< "${run_json}")"
if [[ "${run_status}" != "completed" ]]; then
echo "Run ${run_id} is ${run_status}, not completed: ${run_url}" >&2
exit 1
fi
if ! git cat-file -e "${run_head_sha}^{commit}" 2>/dev/null; then
git fetch origin "${run_head_sha}" --no-tags
fi
if ! git merge-base --is-ancestor "${run_head_sha}" "${SOURCE_SHA}"; then
echo "Run ${run_id} built ${run_head_sha}, which is not an ancestor of ${SOURCE_SHA}: ${run_url}" >&2
exit 1
fi
printf '%s\t%s\t%s\n' "${run_id}" "${run_head_sha}" "${run_url}" >> recovery/run-provenance.tsv
done
owner="$(printf '%s' "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')"
repo="$(printf '%s' "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')"
image_repo="ghcr.io/${owner}/${repo}"
package_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pkgs/container/${repo}"
echo "source_sha=${SOURCE_SHA}" >> "${GITHUB_OUTPUT}"
echo "image_repo=${image_repo}" >> "${GITHUB_OUTPUT}"
echo "package_url=${package_url}" >> "${GITHUB_OUTPUT}"
echo "Artifact run provenance:"
column -t -s $'\t' recovery/run-provenance.tsv
- name: Download recovered package artifacts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ inputs.tag_name }}
PRIMARY_RUN_ID: ${{ inputs.primary_run_id }}
FALLBACK_RUN_IDS: ${{ inputs.fallback_run_ids }}
run: |
set -euo pipefail
fallback_text="${FALLBACK_RUN_IDS//$'\n'/ }"
read -r -a fallback_ids <<< "${fallback_text}"
run_ids=("${PRIMARY_RUN_ID}" "${fallback_ids[@]}")
required=(
"beellama-bin-macos-arm64.tar.gz|beellama-${TAG_NAME}-bin-macos-arm64.tar.gz"
"beellama-bin-ubuntu-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-x64.tar.gz"
"beellama-bin-ubuntu-arm64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-arm64.tar.gz"
"beellama-bin-ubuntu-cuda-12.4-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-cuda-12.4-x64.tar.gz"
"beellama-bin-ubuntu-cuda-13.1-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-cuda-13.1-x64.tar.gz"
"beellama-bin-ubuntu-vulkan-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-vulkan-x64.tar.gz"
"beellama-bin-ubuntu-rocm-7.2-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-rocm-7.2-x64.tar.gz"
"beellama-bin-ubuntu-sycl-x64.tar.gz|beellama-${TAG_NAME}-bin-ubuntu-sycl-x64.tar.gz"
"beellama-bin-win-cpu-x64.zip|beellama-bin-win-cpu-x64.zip"
"beellama-bin-win-sycl-x64.zip|beellama-bin-win-sycl-x64.zip"
"beellama-bin-win-cuda-12.4-x64.zip|beellama-bin-win-cuda-12.4-x64.zip"
"beellama-bin-win-cuda-13.1-x64.zip|beellama-bin-win-cuda-13.1-x64.zip"
"beellama-bin-win-hip-radeon-x64.zip|beellama-bin-win-hip-radeon-x64.zip"
"cudart-beellama-bin-win-cuda-12.4-x64.zip|cudart-beellama-bin-win-cuda-12.4-x64.zip"
"cudart-beellama-bin-win-cuda-13.1-x64.zip|cudart-beellama-bin-win-cuda-13.1-x64.zip"
)
mkdir -p artifact artifact-download recovery
: > recovery/artifact-provenance.tsv
for item in "${required[@]}"; do
artifact_name="${item%%|*}"
expected_file="${item#*|}"
if [[ -f "artifact/${expected_file}" ]]; then
continue
fi
found="false"
for run_id in "${run_ids[@]}"; do
[[ -z "${run_id}" ]] && continue
download_dir="artifact-download/${run_id}/${artifact_name}"
rm -rf "${download_dir}"
mkdir -p "${download_dir}"
if ! gh run download "${run_id}" --repo "${GITHUB_REPOSITORY}" --name "${artifact_name}" --dir "${download_dir}" >/dev/null 2>&1; then
echo "Artifact ${artifact_name} not found in run ${run_id}."
continue
fi
artifact_file="$(
find "${download_dir}" -type f \( -name "${expected_file}" -o -name "${artifact_name}" \) | head -n 1
)"
if [[ -z "${artifact_file}" ]]; then
echo "Run ${run_id} artifact ${artifact_name} did not contain ${expected_file}."
continue
fi
cp "${artifact_file}" "artifact/${expected_file}"
run_head_sha="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}" --jq '.head_sha')"
printf '%s\t%s\t%s\t%s\n' "${expected_file}" "${artifact_name}" "${run_id}" "${run_head_sha}" >> recovery/artifact-provenance.tsv
found="true"
break
done
if [[ "${found}" != "true" ]]; then
echo "Missing required artifact ${artifact_name} (${expected_file}) in all configured runs." >&2
exit 1
fi
done
echo "Recovered artifact provenance:"
column -t -s $'\t' recovery/artifact-provenance.tsv
ls -lh artifact
- name: Prepare release assets
shell: bash
env:
TAG_NAME: ${{ inputs.tag_name }}
run: |
set -euo pipefail
shopt -s nullglob
mkdir -p release
cpu_zip="artifact/beellama-bin-win-cpu-x64.zip"
temp_dir="$(mktemp -d)"
unzip -q "${cpu_zip}" -d "${temp_dir}"
for target_zip in artifact/beellama-bin-win-*-x64.zip; do
if [[ "$(basename "${target_zip}")" == "beellama-bin-win-cpu-x64.zip" ]]; then
continue
fi
realpath_target_zip="$(realpath "${target_zip}")"
(cd "${temp_dir}" && zip -qr "${realpath_target_zip}" .)
done
rm -rf "${temp_dir}"
for zip_file in artifact/beellama-bin-win-*.zip; do
base_name="$(basename "${zip_file}" .zip)"
mv "${zip_file}" "release/beellama-${TAG_NAME}-${base_name#beellama-}.zip"
done
for zip_file in artifact/cudart-beellama-bin-win-cuda-*.zip; do
base_name="$(basename "${zip_file}" .zip)"
suffix="${base_name#cudart-beellama-bin-win-}"
mv "${zip_file}" "release/beellama-${TAG_NAME}-cudart-win-${suffix}.zip"
done
for tar_file in artifact/*.tar.gz; do
mv "${tar_file}" release/
done
python3 scripts/verify-windows-package.py release/beellama-${TAG_NAME}-bin-win-*.zip
for cuda_version in 12.4 13.1; do
package_zip="release/beellama-${TAG_NAME}-bin-win-cuda-${cuda_version}-x64.zip"
runtime_zip="release/beellama-${TAG_NAME}-cudart-win-cuda-${cuda_version}-x64.zip"
temp_dir="$(mktemp -d)"
unzip -q "${package_zip}" -d "${temp_dir}"
unzip -q "${runtime_zip}" -d "${temp_dir}"
python3 scripts/verify-windows-package.py "${temp_dir}"
rm -rf "${temp_dir}"
done
sha256sum release/* > release/SHA256SUMS.txt
ls -lh release
- name: Write release notes
shell: bash
env:
TAG_NAME: ${{ inputs.tag_name }}
SOURCE_SHA: ${{ inputs.source_sha }}
IMAGE_REPO: ${{ steps.meta.outputs.image_repo }}
PACKAGE_URL: ${{ steps.meta.outputs.package_url }}
run: |
set -euo pipefail
release_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${TAG_NAME}"
changelog_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${SOURCE_SHA}/CHANGELOG.md"
python3 scripts/extract-changelog.py \
--tag "${TAG_NAME}" \
--changelog CHANGELOG.md \
--fallback-url "${changelog_url}" \
--output changelog-fragment.md
python3 scripts/write-release-notes.py \
--tag "${TAG_NAME}" \
--release-url "${release_url}" \
--image-repo "${IMAGE_REPO}" \
--package-url "${PACKAGE_URL}" \
--changelog changelog-fragment.md \
--output release-notes.md
- name: Upload recovered source artifacts
uses: actions/upload-artifact@v6
with:
name: beellama-recovered-source-artifacts-${{ inputs.tag_name }}
path: recovery/artifact-provenance.tsv
- name: Upload recovered release bundle
uses: actions/upload-artifact@v6
with:
name: beellama-recovered-release-${{ inputs.tag_name }}
path: |
release/*
release-notes.md
recovery/*.tsv
docker-build:
name: Docker ${{ matrix.config.display }}
if: ${{ inputs.publish_docker }}
needs: recover-assets
runs-on: ${{ matrix.config.runs_on }}
strategy:
fail-fast: false
matrix:
config:
- name: cpu-amd64
display: CPU amd64
group: cpu
arch: amd64
platform: linux/amd64
artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-x64.tar.gz
dockerfile: .devops/runtime-server.Dockerfile
base_image: ubuntu:24.04
runtime_packages: ca-certificates curl libgomp1
runs_on: ubuntu-24.04
- name: cpu-arm64
display: CPU arm64
group: cpu
arch: arm64
platform: linux/arm64
artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-arm64.tar.gz
dockerfile: .devops/runtime-server.Dockerfile
base_image: ubuntu:24.04
runtime_packages: ca-certificates curl libgomp1
runs_on: ubuntu-24.04-arm
- name: cuda12
display: CUDA 12.4
group: cuda12
arch: amd64
platform: linux/amd64
artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-cuda-12.4-x64.tar.gz
dockerfile: .devops/runtime-server.Dockerfile
base_image: nvidia/cuda:12.4.1-runtime-ubuntu22.04
runtime_packages: ca-certificates curl libgomp1
runs_on: ubuntu-24.04
- name: cuda13
display: CUDA 13.1
group: cuda13
arch: amd64
platform: linux/amd64
artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-cuda-13.1-x64.tar.gz
dockerfile: .devops/runtime-server.Dockerfile
base_image: nvidia/cuda:13.1.1-runtime-ubuntu24.04
runtime_packages: ca-certificates curl libgomp1
runs_on: ubuntu-24.04
- name: rocm
display: ROCm
group: rocm
arch: amd64
platform: linux/amd64
artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-rocm-7.2-x64.tar.gz
dockerfile: .devops/runtime-server.Dockerfile
base_image: rocm/dev-ubuntu-24.04:7.2.1-complete
runtime_packages: ca-certificates curl libgomp1
runs_on: ubuntu-24.04
- name: vulkan
display: Vulkan
group: vulkan
arch: amd64
platform: linux/amd64
artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-vulkan-x64.tar.gz
dockerfile: .devops/runtime-server.Dockerfile
base_image: ubuntu:24.04
runtime_packages: ca-certificates curl libegl1 libgl1 libgles2 libglvnd0 libglx0 libgomp1 libvulkan1 mesa-vulkan-drivers
runs_on: ubuntu-24.04
- name: sycl
display: SYCL
group: sycl
arch: amd64
platform: linux/amd64
artifact: beellama-${{ inputs.tag_name }}-bin-ubuntu-sycl-x64.tar.gz
dockerfile: .devops/runtime-intel-server.Dockerfile
base_image: unused
runtime_packages: unused
runs_on: ubuntu-24.04
steps:
- name: Clone source
uses: actions/checkout@v6
with:
ref: ${{ needs.recover-assets.outputs.source_sha }}
fetch-depth: 0
- name: Download recovered release bundle
uses: actions/download-artifact@v7
with:
name: beellama-recovered-release-${{ inputs.tag_name }}
path: ./bundle
- name: Prepare Docker context
shell: bash
run: |
set -euo pipefail
mkdir -p docker-context/app
tar_file="$(find bundle -type f -name '${{ matrix.config.artifact }}' | head -n 1)"
if [[ -z "${tar_file}" ]]; then
echo "No package found for ${{ matrix.config.artifact }}" >&2
find bundle -type f -maxdepth 3 | sort >&2
exit 1
fi
tar -xzf "${tar_file}" -C docker-context/app --strip-components=1
test -x docker-context/app/llama-server
cp "${{ matrix.config.dockerfile }}" docker-context/Dockerfile
ls -lh docker-context/app/llama-server
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Log in to GHCR
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get build date
id: build_date
run: echo "date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "${GITHUB_OUTPUT}"
- name: Build and push image digest
id: build
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
context: ./docker-context
file: ./docker-context/Dockerfile
platforms: ${{ matrix.config.platform }}
outputs: type=image,name=${{ needs.recover-assets.outputs.image_repo }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
provenance: false
build-args: |
BASE_IMAGE=${{ matrix.config.base_image }}
RUNTIME_PACKAGES=${{ matrix.config.runtime_packages }}
BUILD_DATE=${{ steps.build_date.outputs.date }}
APP_VERSION=${{ inputs.tag_name }}
APP_REVISION=${{ needs.recover-assets.outputs.source_sha }}
IMAGE_URL=${{ github.server_url }}/${{ github.repository }}
IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }}
annotations: |
manifest:org.opencontainers.image.created=${{ steps.build_date.outputs.date }}
manifest:org.opencontainers.image.version=${{ inputs.tag_name }}
manifest:org.opencontainers.image.revision=${{ needs.recover-assets.outputs.source_sha }}
manifest:org.opencontainers.image.title=BeeLlama.cpp
manifest:org.opencontainers.image.description=BeeLlama.cpp GGUF inference with DFlash, TurboQuant, and TCQ cache types
manifest:org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}
manifest:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
cache-from: type=registry,ref=${{ needs.recover-assets.outputs.image_repo }}:buildcache-runtime-${{ matrix.config.name }}
cache-to: type=registry,ref=${{ needs.recover-assets.outputs.image_repo }}:buildcache-runtime-${{ matrix.config.name }},mode=max
- name: Record digest
shell: bash
run: |
set -euo pipefail
digest="${{ steps.build.outputs.digest }}"
if [[ -z "${digest}" ]]; then
echo "Docker build did not return a digest." >&2
exit 1
fi
mkdir -p /tmp/docker-digests
printf '%s\t%s\t%s\n' "${{ matrix.config.group }}" "${{ matrix.config.arch }}" "${digest}" > "/tmp/docker-digests/${{ matrix.config.name }}.tsv"
- name: Upload digest metadata
uses: actions/upload-artifact@v6
with:
name: docker-digest-${{ matrix.config.name }}
path: /tmp/docker-digests/${{ matrix.config.name }}.tsv
if-no-files-found: error
docker-merge:
name: Docker tags
if: ${{ inputs.publish_docker }}
needs:
- recover-assets
- docker-build
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
config:
- group: cpu
display: CPU
platforms: linux/amd64 linux/arm64
tag_suffixes: none -cpu
- group: cuda12
display: CUDA 12.4
platforms: linux/amd64
tag_suffixes: -cuda -cuda12
- group: cuda13
display: CUDA 13.1
platforms: linux/amd64
tag_suffixes: -cuda13
- group: rocm
display: ROCm
platforms: linux/amd64
tag_suffixes: -rocm
- group: vulkan
display: Vulkan
platforms: linux/amd64
tag_suffixes: -vulkan
- group: sycl
display: SYCL
platforms: linux/amd64
tag_suffixes: -sycl
steps:
- name: Download digest metadata
uses: actions/download-artifact@v7
with:
pattern: docker-digest-*
path: /tmp/docker-digests
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Log in to GHCR
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create Docker tags
shell: bash
env:
IMAGE_REPO: ${{ needs.recover-assets.outputs.image_repo }}
TAG_NAME: ${{ inputs.tag_name }}
GROUP: ${{ matrix.config.group }}
PLATFORMS: ${{ matrix.config.platforms }}
TAG_SUFFIXES: ${{ matrix.config.tag_suffixes }}
run: |
set -euo pipefail
digest_glob="/tmp/docker-digests/*.tsv"
if ! ls ${digest_glob} >/dev/null 2>&1; then
echo "No Docker digest metadata found." >&2
exit 1
fi
sources=()
for platform in ${PLATFORMS}; do
arch="${platform#linux/}"
digest="$(awk -F '\t' -v g="${GROUP}" -v a="${arch}" '$1 == g && $2 == a { print $3; exit }' ${digest_glob})"
if [[ -z "${digest}" ]]; then
echo "Missing digest for group=${GROUP} arch=${arch}" >&2
exit 1
fi
sources+=("${IMAGE_REPO}@${digest}")
done
tag_args=()
for suffix in ${TAG_SUFFIXES}; do
if [[ "${suffix}" == "none" ]]; then
suffix=""
fi
tag_args+=("-t" "${IMAGE_REPO}:server${suffix}")
tag_args+=("-t" "${IMAGE_REPO}:server${suffix}-${TAG_NAME}")
done
printf 'Creating Docker tags:\n'
printf ' %s\n' "${tag_args[@]}"
docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"
publish-release:
name: Publish GitHub release
if: ${{ always() && inputs.publish_release && needs.recover-assets.result == 'success' && (!inputs.publish_docker || needs.docker-merge.result == 'success') }}
needs:
- recover-assets
- docker-merge
runs-on: ubuntu-24.04
steps:
- name: Clone source
uses: actions/checkout@v6
with:
ref: ${{ needs.recover-assets.outputs.source_sha }}
fetch-depth: 0
- name: Prepare release tag
env:
TAG_NAME: ${{ inputs.tag_name }}
SOURCE_SHA: ${{ needs.recover-assets.outputs.source_sha }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -f "${TAG_NAME}" "${SOURCE_SHA}"
git push origin "refs/tags/${TAG_NAME}" --force
- name: Download release assets
uses: actions/download-artifact@v7
with:
name: beellama-recovered-release-${{ inputs.tag_name }}
path: ./release-bundle
- name: Create or update release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ inputs.tag_name }}
SOURCE_SHA: ${{ needs.recover-assets.outputs.source_sha }}
run: |
set -euo pipefail
notes_file="$(find release-bundle -type f -name release-notes.md | head -n 1)"
if [[ -z "${notes_file}" ]]; then
echo "release-notes.md not found in release asset bundle" >&2
exit 1
fi
mapfile -t release_files < <(find release-bundle -type f ! -name release-notes.md ! -name '*provenance.tsv' | sort)
if [[ "${#release_files[@]}" -eq 0 ]]; then
echo "No release files found in release asset bundle" >&2
exit 1
fi
if gh release view "${TAG_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
gh release upload "${TAG_NAME}" "${release_files[@]}" --clobber --repo "${GITHUB_REPOSITORY}"
mapfile -t existing_assets < <(gh release view "${TAG_NAME}" --repo "${GITHUB_REPOSITORY}" --json assets --jq '.assets[].name')
for existing_asset in "${existing_assets[@]}"; do
keep_asset="false"
for release_file in "${release_files[@]}"; do
if [[ "$(basename "${release_file}")" == "${existing_asset}" ]]; then
keep_asset="true"
break
fi
done
if [[ "${keep_asset}" != "true" ]]; then
gh release delete-asset "${TAG_NAME}" "${existing_asset}" --yes --repo "${GITHUB_REPOSITORY}"
fi
done
gh release edit "${TAG_NAME}" --repo "${GITHUB_REPOSITORY}" --title "${TAG_NAME}" --notes-file "${notes_file}" --target "${SOURCE_SHA}" --prerelease=false --latest=true --verify-tag
else
gh release create "${TAG_NAME}" "${release_files[@]}" --repo "${GITHUB_REPOSITORY}" --title "${TAG_NAME}" --notes-file "${notes_file}" --target "${SOURCE_SHA}" --latest=true --verify-tag
fi