Skip to content

fix(ci): exclude SHA256SUMS from checksum input to prevent file lock #10

fix(ci): exclude SHA256SUMS from checksum input to prevent file lock

fix(ci): exclude SHA256SUMS from checksum input to prevent file lock #10

Workflow file for this run

name: Build & Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: '要构建的 Tag(如 v0.2.0)'
required: true
type: string
permissions: {}
# 防止同一 tag 重复构建
concurrency:
group: release-${{ github.event.inputs.tag || github.ref_name }}
cancel-in-progress: false
jobs:
# ===== Stage 1: 三平台并行构建 =====
build:
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
env:
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.RELEASE_TAG }}
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: npm install
- name: Build Vite
run: npm run build
- name: Package Electron
env:
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
run: npx electron-builder --config electron-builder.yml --publish never
- name: Prepare artifacts (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
New-Item -ItemType Directory -Force artifacts-upload | Out-Null
Copy-Item release\*.exe artifacts-upload\ -ErrorAction SilentlyContinue
Set-Location artifacts-upload
$files = Get-ChildItem -File -Exclude SHA256SUMS | Select-Object -ExpandProperty Name
foreach ($f in $files) {
$hash = (Get-FileHash -Algorithm SHA256 $f).Hash.ToLower()
"$hash $f" | Out-File -Encoding ASCII -Append SHA256SUMS
}
- name: Prepare artifacts (Unix)
if: matrix.os != 'windows-latest'
shell: bash
run: |
mkdir -p artifacts-upload
cp release/*.exe release/*.dmg release/*.AppImage release/*.deb artifacts-upload/ 2>/dev/null || true
cd artifacts-upload
shopt -s nullglob
for f in *; do
[ "$f" = "SHA256SUMS" ] && continue
sha256sum "$f"
done > SHA256SUMS
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.os }}
path: artifacts-upload/*
if-no-files-found: error
retention-days: 14
# ===== Stage 2: 验证并创建 Draft Release =====
stage-release:
runs-on: ubuntu-latest
needs: build
permissions:
actions: read
contents: write
env:
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: release-assets
merge-multiple: true
- name: Validate artifacts
run: |
set -euo pipefail
echo "=== Artifacts ==="
ls -la release-assets/
# At least one binary per platform must exist
found_win=$(find release-assets/ -maxdepth 1 -name "*.exe" | wc -l)
found_mac=$(find release-assets/ -maxdepth 1 -name "*.dmg" | wc -l)
found_linux=$(find release-assets/ -maxdepth 1 \( -name "*.AppImage" -o -name "*.deb" \) | wc -l)
echo "Windows: ${found_win} files"
echo "macOS: ${found_mac} files"
echo "Linux: ${found_linux} files"
if [ "${found_win}" -eq 0 ]; then echo "::error::Missing Windows binaries"; exit 1; fi
if [ "${found_mac}" -eq 0 ]; then echo "::error::Missing macOS binaries"; exit 1; fi
if [ "${found_linux}" -eq 0 ]; then echo "::error::Missing Linux binaries"; exit 1; fi
- name: Generate release notes
run: |
VERSION="${RELEASE_TAG#v}"
{
echo "## SunCode ${RELEASE_TAG}"
echo
echo "### 变更"
echo
# Extract changes since previous tag
PREV_TAG=$(git tag --sort=-v:refname | grep -A1 "${RELEASE_TAG}" | tail -1)
if [ -n "${PREV_TAG}" ]; then
git log "${PREV_TAG}..${RELEASE_TAG}" --pretty=format:"- %s" --no-merges
else
git log --pretty=format:"- %s" --no-merges
fi
echo
echo
echo "### 下载"
echo
echo "| 平台 | 文件 |"
echo "|------|------|"
for f in release-assets/*.exe release-assets/*.dmg release-assets/*.AppImage release-assets/*.deb; do
[ -f "$f" ] || continue
name=$(basename "$f")
echo "| $(echo "$name" | sed 's/\..*//') | $name |"
done
} > /tmp/release-notes.md
cat /tmp/release-notes.md
- name: Create draft GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.RELEASE_TAG }}
name: SunCode ${{ env.RELEASE_TAG }}
draft: true
body_path: /tmp/release-notes.md
files: |
release-assets/*
# ===== Stage 3: 发布 Draft Release =====
publish-release:
runs-on: ubuntu-latest
needs: stage-release
permissions:
contents: write
env:
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
steps:
- name: Publish GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# 检查 draft 是否存在
IS_DRAFT=$(gh release view "${RELEASE_TAG}" --json isDraft --jq .isDraft 2>/dev/null || echo "")
if [ "${IS_DRAFT}" == "" ]; then
echo "::error::Draft Release ${RELEASE_TAG} 不存在"
exit 1
fi
if [ "${IS_DRAFT}" == "false" ]; then
echo "::warning::Release ${RELEASE_TAG} 已经是公开状态"
exit 0
fi
gh release edit "${RELEASE_TAG}" --draft=false
echo "✅ 发布成功: ${RELEASE_TAG}"
# ===== 失败清理:删除 Draft Release =====
cleanup-draft:
runs-on: ubuntu-latest
needs: [build, stage-release, publish-release]
if: ${{ always() && needs.stage-release.result != 'skipped' && needs.stage-release.result == 'success' && needs.publish-release.result != 'success' }}
permissions:
contents: write
env:
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
steps:
- name: Delete draft after failure
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
IS_DRAFT=$(gh release view "${RELEASE_TAG}" --json isDraft --jq .isDraft 2>/dev/null || echo "")
if [ "${IS_DRAFT}" == "true" ]; then
gh release delete "${RELEASE_TAG}" --yes
echo "🧹 已清理失败的 Draft Release: ${RELEASE_TAG}"
fi