Skip to content

Build Desktop Tauri #486

Build Desktop Tauri

Build Desktop Tauri #486

name: Build Desktop Tauri
on:
workflow_dispatch:
inputs:
source_git_url:
description: AstrBot source git URL
required: false
default: https://github.com/AstrBotDevs/AstrBot.git
source_git_ref:
description: Optional source ref override for `tag-poll` (branch/tag/commit SHA). Ignored in `nightly`.
required: false
default: ""
publish_release:
description: Publish GitHub Release after successful builds
required: false
type: boolean
default: true
build_mode:
description: >-
Build mode (`tag-poll` | `nightly`): `nightly` (default) always builds latest upstream commit,
`tag-poll` builds latest upstream tag (or `source_git_ref` override)
required: false
type: choice
default: nightly
options:
- tag-poll
- nightly
schedule:
# Hourly tag-poll (exclude the dedicated nightly window at 03:xx UTC).
- cron: '0 0-2,4-23 * * *'
# Dedicated daily nightly build.
- cron: '7 3 * * *'
permissions:
contents: read
env:
ASTRBOT_SOURCE_GIT_URL: ${{ vars.ASTRBOT_SOURCE_GIT_URL || 'https://github.com/AstrBotDevs/AstrBot.git' }}
ASTRBOT_SOURCE_GIT_REF: ${{ vars.ASTRBOT_SOURCE_GIT_REF || 'master' }}
ASTRBOT_NIGHTLY_SOURCE_GIT_REF: ${{ vars.ASTRBOT_NIGHTLY_SOURCE_GIT_REF || 'master' }}
ASTRBOT_NIGHTLY_SCHEDULE_CRON: ${{ vars.ASTRBOT_NIGHTLY_SCHEDULE_CRON || '7 3 * * *' }}
ASTRBOT_NIGHTLY_UTC_HOUR: ${{ vars.ASTRBOT_NIGHTLY_UTC_HOUR || '3' }}
ASTRBOT_DESKTOP_UPDATER_PUBLIC_KEY: ${{ vars.ASTRBOT_DESKTOP_UPDATER_PUBLIC_KEY || '' }}
jobs:
resolve_build_context:
name: Resolve Build Context
runs-on: ubuntu-latest
outputs:
source_git_url: ${{ steps.resolve.outputs.source_git_url }}
source_git_ref: ${{ steps.resolve.outputs.source_git_ref }}
astrbot_version: ${{ steps.resolve.outputs.astrbot_version }}
should_build: ${{ steps.resolve.outputs.should_build }}
build_mode: ${{ steps.resolve.outputs.build_mode }}
publish_release: ${{ steps.resolve.outputs.publish_release }}
release_tag: ${{ steps.resolve.outputs.release_tag }}
release_name: ${{ steps.resolve.outputs.release_name }}
release_prerelease: ${{ steps.resolve.outputs.release_prerelease }}
release_make_latest: ${{ steps.resolve.outputs.release_make_latest }}
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup Toolchains
uses: ./.github/actions/setup-toolchains
with:
setup-node: 'false'
python-version: '3.12'
- name: Resolve source, version and trigger guard
id: resolve
env:
ASTRBOT_SOURCE_GIT_URL: ${{ env.ASTRBOT_SOURCE_GIT_URL }}
ASTRBOT_SOURCE_GIT_REF: ${{ env.ASTRBOT_SOURCE_GIT_REF }}
WORKFLOW_SOURCE_GIT_URL: ${{ github.event.inputs.source_git_url }}
WORKFLOW_SOURCE_GIT_REF: ${{ github.event.inputs.source_git_ref }}
WORKFLOW_PUBLISH_RELEASE: ${{ github.event.inputs.publish_release }}
WORKFLOW_BUILD_MODE: ${{ github.event.inputs.build_mode }}
GITHUB_TOKEN: ${{ github.token }}
GH_REPOSITORY: ${{ github.repository }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_EVENT_SCHEDULE: ${{ github.event.schedule || '' }}
ASTRBOT_NIGHTLY_SOURCE_GIT_REF: ${{ env.ASTRBOT_NIGHTLY_SOURCE_GIT_REF }}
ASTRBOT_NIGHTLY_SCHEDULE_CRON: ${{ env.ASTRBOT_NIGHTLY_SCHEDULE_CRON }}
ASTRBOT_NIGHTLY_UTC_HOUR: ${{ env.ASTRBOT_NIGHTLY_UTC_HOUR }}
run: bash scripts/ci/resolve-build-context.sh
sync_repo_version:
name: Sync Repository Version
needs: resolve_build_context
if: ${{ github.event_name == 'schedule' && needs.resolve_build_context.outputs.should_build == 'true' && needs.resolve_build_context.outputs.build_mode == 'tag-poll' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout branch
uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Setup Toolchains
uses: ./.github/actions/setup-toolchains
with:
setup-python: 'false'
- name: Setup pnpm
uses: pnpm/action-setup@v4.2.0
with:
version: 10.28.2
- name: Resolve desktop version env
id: desktop_version
shell: bash
run: bash scripts/ci/resolve-desktop-version.sh "${{ needs.resolve_build_context.outputs.astrbot_version }}" "${GITHUB_OUTPUT}"
- name: Sync desktop version to upstream tag
env:
ASTRBOT_SOURCE_GIT_URL: ${{ needs.resolve_build_context.outputs.source_git_url }}
ASTRBOT_SOURCE_GIT_REF: ${{ needs.resolve_build_context.outputs.source_git_ref }}
ASTRBOT_DESKTOP_VERSION: ${{ steps.desktop_version.outputs.prefixed }}
run: make update
- name: Commit and push version files
env:
ASTRBOT_VERSION: ${{ steps.desktop_version.outputs.normalized }}
TARGET_REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
changed_files="$(git status --porcelain -- package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json)"
if [ -z "${changed_files}" ]; then
echo "Version files are already up to date. Nothing to commit."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json
git commit -m "chore(version): sync desktop version to v${ASTRBOT_VERSION}"
git fetch origin "${TARGET_REF_NAME}"
if ! git pull --rebase origin "${TARGET_REF_NAME}"; then
echo "::warning::Failed to rebase onto origin/${TARGET_REF_NAME}. Skipping push to avoid noisy failures."
git rebase --abort || true
exit 0
fi
if ! git push origin "HEAD:${TARGET_REF_NAME}"; then
echo "::warning::Push to ${TARGET_REF_NAME} was rejected (likely branch protection or race). Skipping."
exit 0
fi
build-linux:
needs:
- resolve_build_context
- sync_repo_version
if: ${{ always() && needs.resolve_build_context.outputs.should_build == 'true' && (needs.sync_repo_version.result == 'success' || needs.sync_repo_version.result == 'skipped') }}
name: linux-${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf
- name: Setup desktop build environment
uses: ./.github/actions/setup-desktop-build
- name: Resolve desktop version env
id: desktop_version
shell: bash
run: bash scripts/ci/resolve-desktop-version.sh "${{ needs.resolve_build_context.outputs.astrbot_version }}" "${GITHUB_OUTPUT}"
- name: Sync and verify desktop version for this build
uses: ./.github/actions/sync-desktop-version
with:
astrbot_version: ${{ steps.desktop_version.outputs.normalized }}
- name: Build desktop installers (Linux)
env:
ASTRBOT_SOURCE_GIT_URL: ${{ needs.resolve_build_context.outputs.source_git_url }}
ASTRBOT_SOURCE_GIT_REF: ${{ needs.resolve_build_context.outputs.source_git_ref }}
ASTRBOT_DESKTOP_VERSION: ${{ steps.desktop_version.outputs.prefixed }}
ASTRBOT_DESKTOP_UPDATER_PUBLIC_KEY: ${{ env.ASTRBOT_DESKTOP_UPDATER_PUBLIC_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPIMAGE_EXTRACT_AND_RUN: '1'
NO_STRIP: '1'
GITHUB_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
echo "Building Linux release bundles (deb and rpm only)."
cargo tauri build --bundles deb,rpm
- name: Smoke test backend startup (Linux)
shell: bash
run: |
set -euo pipefail
node scripts/ci/backend-smoke-test.mjs --label "linux-${{ matrix.arch }}"
- name: Upload artifacts
uses: actions/upload-artifact@v6.0.0
with:
name: astrbot-desktop-tauri-${{ needs.resolve_build_context.outputs.astrbot_version }}-linux-${{ matrix.arch }}
if-no-files-found: error
path: |
src-tauri/target/release/bundle/**/*.deb
src-tauri/target/release/bundle/**/*.rpm
build-macos:
needs:
- resolve_build_context
- sync_repo_version
if: ${{ always() && needs.resolve_build_context.outputs.should_build == 'true' && (needs.sync_repo_version.result == 'success' || needs.sync_repo_version.result == 'skipped') }}
name: macos-${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: macos-15-intel
target: x86_64-apple-darwin
- arch: arm64
runner: macos-latest
target: aarch64-apple-darwin
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup desktop build environment
uses: ./.github/actions/setup-desktop-build
- name: Resolve desktop version env
id: desktop_version
shell: bash
run: bash scripts/ci/resolve-desktop-version.sh "${{ needs.resolve_build_context.outputs.astrbot_version }}" "${GITHUB_OUTPUT}"
- name: Sync and verify desktop version for this build
uses: ./.github/actions/sync-desktop-version
with:
astrbot_version: ${{ steps.desktop_version.outputs.normalized }}
- name: Resolve macOS app bundle name
id: resolve_macos_app_bundle
env:
ASTRBOT_MACOS_APP_BUNDLE_NAME: ${{ vars.ASTRBOT_MACOS_APP_BUNDLE_NAME || '' }}
shell: bash
run: |
set -euo pipefail
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 is required to resolve macOS app bundle name." >&2
exit 1
fi
python3 --version
python3 scripts/ci/resolve-macos-app-name.py \
--config src-tauri/tauri.conf.json \
--override-name "${ASTRBOT_MACOS_APP_BUNDLE_NAME}" \
--override-source "env:ASTRBOT_MACOS_APP_BUNDLE_NAME" \
--github-output "${GITHUB_OUTPUT}"
- name: Build desktop app bundle (macOS)
env:
ASTRBOT_SOURCE_GIT_URL: ${{ needs.resolve_build_context.outputs.source_git_url }}
ASTRBOT_SOURCE_GIT_REF: ${{ needs.resolve_build_context.outputs.source_git_ref }}
ASTRBOT_DESKTOP_VERSION: ${{ steps.desktop_version.outputs.prefixed }}
ASTRBOT_DESKTOP_UPDATER_PUBLIC_KEY: ${{ env.ASTRBOT_DESKTOP_UPDATER_PUBLIC_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
ASTRBOT_DESKTOP_CRYPTOGRAPHY_FALLBACK_VERSIONS: ${{ vars.ASTRBOT_DESKTOP_CRYPTOGRAPHY_FALLBACK_VERSIONS || '' }}
ASTRBOT_MACOS_BUILD_MAX_ATTEMPTS: ${{ vars.ASTRBOT_MACOS_BUILD_MAX_ATTEMPTS || '3' }}
ASTRBOT_MACOS_BUILD_RETRY_SLEEP_SECONDS: ${{ vars.ASTRBOT_MACOS_BUILD_RETRY_SLEEP_SECONDS || '8' }}
GITHUB_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
max_attempts_default=3
max_attempts_upper_bound=6
retry_sleep_seconds_default=8
max_attempts="${ASTRBOT_MACOS_BUILD_MAX_ATTEMPTS}"
retry_sleep_seconds="${ASTRBOT_MACOS_BUILD_RETRY_SLEEP_SECONDS}"
# Retry only for known transient cargo/crates network failures.
# Spurious retry hints emitted by cargo.
transient_retry_spurious='spurious network error|network failure seems to have happened'
# HTTP-layer transient fetch failures (rate limits and 5xx responses).
transient_retry_http='failed to download from|failed to get successful HTTP response|received HTTP code (429|5[0-9][0-9])'
# Transport-layer transient failures.
transient_retry_transport='Operation timed out|Connection reset by peer|Connection refused|Temporary failure in name resolution'
retry_pattern="${transient_retry_spurious}|${transient_retry_http}|${transient_retry_transport}"
case "${max_attempts}" in
''|*[!0-9]*|0) max_attempts="${max_attempts_default}" ;;
esac
if [ "${max_attempts}" -gt "${max_attempts_upper_bound}" ]; then
echo "::warning::ASTRBOT_MACOS_BUILD_MAX_ATTEMPTS=${max_attempts} exceeds upper bound ${max_attempts_upper_bound}; clamping."
max_attempts="${max_attempts_upper_bound}"
fi
case "${retry_sleep_seconds}" in
''|*[!0-9]*|0) retry_sleep_seconds="${retry_sleep_seconds_default}" ;;
esac
echo "macOS build retry config: max_attempts=${max_attempts}, retry_sleep_seconds=${retry_sleep_seconds}, max_attempts_upper_bound=${max_attempts_upper_bound}"
for attempt in $(seq 1 "${max_attempts}"); do
build_log="$(mktemp -t tauri-macos-build.XXXXXX.log)"
if cargo tauri build --verbose --target ${{ matrix.target }} --bundles app 2>&1 | tee "${build_log}"; then
rm -f "${build_log}" || true
break
fi
if [ "${attempt}" -ge "${max_attempts}" ]; then
echo "macOS build failed after ${max_attempts} attempts." >&2
rm -f "${build_log}" || true
exit 1
fi
if ! grep -Eiq "${retry_pattern}" "${build_log}"; then
echo "macOS build failed with non-transient error on attempt ${attempt}/${max_attempts}; skip retries." >&2
rm -f "${build_log}" || true
exit 1
fi
rm -f "${build_log}" || true
echo "macOS build hit transient failure on attempt ${attempt}/${max_attempts}; retrying in ${retry_sleep_seconds}s..."
sleep "${retry_sleep_seconds}"
done
- name: Smoke test backend startup (macOS)
shell: bash
run: |
set -euo pipefail
node scripts/ci/backend-smoke-test.mjs --label "macos-${{ matrix.arch }}"
- name: Collect macOS updater artifacts
env:
ASTRBOT_VERSION: ${{ needs.resolve_build_context.outputs.astrbot_version }}
RESOLVED_APP_BUNDLE_NAME: ${{ steps.resolve_macos_app_bundle.outputs.app_bundle_name }}
RESOLVED_APP_BUNDLE_NAME_SOURCE: ${{ steps.resolve_macos_app_bundle.outputs.app_bundle_name_source }}
shell: bash
run: |
set -euo pipefail
bundle_root="src-tauri/target/${{ matrix.target }}/release/bundle"
bundle_dir="${bundle_root}/macos"
release_dir="${bundle_root}/release-artifacts"
app_bundle_name="${RESOLVED_APP_BUNDLE_NAME}"
app_bundle_name_source="${RESOLVED_APP_BUNDLE_NAME_SOURCE}"
if [ -z "${app_bundle_name}" ]; then
echo "Resolved app bundle name is empty (source=${app_bundle_name_source})." >&2
exit 1
fi
# Tauri currently emits macOS updater archives under bundle/macos and this step stages
# renamed copies under bundle/release-artifacts. Fail loudly if that layout changes.
if [ ! -d "${bundle_root}" ]; then
echo "Expected Tauri bundle root not found: ${bundle_root}" >&2
ls -la "src-tauri/target/${{ matrix.target }}/release" || true
exit 1
fi
if [ ! -d "${bundle_dir}" ]; then
echo "Expected Tauri macOS bundle directory not found: ${bundle_dir}" >&2
ls -la "${bundle_root}" || true
exit 1
fi
mkdir -p "${release_dir}"
expected_updater_archive="${bundle_dir}/${app_bundle_name}.app.tar.gz"
if [ -f "${expected_updater_archive}" ]; then
updater_archive="${expected_updater_archive}"
else
shopt -s nullglob
updater_archives=("${bundle_dir}/${app_bundle_name}"*.app.tar.gz)
if [ "${#updater_archives[@]}" -ne 1 ]; then
echo "Expected a macOS updater archive matching ${app_bundle_name}*.app.tar.gz in ${bundle_dir}, found ${#updater_archives[@]}." >&2
ls -la "${bundle_dir}" || true
exit 1
fi
updater_archive="${updater_archives[0]}"
fi
updater_signature="${updater_archive}.sig"
if [ ! -f "${updater_signature}" ]; then
echo "Expected macOS updater signature not found: ${updater_signature}" >&2
ls -la "${bundle_dir}" || true
exit 1
fi
release_base="AstrBot_${ASTRBOT_VERSION}_macos_${{ matrix.arch }}.app.tar.gz"
cp "${updater_archive}" "${release_dir}/${release_base}"
cp "${updater_signature}" "${release_dir}/${release_base}.sig"
echo "Collected ${release_dir}/${release_base}"
- name: Upload artifacts
uses: actions/upload-artifact@v6.0.0
with:
name: astrbot-desktop-tauri-${{ needs.resolve_build_context.outputs.astrbot_version }}-macos-${{ matrix.arch }}
if-no-files-found: error
path: |
src-tauri/target/${{ matrix.target }}/release/bundle/release-artifacts/*.app.tar.gz
src-tauri/target/${{ matrix.target }}/release/bundle/release-artifacts/*.app.tar.gz.sig
build-windows:
needs:
- resolve_build_context
- sync_repo_version
if: ${{ always() && needs.resolve_build_context.outputs.should_build == 'true' && (needs.sync_repo_version.result == 'success' || needs.sync_repo_version.result == 'skipped') }}
name: windows-${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
env:
ASTRBOT_WINDOWS_BUNDLES: nsis
WINDOWS_INSTALLER_EXE_GLOBS: |
src-tauri/target/release/bundle/nsis/*.exe
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: windows-2022
- arch: arm64
runner: windows-11-arm
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Setup desktop build environment
uses: ./.github/actions/setup-desktop-build
- name: Resolve desktop version env
id: desktop_version
shell: bash
run: bash scripts/ci/resolve-desktop-version.sh "${{ needs.resolve_build_context.outputs.astrbot_version }}" "${GITHUB_OUTPUT}"
- name: Sync and verify desktop version for this build
uses: ./.github/actions/sync-desktop-version
with:
astrbot_version: ${{ steps.desktop_version.outputs.normalized }}
- name: Build desktop installers (Windows)
env:
ASTRBOT_SOURCE_GIT_URL: ${{ needs.resolve_build_context.outputs.source_git_url }}
ASTRBOT_SOURCE_GIT_REF: ${{ needs.resolve_build_context.outputs.source_git_ref }}
ASTRBOT_DESKTOP_VERSION: ${{ steps.desktop_version.outputs.prefixed }}
ASTRBOT_DESKTOP_UPDATER_PUBLIC_KEY: ${{ env.ASTRBOT_DESKTOP_UPDATER_PUBLIC_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
ASTRBOT_DESKTOP_CRYPTOGRAPHY_FALLBACK_VERSIONS: ${{ vars.ASTRBOT_DESKTOP_CRYPTOGRAPHY_FALLBACK_VERSIONS || '' }}
GITHUB_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ github.token }}
shell: bash
run: bash scripts/ci/build-windows-installers.sh
- name: Smoke test backend startup (Windows)
shell: bash
run: |
set -euo pipefail
node scripts/ci/backend-smoke-test.mjs --label "windows-${{ matrix.arch }}"
- name: Verify Windows installer outputs
shell: bash
run: bash scripts/ci/verify-windows-installer-outputs.sh
- name: Upload artifacts
uses: actions/upload-artifact@v6.0.0
with:
name: astrbot-desktop-tauri-${{ needs.resolve_build_context.outputs.astrbot_version }}-windows-${{ matrix.arch }}
if-no-files-found: error
# Only upload Windows installer executables.
# A broad **/*.exe pattern also matches bundled Python runtime helpers.
path: |
${{ env.WINDOWS_INSTALLER_EXE_GLOBS }}
src-tauri/target/release/bundle/nsis/*.exe.sig
release:
name: Publish GitHub Release
# Use always() here to avoid skipped upstream guard jobs (for example
# sync_repo_version on workflow_dispatch) from implicitly short-circuiting
# manual nightly release publishing.
if: ${{ always() && needs.resolve_build_context.outputs.should_build == 'true' && needs.resolve_build_context.outputs.release_tag != '' && needs.build-linux.result == 'success' && needs.build-macos.result == 'success' && needs.build-windows.result == 'success' }}
needs:
- resolve_build_context
- build-linux
- build-macos
- build-windows
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Download build artifacts
uses: actions/download-artifact@v7.0.0
with:
pattern: astrbot-desktop-tauri-${{ needs.resolve_build_context.outputs.astrbot_version }}-*
path: release-artifacts
merge-multiple: true
- name: Show artifacts
run: find release-artifacts -type f | sort
- name: Normalize release artifact filenames
env:
BUILD_MODE: ${{ needs.resolve_build_context.outputs.build_mode }}
SOURCE_GIT_REF: ${{ needs.resolve_build_context.outputs.source_git_ref }}
ASTRBOT_STRICT_ARTIFACT_FILENAME_NORMALIZATION: ${{ vars.ASTRBOT_STRICT_ARTIFACT_FILENAME_NORMALIZATION || '0' }}
shell: bash
run: |
set -euo pipefail
normalize_args=(
--root release-artifacts
--build-mode "${BUILD_MODE}"
--source-git-ref "${SOURCE_GIT_REF}"
)
strict_flag="$(printf '%s' "${ASTRBOT_STRICT_ARTIFACT_FILENAME_NORMALIZATION}" | tr '[:upper:]' '[:lower:]')"
case "${strict_flag}" in
1|true|yes|on)
normalize_args+=(--strict-unmatched)
;;
esac
python3 -m scripts.ci.normalize_release_artifact_filenames "${normalize_args[@]}"
- name: Validate artifact filename uniqueness
shell: bash
run: |
set -euo pipefail
python3 scripts/ci/validate-release-artifacts.py release-artifacts
- name: Generate Tauri updater manifest
env:
RELEASE_TAG: ${{ needs.resolve_build_context.outputs.release_tag }}
RELEASE_VERSION: ${{ needs.resolve_build_context.outputs.astrbot_version }}
BUILD_MODE: ${{ needs.resolve_build_context.outputs.build_mode }}
shell: bash
run: |
set -euo pipefail
manifest_channel="stable"
manifest_output="release-artifacts/latest-stable.json"
if [ "${BUILD_MODE}" = "nightly" ]; then
manifest_channel="nightly"
manifest_output="release-artifacts/latest-nightly.json"
fi
python3 -m scripts.ci.generate_tauri_latest_json \
--artifacts-root release-artifacts \
--repo "${GITHUB_REPOSITORY}" \
--tag "${RELEASE_TAG}" \
--version "${RELEASE_VERSION}" \
--channel "${manifest_channel}" \
--output "${manifest_output}"
- name: Remove existing assets from target release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.resolve_build_context.outputs.release_tag }}
shell: bash
run: bash scripts/ci/cleanup-release-assets.sh
- name: Create or update release
uses: softprops/action-gh-release@v2.5.0
with:
tag_name: ${{ needs.resolve_build_context.outputs.release_tag }}
name: ${{ needs.resolve_build_context.outputs.release_name }}
body: |
Automated desktop package release.
- Source: `${{ needs.resolve_build_context.outputs.source_git_url }}`
- Ref: `${{ needs.resolve_build_context.outputs.source_git_ref }}`
- Mode: `${{ needs.resolve_build_context.outputs.build_mode }}`
- Windows installer format: `nsis`.
generate_release_notes: true
prerelease: ${{ needs.resolve_build_context.outputs.release_prerelease == 'true' }}
make_latest: ${{ needs.resolve_build_context.outputs.release_make_latest == 'true' }}
overwrite_files: true
files: release-artifacts/**/*
fail_on_unmatched_files: true
- name: Demote previous prerelease marker
if: ${{ needs.resolve_build_context.outputs.release_prerelease == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
CURRENT_RELEASE_TAG: ${{ needs.resolve_build_context.outputs.release_tag }}
shell: bash
run: |
set -euo pipefail
previous_prerelease_id="$(
gh api --paginate "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \
| jq -s -r --arg current_tag "${CURRENT_RELEASE_TAG}" '
# gh --paginate emits one JSON value per page; keep array pages and flatten.
map(select(type == "array"))
| flatten
| map(
(.tag_name // "") as $tag
| select(
.prerelease == true
and .draft == false
and .tag_name != $current_tag
# Match nightly-like tags in a case-insensitive way:
# nightly, *-nightly*, *.nightly*, etc.
and ($tag | test("(^|[-.])nightly([-.]|$)"; "i"))
)
)
| sort_by(.published_at // .created_at)
| reverse
| .[0].id // empty
'
)"
if [ -z "${previous_prerelease_id}" ]; then
echo "No previous prerelease found to demote."
exit 0
fi
gh api -X PATCH "repos/${GITHUB_REPOSITORY}/releases/${previous_prerelease_id}" \
-f prerelease=false \
-f make_latest=false >/dev/null
echo "Demoted previous prerelease release_id=${previous_prerelease_id}"