ci(release): 修复 Windows native 与镜像扫描 #13
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.6" | |
| required: true | |
| type: string | |
| publish_latest: | |
| description: "Also publish the Docker latest tag" | |
| required: true | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: write | |
| packages: write | |
| security-events: write | |
| attestations: write | |
| id-token: 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 }} | |
| TASKBRIDGE_WS_URL: ${{ vars.TASKBRIDGE_WS_URL }} | |
| 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.6." | |
| exit 1 | |
| fi | |
| expected_version="$(tr -d '[:space:]' < VERSION)" | |
| if [ "${RELEASE_VERSION#v}" != "$expected_version" ]; then | |
| echo "::error::Release version must match VERSION (${expected_version})." | |
| exit 1 | |
| fi | |
| - name: Set up Node.js for repository guards | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - name: Verify version source | |
| run: node scripts/check-version-source.mjs | |
| - name: Set up Python for dependency audit | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| cache-dependency-path: | | |
| backend/requirements.txt | |
| backend/requirements-dev.txt | |
| - name: Install backend audit dependencies | |
| working-directory: backend | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements-dev.txt | |
| - name: Audit Python dependencies | |
| working-directory: backend | |
| run: python -m pip_audit -r requirements-dev.txt | |
| - name: Run backend tests | |
| working-directory: backend | |
| run: python -m pytest tests -q | |
| - name: Verify database migrations | |
| working-directory: backend | |
| run: python -m pytest tests/test_migrations.py -q | |
| - name: Compile backend and tools | |
| working-directory: backend | |
| run: python -m compileall -q app tests tools | |
| - name: Verify OpenAPI contract | |
| working-directory: backend | |
| run: python -m tools.openapi_contract --check | |
| - name: Lint backend | |
| working-directory: backend | |
| run: python -m ruff check app tests tools | |
| - name: Validate release endpoint configuration | |
| shell: bash | |
| run: | | |
| validate_endpoint() { | |
| local name="$1" | |
| local value="$2" | |
| local required_path="$3" | |
| shift 3 | |
| local allowed_schemes=("$@") | |
| if [ -z "$value" ]; then | |
| echo "::error::${name} must be configured in repository variables before release." | |
| exit 1 | |
| fi | |
| local scheme_ok=false | |
| for scheme in "${allowed_schemes[@]}"; do | |
| if [[ "$value" == ${scheme}* ]]; then | |
| scheme_ok=true | |
| break | |
| fi | |
| done | |
| if [ "$scheme_ok" != true ]; then | |
| echo "::error::${name} must use one of: ${allowed_schemes[*]} for release artifacts." | |
| exit 1 | |
| fi | |
| if [[ "$value" != *"${required_path}"* ]]; then | |
| echo "::error::${name} must include ${required_path}." | |
| exit 1 | |
| fi | |
| host="$(python -c 'from urllib.parse import urlparse; import sys; print((urlparse(sys.argv[1]).hostname or "").lower())' "$value")" | |
| if [[ "$host" =~ ^(localhost|127\.|0\.0\.0\.0|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.) ]]; then | |
| echo "::error::${name} must not use a private or local network address for public release artifacts." | |
| exit 1 | |
| fi | |
| } | |
| validate_endpoint "TASKBRIDGE_BASE_URL" "$TASKBRIDGE_BASE_URL" "/api/v1/" "http://" "https://" | |
| validate_endpoint "TASKBRIDGE_WS_URL" "$TASKBRIDGE_WS_URL" "/ws/sync" "ws://" "wss://" | |
| - 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: Prepare Android signing key | |
| shell: bash | |
| run: | | |
| signing_values=( | |
| "$ANDROID_KEYSTORE_BASE64" | |
| "$ANDROID_KEYSTORE_PASSWORD" | |
| "$ANDROID_KEY_ALIAS" | |
| "$ANDROID_KEY_PASSWORD" | |
| ) | |
| configured=0 | |
| for value in "${signing_values[@]}"; do | |
| if [ -n "$value" ]; then | |
| configured=1 | |
| fi | |
| done | |
| missing=() | |
| for name in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do | |
| if [ -z "${!name}" ]; then | |
| missing+=("$name") | |
| fi | |
| done | |
| if [ "$configured" -eq 0 ]; then | |
| echo "::notice::Android signing secrets are not configured; building an unsigned release APK." | |
| echo "TASKBRIDGE_ANDROID_SIGNED_RELEASE=false" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| echo "::error::Incomplete Android signing secrets: ${missing[*]}." | |
| echo "Configure all Android signing secrets or leave all of them empty for an unsigned APK." >&2 | |
| exit 1 | |
| fi | |
| echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > release.keystore | |
| if [ ! -s release.keystore ]; then | |
| echo "Decoded Android signing keystore is empty or missing." >&2 | |
| exit 1 | |
| fi | |
| echo "TASKBRIDGE_ANDROID_SIGNED_RELEASE=true" >> "$GITHUB_ENV" | |
| 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 }} | |
| - 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 }} | |
| - name: Verify Android APK signature | |
| shell: bash | |
| run: | | |
| if [ "$TASKBRIDGE_ANDROID_SIGNED_RELEASE" != "true" ]; then | |
| echo "::notice::Skipping Android APK signature verification because this release is unsigned." | |
| exit 0 | |
| fi | |
| apk_path="$(find app/build/outputs/apk/release -maxdepth 1 -type f -name 'app-release.apk' | head -n 1)" | |
| if [ -z "$apk_path" ]; then | |
| echo "No release APK was produced." >&2 | |
| exit 1 | |
| fi | |
| android_sdk="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" | |
| if [ -z "$android_sdk" ]; then | |
| echo "ANDROID_HOME or ANDROID_SDK_ROOT is required to locate apksigner." >&2 | |
| exit 1 | |
| fi | |
| apksigner="$(find "$android_sdk/build-tools" -maxdepth 2 -type f -name apksigner | sort -V | tail -n 1)" | |
| if [ -z "$apksigner" ]; then | |
| echo "apksigner was not found in Android SDK build-tools." >&2 | |
| exit 1 | |
| fi | |
| "$apksigner" verify --verbose --print-certs "$apk_path" | |
| - name: Prepare Android artifact | |
| shell: bash | |
| run: | | |
| mkdir -p ../artifacts | |
| find app/build/outputs/apk -maxdepth 3 -type f -print | |
| if [ "$TASKBRIDGE_ANDROID_SIGNED_RELEASE" = "true" ]; then | |
| apk_path="$(find app/build/outputs/apk/release -maxdepth 1 -type f -name 'app-release.apk' | head -n 1)" | |
| artifact_name="TaskBridge-${RELEASE_VERSION}-android.apk" | |
| else | |
| apk_path="$(find app/build/outputs/apk/release -maxdepth 1 -type f -name 'app-release-unsigned.apk' | head -n 1)" | |
| artifact_name="TaskBridge-${RELEASE_VERSION}-android-unsigned.apk" | |
| fi | |
| if [ -z "$apk_path" ]; then | |
| echo "No release APK was produced." >&2 | |
| exit 1 | |
| fi | |
| cp "$apk_path" "../artifacts/${artifact_name}" | |
| - 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: Verify version source | |
| run: node ..\scripts\check-version-source.mjs | |
| - name: Verify desktop lockfile registry metadata | |
| run: npm run check:lockfile-registry | |
| - name: Install dependencies | |
| run: npm ci | |
| env: | |
| TASKBRIDGE_SKIP_NATIVE_REBUILD: "1" | |
| - name: Audit npm dependencies | |
| run: npm audit --audit-level=high | |
| - name: Verify desktop endpoint config | |
| run: npm run check:desktop-endpoint-config | |
| - name: Verify security-sensitive config | |
| run: npm run check:security-config | |
| - name: Verify auth session governance | |
| run: npm run check:auth-session-config | |
| - name: Verify backend observability config | |
| run: npm run check:backend-observability | |
| - name: Verify desktop package size config | |
| run: npm run check:package-size-config | |
| - name: Run desktop unit tests | |
| run: npm run test:unit | |
| - name: Verify quick-add parser behavior | |
| run: npm run check:quick-add-parser | |
| - name: Verify task ordering behavior | |
| run: npm run check:task-order | |
| - name: Verify sync push handling | |
| run: npm run check:sync-push | |
| - name: Verify sync diagnostics | |
| run: npm run check:sync-diagnostics | |
| - name: Verify sync recovery center | |
| run: npm run check:sync-recovery-center | |
| - name: Verify desktop backup handling | |
| run: npm run check:desktop-backup | |
| - name: Verify desktop theme config | |
| run: npm run check:desktop-theme | |
| - name: Verify desktop docs consistency | |
| run: npm run check:desktop-docs | |
| - name: Verify local bootstrap guidance | |
| run: npm run check:local-bootstrap | |
| - name: Verify release readiness config | |
| run: npm run check:release-readiness | |
| - name: Verify release artifact config | |
| run: npm run check:release-artifacts | |
| - name: Verify production hardening config | |
| run: npm run check:production-hardening | |
| - name: Verify desktop task list completeness | |
| run: npm run check:desktop-task-list-completeness | |
| - name: Verify Android localization config | |
| run: npm run check:android-localization | |
| - name: Verify Android task delete confirmation | |
| run: npm run check:android-task-delete-confirmation | |
| - name: Verify refresh token singleflight config | |
| run: npm run check:refresh-singleflight | |
| - name: Verify registration governance config | |
| run: npm run check:registration-governance | |
| - name: Verify sync retry preservation | |
| run: npm run check:sync-retry-preservation | |
| - name: Verify Android list and realtime config | |
| run: npm run check:android-list-realtime | |
| - name: Verify release endpoint defaults | |
| run: npm run check:release-endpoint-defaults | |
| - name: Verify Android sync status message config | |
| run: npm run check:android-sync-status-message | |
| - name: Verify Android sync recovery config | |
| run: npm run check:android-sync-recovery | |
| - name: Verify CI workflow desktop coverage | |
| run: npm run check:ci-workflows | |
| - name: Verify cross-client contract drift | |
| run: npm run check:contract-drift | |
| - name: Verify source test visibility | |
| run: npm run check:source-tests-visible | |
| - name: Verify supply chain config | |
| run: npm run check:supply-chain | |
| - name: Verify desktop auto-update config | |
| run: npm run check:desktop-auto-update | |
| - name: Verify Android data extraction config | |
| run: npm run check:android-data-extraction | |
| - name: Verify security governance config | |
| run: npm run check:security-governance | |
| - name: Verify Web client shell | |
| run: node ..\scripts\check-web-client.mjs | |
| - name: Run Web unit tests | |
| run: npm --prefix .. run test:web | |
| - name: Verify Web offline-first behavior | |
| run: node ..\scripts\check-web-offline-first.mjs | |
| - name: Verify Web offline core behavior | |
| run: node ..\scripts\check-web-offline-core.mjs | |
| - name: Smoke test Web client shell | |
| run: node ..\scripts\smoke-web-client.mjs | |
| - name: Prepare Windows signing certificate | |
| shell: pwsh | |
| run: | | |
| $hasCertificate = -not [string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_BASE64) | |
| $hasPassword = -not [string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD) | |
| if (-not $hasCertificate -and -not $hasPassword) { | |
| "TASKBRIDGE_WINDOWS_SIGNED_RELEASE=false" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| "CSC_IDENTITY_AUTO_DISCOVERY=false" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| Write-Host "::notice::Windows signing certificate is not configured; building an unsigned installer." | |
| exit 0 | |
| } | |
| if (-not $hasCertificate -or -not $hasPassword) { | |
| throw "Incomplete Windows signing secrets. Configure both WINDOWS_CERTIFICATE_BASE64 and WINDOWS_CERTIFICATE_PASSWORD, or leave both empty for an unsigned installer." | |
| } | |
| $certificatePath = Join-Path $env:RUNNER_TEMP "taskbridge-codesign.pfx" | |
| [IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_BASE64)) | |
| if ((Get-Item -LiteralPath $certificatePath).Length -le 0) { | |
| throw "Decoded Windows signing certificate is empty." | |
| } | |
| "CSC_LINK=$certificatePath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| "CSC_KEY_PASSWORD=$env:WINDOWS_CERTIFICATE_PASSWORD" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| "TASKBRIDGE_WINDOWS_SIGNED_RELEASE=true" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| env: | |
| WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }} | |
| WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} | |
| - name: Build Windows installer | |
| run: npm run dist | |
| env: | |
| TASKBRIDGE_BASE_URL: ${{ env.TASKBRIDGE_BASE_URL }} | |
| TASKBRIDGE_WS_URL: ${{ env.TASKBRIDGE_WS_URL }} | |
| ELECTRON_CACHE: ${{ github.workspace }}\\.cache\\electron | |
| ELECTRON_BUILDER_CACHE: ${{ github.workspace }}\\.cache\\electron-builder | |
| - name: Verify Windows installer signatures | |
| shell: pwsh | |
| run: | | |
| if ($env:TASKBRIDGE_WINDOWS_SIGNED_RELEASE -ne "true") { | |
| Write-Host "::notice::Skipping Windows installer signature verification because this release is unsigned." | |
| exit 0 | |
| } | |
| $installers = @(Get-ChildItem -Path release -File | Where-Object { $_.Extension -eq '.exe' }) | |
| if ($installers.Count -eq 0) { | |
| throw "No Windows installer .exe was produced." | |
| } | |
| foreach ($installer in $installers) { | |
| $signature = Get-AuthenticodeSignature -LiteralPath $installer.FullName | |
| if ($signature.Status -ne 'Valid') { | |
| throw "Windows installer is not signed with a valid Authenticode signature: $($installer.FullName) [$($signature.Status)]" | |
| } | |
| } | |
| - 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', '.yml', '.blockmap') -or $_.Name -eq 'latest.yml' | |
| } | ForEach-Object { | |
| Copy-Item -LiteralPath $_.FullName -Destination ..\artifacts\$($_.Name) | |
| } | |
| - name: Validate desktop update artifacts | |
| shell: pwsh | |
| run: | | |
| # Desktop auto-update publishes to the stable latest channel. | |
| $artifacts = @(Get-ChildItem -Path ..\artifacts -File) | |
| if (-not ($artifacts | Where-Object { $_.Name -eq 'latest.yml' })) { | |
| throw "No desktop auto-update latest.yml was produced." | |
| } | |
| if (-not ($artifacts | Where-Object { $_.Extension -eq '.blockmap' })) { | |
| throw "No desktop installer blockmap was produced." | |
| } | |
| if (-not ($artifacts | Where-Object { $_.Extension -eq '.exe' })) { | |
| throw "No Windows installer .exe was copied for update delivery." | |
| } | |
| - 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 backend image for vulnerability scan | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./backend | |
| file: ./backend/Dockerfile | |
| push: false | |
| load: true | |
| tags: taskbridge:release-scan | |
| labels: ${{ steps.meta.outputs.labels }} | |
| - name: Scan backend image before publish | |
| uses: aquasecurity/trivy-action@0.35.0 | |
| with: | |
| image-ref: taskbridge:release-scan | |
| severity: 'CRITICAL,HIGH' | |
| ignore-unfixed: true | |
| exit-code: '1' | |
| - 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 }} | |
| sbom: true | |
| provenance: mode=max | |
| 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: Generate release artifact checksums | |
| shell: bash | |
| run: | | |
| cd release-artifacts | |
| sha256sum * > SHA256SUMS.txt | |
| - name: Attest release artifacts | |
| uses: actions/attest-build-provenance@v2 | |
| with: | |
| subject-path: release-artifacts/* | |
| - 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 }} |