【优化】桌面端依赖问题 #6
Workflow file for this run
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: TaskBridge Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Release version, for example v0.1.4" | |
| required: true | |
| type: string | |
| publish_latest: | |
| description: "Also publish the Docker latest tag" | |
| required: true | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: write | |
| packages: write | |
| concurrency: | |
| group: release-${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }} | |
| cancel-in-progress: false | |
| env: | |
| RELEASE_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }} | |
| DOCKERHUB_IMAGE: ${{ vars.DOCKERHUB_IMAGE || '27xk/taskbridge' }} | |
| TASKBRIDGE_BASE_URL: ${{ vars.TASKBRIDGE_BASE_URL || 'http://192.168.10.30:8000/api/v1/' }} | |
| TASKBRIDGE_WS_URL: ${{ vars.TASKBRIDGE_WS_URL || 'ws://192.168.10.30:8000/ws/sync' }} | |
| TASKBRIDGE_ALLOW_UNSIGNED_RELEASE: ${{ vars.TASKBRIDGE_ALLOW_UNSIGNED_RELEASE || 'false' }} | |
| jobs: | |
| prepare: | |
| name: Prepare Release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate release version | |
| shell: bash | |
| run: | | |
| if [[ ! "${RELEASE_VERSION}" =~ ^v[0-9]+(\.[0-9]+){0,2}([-.][0-9A-Za-z.]+)?$ ]]; then | |
| echo "::error::Release version must start with v, for example v0.1.4." | |
| exit 1 | |
| fi | |
| - name: Ensure release tag exists | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| shell: bash | |
| run: | | |
| git fetch --tags --force | |
| if git rev-parse -q --verify "refs/tags/${RELEASE_VERSION}" >/dev/null; then | |
| echo "Tag ${RELEASE_VERSION} already exists." | |
| else | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag "${RELEASE_VERSION}" "${GITHUB_SHA}" | |
| git push origin "refs/tags/${RELEASE_VERSION}" | |
| fi | |
| android: | |
| name: Build Android APK | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| defaults: | |
| run: | |
| working-directory: android | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| with: | |
| gradle-home-cache-cleanup: true | |
| - name: Decode Android signing key | |
| shell: bash | |
| run: | | |
| if [ -n "$ANDROID_KEYSTORE_BASE64" ]; then | |
| echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > release.keystore | |
| fi | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| - name: Validate Android signing secrets | |
| shell: bash | |
| run: | | |
| missing=() | |
| provided=() | |
| for name in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do | |
| if [ -z "${!name}" ]; then | |
| missing+=("$name") | |
| else | |
| provided+=("$name") | |
| fi | |
| done | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| if [ "${#provided[@]}" -gt 0 ]; then | |
| echo "Android signing secrets are partially configured. Missing: ${missing[*]}" >&2 | |
| exit 1 | |
| fi | |
| if [ "${TASKBRIDGE_ALLOW_UNSIGNED_RELEASE}" = "true" ]; then | |
| echo "::warning::Android signing secrets are not configured. Building an unsigned release APK because TASKBRIDGE_ALLOW_UNSIGNED_RELEASE=true." | |
| exit 0 | |
| fi | |
| echo "Missing Android signing secrets: ${missing[*]}" >&2 | |
| echo "Configure Android signing secrets, or set repository variable TASKBRIDGE_ALLOW_UNSIGNED_RELEASE=true to publish an unsigned APK." >&2 | |
| exit 1 | |
| fi | |
| if [ ! -s release.keystore ]; then | |
| echo "Decoded Android signing keystore is empty or missing." >&2 | |
| exit 1 | |
| fi | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| TASKBRIDGE_ALLOW_UNSIGNED_RELEASE: ${{ env.TASKBRIDGE_ALLOW_UNSIGNED_RELEASE }} | |
| - name: Make Gradle wrapper executable | |
| run: chmod +x gradlew | |
| - name: Run unit tests | |
| run: ./gradlew testReleaseUnitTest -PTASKBRIDGE_USE_CHINA_MIRRORS=false -PTASKBRIDGE_BASE_URL="${TASKBRIDGE_BASE_URL}" -PTASKBRIDGE_WS_URL="${TASKBRIDGE_WS_URL}" --stacktrace | |
| - name: Build release APK | |
| run: ./gradlew :app:assembleRelease -PTASKBRIDGE_USE_CHINA_MIRRORS=false -PTASKBRIDGE_BASE_URL="${TASKBRIDGE_BASE_URL}" -PTASKBRIDGE_WS_URL="${TASKBRIDGE_WS_URL}" --stacktrace | |
| env: | |
| ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android/release.keystore | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| TASKBRIDGE_ALLOW_UNSIGNED_RELEASE: ${{ env.TASKBRIDGE_ALLOW_UNSIGNED_RELEASE }} | |
| - name: Prepare Android artifact | |
| shell: bash | |
| run: | | |
| mkdir -p ../artifacts | |
| find app/build/outputs/apk -maxdepth 3 -type f -print | |
| apk_path="$(find app/build/outputs/apk/release -maxdepth 1 -type f \( -name 'app-release.apk' -o -name 'app-release-unsigned.apk' \) | head -n 1)" | |
| if [ -z "$apk_path" ]; then | |
| echo "No release APK was produced." >&2 | |
| exit 1 | |
| fi | |
| cp "$apk_path" "../artifacts/TaskBridge-${RELEASE_VERSION}-android.apk" | |
| - name: Upload Android artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: taskbridge-android | |
| path: artifacts/*.apk | |
| if-no-files-found: error | |
| desktop: | |
| name: Build Windows installer | |
| runs-on: windows-latest | |
| needs: prepare | |
| defaults: | |
| run: | |
| working-directory: desktop | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: desktop/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Verify desktop endpoint config | |
| run: npm run check:desktop-endpoint-config | |
| - name: Verify security-sensitive config | |
| run: npm run check:security-config | |
| - name: Build Windows installer | |
| run: npm run dist | |
| env: | |
| TASKBRIDGE_BASE_URL: ${{ env.TASKBRIDGE_BASE_URL }} | |
| TASKBRIDGE_WS_URL: ${{ env.TASKBRIDGE_WS_URL }} | |
| CSC_IDENTITY_AUTO_DISCOVERY: "false" | |
| ELECTRON_CACHE: ${{ github.workspace }}\\.cache\\electron | |
| ELECTRON_BUILDER_CACHE: ${{ github.workspace }}\\.cache\\electron-builder | |
| - name: Prepare desktop artifacts | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path ..\artifacts | Out-Null | |
| Get-ChildItem -Path release -Recurse -File | Select-Object FullName, Length | |
| Get-ChildItem -Path release -File | Where-Object { $_.Extension -in '.exe', '.blockmap' } | ForEach-Object { | |
| Copy-Item -LiteralPath $_.FullName -Destination ..\artifacts\$($_.Name) | |
| } | |
| - name: Upload Windows artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: taskbridge-desktop | |
| path: artifacts/* | |
| if-no-files-found: error | |
| docker: | |
| name: Build and publish backend image | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| outputs: | |
| ghcr_image: ${{ steps.image.outputs.ghcr }} | |
| dockerhub_image: ${{ steps.image.outputs.dockerhub }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Validate Docker Hub secrets | |
| shell: bash | |
| run: | | |
| missing=() | |
| for name in DOCKERHUB_USERNAME DOCKERHUB_TOKEN; do | |
| if [ -z "${!name}" ]; then | |
| missing+=("$name") | |
| fi | |
| done | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| echo "::error::Missing Docker Hub secrets: ${missing[*]}." | |
| exit 1 | |
| fi | |
| env: | |
| DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} | |
| DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Resolve image name | |
| id: image | |
| shell: bash | |
| run: | | |
| ghcr_image="ghcr.io/${GITHUB_REPOSITORY_OWNER}/taskbridge" | |
| dockerhub_image="${DOCKERHUB_IMAGE}" | |
| echo "ghcr=${ghcr_image,,}" >> "$GITHUB_OUTPUT" | |
| echo "dockerhub=${dockerhub_image,,}" >> "$GITHUB_OUTPUT" | |
| - name: Docker metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: | | |
| ${{ steps.image.outputs.ghcr }} | |
| ${{ steps.image.outputs.dockerhub }} | |
| tags: | | |
| type=raw,value=${{ env.RELEASE_VERSION }} | |
| type=raw,value=latest,enable=${{ github.event_name == 'push' || inputs.publish_latest == true }} | |
| - name: Build and push backend image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./backend | |
| file: ./backend/Dockerfile | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| release: | |
| name: Update GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: | |
| - android | |
| - desktop | |
| - docker | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: release-artifacts | |
| pattern: taskbridge-* | |
| merge-multiple: true | |
| - name: List release artifacts | |
| shell: bash | |
| run: find release-artifacts -maxdepth 1 -type f -print | |
| - name: Publish GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ env.RELEASE_VERSION }} | |
| name: TaskBridge ${{ env.RELEASE_VERSION }} | |
| target_commitish: ${{ github.sha }} | |
| generate_release_notes: true | |
| files: release-artifacts/* | |
| fail_on_unmatched_files: true | |
| body: | | |
| Backend Docker images: | |
| - `${{ needs.docker.outputs.ghcr_image }}:${{ env.RELEASE_VERSION }}` | |
| - `${{ needs.docker.outputs.dockerhub_image }}:${{ env.RELEASE_VERSION }}` | |
| When `latest` is enabled, these tags are also published: | |
| - `${{ needs.docker.outputs.ghcr_image }}:latest` | |
| - `${{ needs.docker.outputs.dockerhub_image }}:latest` | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |