feat: 实时用量统计与输入框自动聚焦优化 #29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Release | ||
| on: | ||
| push: | ||
| tags: | ||
| - 'v*' | ||
| workflow_dispatch: | ||
| inputs: | ||
| tag: | ||
| description: '要构建的 Tag(如 v0.2.0)' | ||
| required: true | ||
| type: string | ||
| permissions: {} | ||
| 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 | ||
| } | ||
| Write-Output "=== Prepared artifacts ===" | ||
| Get-ChildItem | Select-Object Name, Length | ||
| - 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 | ||
| echo "=== Prepared artifacts ===" | ||
| ls -la | ||
| - 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: 创建并发布 Release ===== | ||
| release: | ||
| runs-on: ubuntu-latest | ||
| needs: build | ||
| permissions: | ||
| actions: read | ||
| contents: write | ||
| env: | ||
| RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }} | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/download-artifact@v4 | ||
| with: | ||
| path: release-assets | ||
| merge-multiple: true | ||
| - name: Verify artifacts | ||
| run: | | ||
| echo "=== Downloaded artifacts ===" | ||
| ls -la release-assets/ | ||
| echo | ||
| # Validate each platform | ||
| find release-assets/ -maxdepth 1 -name "*.exe" | grep -q . && echo "✓ Windows" || echo "✗ Windows MISSING" | ||
| find release-assets/ -maxdepth 1 -name "*.dmg" | grep -q . && echo "✓ macOS" || echo "✗ macOS MISSING" | ||
| find release-assets/ -maxdepth 1 \( -name "*.AppImage" -o -name "*.deb" \) | grep -q . && echo "✓ Linux" || echo "✗ Linux MISSING" | ||
| - name: Create and publish Release | ||
| run: | | ||
| set -euo pipefail | ||
| VERSION="${RELEASE_TAG#v}" | ||
| # Collect binary filenames | ||
| cd release-assets | ||
| BINARIES=(*) | ||
| echo "Binaries to upload: ${BINARIES[*]}" | ||
| # Build release notes | ||
| cat > /tmp/notes.md << NOTES | ||
| ## SunCode ${RELEASE_TAG} | ||
| ### 文件 | ||
| | 文件 | SHA256 | | ||
| |------|--------| | ||
| NOTES | ||
| while read -r hash name; do | ||
| echo "| ${name} | \`${hash}\` |" >> /tmp/notes.md | ||
| done < SHA256SUMS 2>/dev/null || true | ||
| cat >> /tmp/notes.md << NOTES | ||
| ### 安装 | ||
| - **Windows**: 下载 \`.exe\` 安装程序或便携版 | ||
| - **macOS**: 下载 \`.dmg\` 拖入 Applications | ||
| - **Linux**: 下载 \`.AppImage\` 并 \`chmod +x\`,或 \`.deb\` 用 \`sudo dpkg -i\` 安装 | ||
| > 未签名应用,Windows 可能提示 SmartScreen,macOS 需右键打开。 | ||
| NOTES | ||
| cat /tmp/notes.md | ||
| # Remove any existing draft for this tag | ||
| IS_DRAFT=$(gh release view "${RELEASE_TAG}" --json isDraft --jq .isDraft 2>/dev/null || echo "") | ||
| if [ "${IS_DRAFT}" == "true" ]; then | ||
| echo "Removing existing draft..." | ||
| gh release delete "${RELEASE_TAG}" --yes | ||
| fi | ||
| if [ "${IS_DRAFT}" == "false" ]; then | ||
| echo "::error::Release ${RELEASE_TAG} already published" | ||
| exit 1 | ||
| fi | ||
| # Create release directly (not draft — just ship it) | ||
| gh release create "${RELEASE_TAG}" \ | ||
| --title "SunCode ${RELEASE_TAG}" \ | ||
| --notes-file /tmp/notes.md \ | ||
| "${BINARIES[@]}" | ||
| echo "::notice::✅ Release ${RELEASE_TAG} 发布成功" | ||
| - name: Generate and upload update metadata | ||
| run: | | ||
| set -euo pipefail | ||
| VERSION="${RELEASE_TAG#v}" | ||
| RELEASE_DATE=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) | ||
| cd release-assets | ||
| # ── Windows NSIS: latest.yml ── | ||
| # Match the NSIS installer (not portable) | ||
| NSIS_EXE=$(ls *.exe 2>/dev/null | grep -v -i portable | head -1 || echo "") | ||
| if [ -n "$NSIS_EXE" ]; then | ||
| SIZE=$(stat -c%s "$NSIS_EXE" 2>/dev/null || stat -f%z "$NSIS_EXE") | ||
| SHA512=$(sha512sum "$NSIS_EXE" | awk '{print $1}') | ||
| cat > latest.yml << YAMLEOF | ||
| version: ${VERSION} | ||
| files: | ||
| - url: ${NSIS_EXE} | ||
| sha512: ${SHA512} | ||
| size: ${SIZE} | ||
| path: ${NSIS_EXE} | ||
| sha512: ${SHA512} | ||
| releaseDate: ${RELEASE_DATE} | ||
| YAMLEOF | ||
| echo "=== latest.yml ===" | ||
| cat latest.yml | ||
| gh release upload "${RELEASE_TAG}" latest.yml --clobber | ||
| fi | ||
| # ── Linux AppImage: latest-linux.yml ── | ||
| APPIMAGE=$(ls *.AppImage 2>/dev/null | head -1 || echo "") | ||
| if [ -n "$APPIMAGE" ]; then | ||
| SIZE=$(stat -c%s "$APPIMAGE" 2>/dev/null || stat -f%z "$APPIMAGE") | ||
| SHA512=$(sha512sum "$APPIMAGE" | awk '{print $1}') | ||
| cat > latest-linux.yml << YAMLEOF | ||
| version: ${VERSION} | ||
| files: | ||
| - url: ${APPIMAGE} | ||
| sha512: ${SHA512} | ||
| size: ${SIZE} | ||
| path: ${APPIMAGE} | ||
| sha512: ${SHA512} | ||
| releaseDate: ${RELEASE_DATE} | ||
| YAMLEOF | ||
| echo "=== latest-linux.yml ===" | ||
| cat latest-linux.yml | ||
| gh release upload "${RELEASE_TAG}" latest-linux.yml --clobber | ||
| fi | ||
| # ── macOS DMG: latest-mac.yml ── | ||
| DMG=$(ls *.dmg 2>/dev/null | head -1 || echo "") | ||
| if [ -n "$DMG" ]; then | ||
| SIZE=$(stat -c%s "$DMG" 2>/dev/null || stat -f%z "$DMG") | ||
| SHA512=$(sha512sum "$DMG" | awk '{print $1}') | ||
| cat > latest-mac.yml << YAMLEOF | ||
| version: ${VERSION} | ||
| files: | ||
| - url: ${DMG} | ||
| sha512: ${SHA512} | ||
| size: ${SIZE} | ||
| path: ${DMG} | ||
| sha512: ${SHA512} | ||
| releaseDate: ${RELEASE_DATE} | ||
| YAMLEOF | ||
| echo "=== latest-mac.yml ===" | ||
| cat latest-mac.yml | ||
| gh release upload "${RELEASE_TAG}" latest-mac.yml --clobber | ||
| fi | ||
| echo "::notice::✅ Update metadata uploaded" | ||