Some checks failed
deploy-site / deploy (push) Failing after 14m16s
The released installer could not start: it carried a better-sqlite3 build for the host Node runtime instead of Electron, so the app died immediately with a module version mismatch when it opened its database. Packaging now proves the Electron build of every runtime-sensitive native module before an installer or archive exists, and installers are produced only from that verified tree, so the mistake cannot pass silently. The release pipelines run the same check. The default local model also pointed at a retired model: a *.gguf name that Ollama cannot serve, while the settings, onboarding, and guide screens recommended an older model. All of them now use the model the service code already preferred.
606 lines
26 KiB
YAML
606 lines
26 KiB
YAML
# .github/workflows/release.yml
|
|
# Multi-Platform Automated Release Pipeline for D3RO Voice Desktop & Admin
|
|
|
|
name: Release & Packaging Pipeline
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*.*.*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: 'Release version (e.g. 1.0.0)'
|
|
required: true
|
|
default: '1.1.0'
|
|
|
|
permissions:
|
|
contents: write
|
|
packages: write
|
|
|
|
jobs:
|
|
release-preflight:
|
|
name: Release Preflight
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Node.js 24
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'npm'
|
|
|
|
- name: Setup .NET 10
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: '10.0.302'
|
|
|
|
- name: Setup Deno 2.8.1
|
|
uses: denoland/setup-deno@v2
|
|
with:
|
|
deno-version: v2.8.1
|
|
|
|
- name: Install JavaScript Dependencies
|
|
run: |
|
|
npm ci
|
|
npm --prefix apps/mobile-rn ci --workspaces=false
|
|
|
|
- name: Verify Source, Security, Tests, and Play Assets
|
|
run: |
|
|
npm run version:check
|
|
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
|
node scripts/ci/sync-version.mjs --check --tag "$GITHUB_REF_NAME"
|
|
fi
|
|
npm run release:metadata:test
|
|
npm run security:secrets:test
|
|
npm run security:secrets
|
|
npm run release:mobile:boundary:test
|
|
npm run release:mobile:config:test
|
|
npm run release:mobile:build-config:test
|
|
npm run release:play:assets
|
|
npm run lint
|
|
npm run typecheck
|
|
npm test
|
|
npm --prefix apps/mobile-rn run lint
|
|
npm --prefix apps/mobile-rn run typecheck
|
|
npm --prefix apps/mobile-rn run test:ci
|
|
|
|
- name: Check and Test Every Supabase Edge Function
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
for entrypoint in server/supabase/functions/*/index.ts; do
|
|
deno check --config server/supabase/functions/deno.json "$entrypoint"
|
|
done
|
|
deno test --config server/supabase/functions/deno.json --allow-read --allow-env server/supabase/functions
|
|
|
|
- name: Test .NET API Authorization and Gateway Boundaries
|
|
run: |
|
|
dotnet restore apps/api-server.Tests/D3ROVoice.Api.Tests.csproj
|
|
dotnet test apps/api-server.Tests/D3ROVoice.Api.Tests.csproj --configuration Release --no-restore -p:StaticWebAssetsEnabled=false
|
|
|
|
# ──────────────────────────────────────────────────────────────────
|
|
# 1. Package Windows Installer (.exe & .blockmap & latest.yml)
|
|
# ──────────────────────────────────────────────────────────────────
|
|
package-windows:
|
|
name: Package Windows Desktop App
|
|
needs: release-preflight
|
|
runs-on: windows-latest
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js 24
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'npm'
|
|
|
|
- name: Install Dependencies
|
|
run: npm ci
|
|
|
|
- name: Build All Workspaces
|
|
run: |
|
|
npm run version:check
|
|
npm run typecheck
|
|
npm run build --workspace=@d3ro/desktop
|
|
|
|
- name: Build STT Sidecar (local transcription engine)
|
|
run: |
|
|
# Local transcription depends on the faster-whisper sidecar; a release
|
|
# that ships without it cannot transcribe at all, so build and verify
|
|
# the bundle before packaging.
|
|
npm run sidecar:setup --workspace=@d3ro/desktop
|
|
npm run sidecar:build --workspace=@d3ro/desktop
|
|
node scripts/ci/verify-sidecar-bundle.mjs
|
|
|
|
- name: Package with Electron Builder (NSIS x64)
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
if ([string]::IsNullOrWhiteSpace($env:CSC_LINK)) {
|
|
throw 'WIN_CSC_LINK must contain the production code-signing PFX path, URL, or base64 payload.'
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($env:CSC_KEY_PASSWORD)) {
|
|
throw 'WIN_CSC_KEY_PASSWORD is required.'
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($env:WIN_CSC_EXPECTED_SIGNER_SUBJECT)) {
|
|
throw 'WIN_CSC_EXPECTED_SIGNER_SUBJECT is required.'
|
|
}
|
|
if ($env:WIN_CSC_EXPECTED_SIGNER_SUBJECT -match '(?i)Everything2EverythingDev') {
|
|
throw 'The local Everything2EverythingDev certificate is not a production signing identity.'
|
|
}
|
|
$releaseVersion = node -p "require('./release/product-version.json').version"
|
|
Push-Location apps/desktop
|
|
try {
|
|
npx electron-builder --win --x64 --config electron-builder.yml
|
|
node scripts/ci/verify-native-abi.mjs
|
|
if ($LASTEXITCODE -ne 0) { throw "electron-builder failed with exit code $LASTEXITCODE." }
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
& scripts/ci/verify-windows-release-artifact.ps1 `
|
|
-ExpectedVersion $releaseVersion `
|
|
-ExpectedSignerSubject $env:WIN_CSC_EXPECTED_SIGNER_SUBJECT `
|
|
-ReleaseDirectory "apps/desktop/release/$releaseVersion"
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
|
|
WIN_CSC_EXPECTED_SIGNER_SUBJECT: ${{ secrets.WIN_CSC_EXPECTED_SIGNER_SUBJECT }}
|
|
|
|
- name: Upload Windows Build Artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: windows-release-assets
|
|
path: |
|
|
apps/desktop/release/*/*.exe
|
|
apps/desktop/release/*/*.blockmap
|
|
apps/desktop/release/*/latest.yml
|
|
|
|
# ──────────────────────────────────────────────────────────────────
|
|
# 2. Package macOS Desktop App (.dmg & .zip & latest-mac.yml)
|
|
# ──────────────────────────────────────────────────────────────────
|
|
package-macos:
|
|
name: Package macOS Desktop App
|
|
needs: release-preflight
|
|
runs-on: macos-latest
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js 24
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'npm'
|
|
|
|
- name: Install Dependencies
|
|
run: npm ci
|
|
|
|
- name: Build All Workspaces
|
|
run: |
|
|
npm run version:check
|
|
npm run typecheck
|
|
npm run build --workspace=@d3ro/desktop
|
|
|
|
- name: Build STT Sidecar (local transcription engine)
|
|
run: |
|
|
# Local transcription depends on the faster-whisper sidecar; a release
|
|
# that ships without it cannot transcribe at all, so build and verify
|
|
# the bundle before packaging.
|
|
npm run sidecar:setup --workspace=@d3ro/desktop
|
|
npm run sidecar:build --workspace=@d3ro/desktop
|
|
node scripts/ci/verify-sidecar-bundle.mjs
|
|
|
|
- name: Package with Electron Builder (DMG & ZIP arm64)
|
|
run: |
|
|
cd apps/desktop
|
|
npx electron-builder --mac --arm64 --config electron-builder.yml
|
|
node scripts/ci/verify-native-abi.mjs
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
|
|
- name: Upload macOS Build Artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: macos-release-assets
|
|
path: |
|
|
apps/desktop/release/*/*.dmg
|
|
apps/desktop/release/*/*.zip
|
|
apps/desktop/release/*/*.blockmap
|
|
apps/desktop/release/*/latest-mac.yml
|
|
|
|
# ──────────────────────────────────────────────────────────────────
|
|
# 3. Package signed Android APK/AAB (arm64, production-only config)
|
|
# ──────────────────────────────────────────────────────────────────
|
|
package-android:
|
|
name: Package Android Mobile App
|
|
needs: release-preflight
|
|
runs-on: ubuntu-latest
|
|
environment: mobile-production-release
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Establish Trusted Mobile Release Identity
|
|
id: android-version
|
|
shell: bash
|
|
env:
|
|
DISPATCH_VERSION: ${{ inputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
git fetch --no-tags origin main
|
|
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
|
|
test "$GITHUB_REF" = "refs/heads/main"
|
|
test "$GITHUB_SHA" = "$(git rev-parse origin/main)"
|
|
VERSION_NAME="$DISPATCH_VERSION"
|
|
RELEASE_TAG="v$VERSION_NAME"
|
|
else
|
|
[[ "$GITHUB_REF" == refs/tags/v* ]]
|
|
VERSION_NAME="${GITHUB_REF_NAME#v}"
|
|
RELEASE_TAG="$GITHUB_REF_NAME"
|
|
git merge-base --is-ancestor "$GITHUB_SHA" origin/main
|
|
fi
|
|
SSOT_VERSION="$(node -p "require('./release/product-version.json').version")"
|
|
VERSION_CODE="$(node -p "require('./release/product-version.json').androidVersionCode")"
|
|
[[ "$VERSION_NAME" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
|
|
test "$VERSION_NAME" = "$SSOT_VERSION"
|
|
test "$RELEASE_TAG" = "v$VERSION_NAME"
|
|
[[ "$VERSION_CODE" =~ ^[1-9][0-9]{0,9}$ ]]
|
|
test "$VERSION_CODE" -le 2100000000
|
|
printf 'name=%s\n' "$VERSION_NAME" >> "$GITHUB_OUTPUT"
|
|
printf 'code=%s\n' "$VERSION_CODE" >> "$GITHUB_OUTPUT"
|
|
printf 'tag=%s\n' "$RELEASE_TAG" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Verify Immutable Checkout Identity
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
test "$GITHUB_SHA" = "$(git rev-parse HEAD)"
|
|
test -z "$(git status --porcelain --untracked-files=all)"
|
|
git rev-parse "${GITHUB_SHA}^{tree}"
|
|
|
|
- name: Setup Node.js 24
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'npm'
|
|
|
|
- name: Verify Mobile Release Boundary Source Contract
|
|
run: node scripts/ci/verify-mobile-release-boundary.mjs --self-test
|
|
|
|
- name: Verify Mobile Release Configuration Contract
|
|
run: npm run release:mobile:config:test
|
|
|
|
- name: Verify Mobile Build Configuration Contract
|
|
run: npm run release:mobile:build-config:test
|
|
|
|
- name: Verify Play Store Asset Contract
|
|
run: npm run release:play:assets
|
|
|
|
- name: Require Restricted AAB Handoff Visibility
|
|
shell: bash
|
|
env:
|
|
REPOSITORY_VISIBILITY: ${{ github.event.repository.visibility }}
|
|
run: |
|
|
set -euo pipefail
|
|
test "$REPOSITORY_VISIBILITY" = "private"
|
|
|
|
- name: Setup JDK 17
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: temurin
|
|
java-version: '17'
|
|
|
|
- name: Setup Android SDK
|
|
uses: android-actions/setup-android@v4
|
|
|
|
- name: Install Pinned Official Bundletool
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
curl --fail --silent --show-error --location \
|
|
--output "$RUNNER_TEMP/bundletool-all-1.18.3.jar" \
|
|
https://github.com/google/bundletool/releases/download/1.18.3/bundletool-all-1.18.3.jar
|
|
printf '%s %s\n' \
|
|
a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29 \
|
|
"$RUNNER_TEMP/bundletool-all-1.18.3.jar" | sha256sum --check --strict
|
|
|
|
- name: Setup Gradle
|
|
uses: gradle/actions/setup-gradle@v6
|
|
with:
|
|
cache-provider: basic
|
|
|
|
- name: Install Dependencies
|
|
run: |
|
|
npm ci
|
|
npm --prefix apps/mobile-rn ci --workspaces=false
|
|
|
|
- name: Materialize Release-only Configuration
|
|
shell: bash
|
|
env:
|
|
ANDROID_RELEASE_KEYSTORE_B64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }}
|
|
ANDROID_GOOGLE_SERVICES_JSON_B64: ${{ secrets.ANDROID_GOOGLE_SERVICES_JSON_B64 }}
|
|
run: |
|
|
set -euo pipefail
|
|
umask 077
|
|
test -n "$ANDROID_RELEASE_KEYSTORE_B64"
|
|
test -n "$ANDROID_GOOGLE_SERVICES_JSON_B64"
|
|
printf '%s' "$ANDROID_RELEASE_KEYSTORE_B64" | base64 --decode > apps/mobile-rn/android/app/release.keystore
|
|
printf '%s' "$ANDROID_GOOGLE_SERVICES_JSON_B64" | base64 --decode > apps/mobile-rn/android/app/google-services.json
|
|
test -s apps/mobile-rn/android/app/release.keystore
|
|
test -s apps/mobile-rn/android/app/google-services.json
|
|
|
|
- name: Prepare Verified Whisper Model
|
|
run: node scripts/ci/prepare-whisper-model.mjs
|
|
|
|
- name: Verify Production Firebase and AdMob Configuration
|
|
env:
|
|
D3RO_FIREBASE_EXPECTED_PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }}
|
|
D3RO_FIREBASE_EXPECTED_PROJECT_NUMBER: ${{ secrets.FIREBASE_PROJECT_NUMBER }}
|
|
D3RO_FIREBASE_EXPECTED_MOBILESDK_APP_ID: ${{ secrets.FIREBASE_MOBILESDK_APP_ID }}
|
|
run: |
|
|
export D3RO_ADMOB_APP_ID="$(node -p "require('./release/android-release-identity.json').adMobAppId")"
|
|
export D3RO_ADMOB_BANNER_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobBannerUnitId")"
|
|
export D3RO_ADMOB_REWARDED_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobRewardedUnitId")"
|
|
npm run release:mobile:config
|
|
|
|
- name: Test Mobile TypeScript and Jest
|
|
run: |
|
|
npm --prefix apps/mobile-rn run lint
|
|
npm --prefix apps/mobile-rn run typecheck
|
|
npm --prefix apps/mobile-rn run test:ci
|
|
|
|
- name: Build Signed arm64 APK and AAB
|
|
working-directory: apps/mobile-rn/android
|
|
env:
|
|
D3RO_RELEASE_STORE_FILE: ${{ github.workspace }}/apps/mobile-rn/android/app/release.keystore
|
|
D3RO_RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_RELEASE_STORE_PASSWORD }}
|
|
D3RO_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }}
|
|
D3RO_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
|
|
D3RO_VERSION_NAME: ${{ steps.android-version.outputs.name }}
|
|
D3RO_VERSION_CODE: ${{ steps.android-version.outputs.code }}
|
|
run: |
|
|
export D3RO_ADMOB_APP_ID="$(node -p "require('../../../release/android-release-identity.json').adMobAppId")"
|
|
export D3RO_ADMOB_BANNER_UNIT_ID="$(node -p "require('../../../release/android-release-identity.json').adMobBannerUnitId")"
|
|
export D3RO_ADMOB_REWARDED_UNIT_ID="$(node -p "require('../../../release/android-release-identity.json').adMobRewardedUnitId")"
|
|
./gradlew :app:assembleRelease :app:bundleRelease -PreactNativeArchitectures=arm64-v8a --no-daemon
|
|
|
|
- name: Materialize Release Evidence Signing Key
|
|
shell: bash
|
|
env:
|
|
ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_B64: ${{ secrets.ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_B64 }}
|
|
run: |
|
|
set -euo pipefail
|
|
umask 077
|
|
test -n "$ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_B64"
|
|
printf '%s' "$ANDROID_RELEASE_EVIDENCE_PRIVATE_KEY_B64" | base64 --decode > apps/mobile-rn/android/app/release-evidence-private.pem
|
|
test -s apps/mobile-rn/android/app/release-evidence-private.pem
|
|
|
|
- name: Verify Release BuildConfig, Signature, ABI, Bundle, Ads, and Offline Model
|
|
shell: bash
|
|
env:
|
|
D3RO_VERSION_NAME: ${{ steps.android-version.outputs.name }}
|
|
D3RO_VERSION_CODE: ${{ steps.android-version.outputs.code }}
|
|
run: |
|
|
set -euo pipefail
|
|
D3RO_ADMOB_APP_ID="$(node -p "require('./release/android-release-identity.json').adMobAppId")"
|
|
D3RO_ADMOB_BANNER_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobBannerUnitId")"
|
|
D3RO_ADMOB_REWARDED_UNIT_ID="$(node -p "require('./release/android-release-identity.json').adMobRewardedUnitId")"
|
|
ANDROID_UPLOAD_CERT_SHA256="$(node -p "require('./release/android-release-identity.json').uploadCertificateSha256")"
|
|
PLAY_APP_SIGNING_CERT_SHA256="$(node -p "require('./release/android-release-identity.json').playAppSigningCertificateSha256")"
|
|
APK=apps/mobile-rn/android/app/build/outputs/apk/release/app-release.apk
|
|
AAB=apps/mobile-rn/android/app/build/outputs/bundle/release/app-release.aab
|
|
test -f "$APK"
|
|
test -f "$AAB"
|
|
node scripts/ci/verify-mobile-build-config.mjs release \
|
|
| tee apps/mobile-rn/android/app/build/outputs/release-build-config.json
|
|
node scripts/ci/create-mobile-release-evidence.mjs \
|
|
--apk "$APK" \
|
|
--aab "$AAB" \
|
|
--bundletool "$RUNNER_TEMP/bundletool-all-1.18.3.jar" \
|
|
--repository "$GITHUB_REPOSITORY" \
|
|
--commit-sha "$GITHUB_SHA" \
|
|
--tree-sha "$(git rev-parse "${GITHUB_SHA}^{tree}")" \
|
|
--git-ref "$GITHUB_REF" \
|
|
--workflow-identity "$GITHUB_WORKFLOW_REF" \
|
|
--run-id "$GITHUB_RUN_ID" \
|
|
--run-attempt "$GITHUB_RUN_ATTEMPT" \
|
|
--runner-identity "$RUNNER_NAME:$RUNNER_OS:$RUNNER_ARCH" \
|
|
--expected-admob-app-id "$D3RO_ADMOB_APP_ID" \
|
|
--expected-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \
|
|
--expected-version-name "$D3RO_VERSION_NAME" \
|
|
--expected-version-code "$D3RO_VERSION_CODE" \
|
|
--private-key apps/mobile-rn/android/app/release-evidence-private.pem \
|
|
--snapshot-dir apps/mobile-rn/android/app/build/outputs/release-snapshot
|
|
VERIFIER_SHA256="$(sha256sum scripts/ci/verify-android-artifact.mjs | awk '{print $1}')"
|
|
BUNDLETOOL_SHA256="a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29"
|
|
node scripts/ci/prepare-mobile-release-publication.mjs \
|
|
--source-root apps/mobile-rn/android/app/build/outputs/release-snapshot \
|
|
--apk apps/mobile-rn/android/app/build/outputs/release-snapshot/app-release.apk \
|
|
--aab apps/mobile-rn/android/app/build/outputs/release-snapshot/app-release.aab \
|
|
--evidence apps/mobile-rn/android/app/build/outputs/release-snapshot/release-artifact-evidence.json \
|
|
--public-key release/mobile-release-evidence-public.pem \
|
|
--destination-dir apps/mobile-rn/android/app/build/outputs/release-publication \
|
|
--expected-admob-app-id "$D3RO_ADMOB_APP_ID" \
|
|
--expected-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \
|
|
--expected-version-name "$D3RO_VERSION_NAME" \
|
|
--expected-version-code "$D3RO_VERSION_CODE" \
|
|
--expected-repository "$GITHUB_REPOSITORY" \
|
|
--expected-commit-sha "$GITHUB_SHA" \
|
|
--expected-tree-sha "$(git rev-parse "${GITHUB_SHA}^{tree}")" \
|
|
--expected-git-ref "$GITHUB_REF" \
|
|
--expected-workflow-identity "$GITHUB_WORKFLOW_REF" \
|
|
--expected-run-id "$GITHUB_RUN_ID" \
|
|
--expected-run-attempt "$GITHUB_RUN_ATTEMPT" \
|
|
--expected-runner-identity "$RUNNER_NAME:$RUNNER_OS:$RUNNER_ARCH" \
|
|
--expected-verifier-sha256 "$VERIFIER_SHA256" \
|
|
--expected-bundletool-sha256 "$BUNDLETOOL_SHA256"
|
|
node scripts/ci/verify-android-app-links.mjs \
|
|
--expected-play-app-signing-cert-sha256 "$PLAY_APP_SIGNING_CERT_SHA256" \
|
|
--forbidden-upload-cert-sha256 "$ANDROID_UPLOAD_CERT_SHA256" \
|
|
| tee apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json
|
|
sha256sum apps/mobile-rn/android/app/build/outputs/release-publication/app-release.apk \
|
|
apps/mobile-rn/android/app/build/outputs/release-publication/app-release.aab \
|
|
| tee apps/mobile-rn/android/app/build/outputs/release-publication/SHA256SUMS.txt
|
|
|
|
- name: Remove Materialized Release Secrets
|
|
if: always()
|
|
shell: bash
|
|
run: rm -f apps/mobile-rn/android/app/release.keystore apps/mobile-rn/android/app/google-services.json apps/mobile-rn/android/app/release-evidence-private.pem
|
|
|
|
- name: Upload Restricted Play Console AAB Handoff
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: android-play-console-handoff
|
|
path: |
|
|
apps/mobile-rn/android/app/build/outputs/release-publication/app-release.aab
|
|
apps/mobile-rn/android/app/build/outputs/release-publication/android-release-evidence.json
|
|
apps/mobile-rn/android/app/build/outputs/release-publication/android-publication-manifest.json
|
|
apps/mobile-rn/android/app/build/outputs/release-publication/SHA256SUMS.txt
|
|
apps/mobile-rn/android/app/build/outputs/release-snapshot/release-artifact-verification.json
|
|
apps/mobile-rn/android/app/build/outputs/release-build-config.json
|
|
apps/mobile-rn/android/app/build/outputs/release-app-links-evidence.json
|
|
retention-days: 7
|
|
if-no-files-found: error
|
|
|
|
# ──────────────────────────────────────────────────────────────────
|
|
# 4. Build & Containerize Admin Dashboard
|
|
# ──────────────────────────────────────────────────────────────────
|
|
package-admin-docker:
|
|
name: Build & Publish Admin Docker Image
|
|
needs: release-preflight
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: actions/setup-buildx-action@v3
|
|
|
|
- name: Log in to GitHub Container Registry (GHCR)
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata (tags, labels) for Docker
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ghcr.io/${{ github.repository }}/admin-console
|
|
|
|
- name: Build and push Docker image
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
file: ./apps/admin/Dockerfile
|
|
push: ${{ github.event_name != 'pull_request' }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
|
|
# ──────────────────────────────────────────────────────────────────
|
|
# 5. Create GitHub Release & Upload Checksums
|
|
# ──────────────────────────────────────────────────────────────────
|
|
publish-release:
|
|
name: Publish Official GitHub Release
|
|
needs: [package-windows, package-macos, package-android, package-admin-docker]
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Re-establish Trusted Release Identity
|
|
id: release-identity
|
|
shell: bash
|
|
env:
|
|
DISPATCH_VERSION: ${{ inputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
git fetch --no-tags origin main
|
|
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
|
|
test "$GITHUB_REF" = "refs/heads/main"
|
|
test "$GITHUB_SHA" = "$(git rev-parse origin/main)"
|
|
VERSION_NAME="$DISPATCH_VERSION"
|
|
RELEASE_TAG="v$VERSION_NAME"
|
|
else
|
|
[[ "$GITHUB_REF" == refs/tags/v* ]]
|
|
VERSION_NAME="${GITHUB_REF_NAME#v}"
|
|
RELEASE_TAG="$GITHUB_REF_NAME"
|
|
git merge-base --is-ancestor "$GITHUB_SHA" origin/main
|
|
fi
|
|
SSOT_VERSION="$(node -p "require('./release/product-version.json').version")"
|
|
[[ "$VERSION_NAME" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
|
|
test "$VERSION_NAME" = "$SSOT_VERSION"
|
|
test "$RELEASE_TAG" = "v$VERSION_NAME"
|
|
printf 'name=%s\n' "$VERSION_NAME" >> "$GITHUB_OUTPUT"
|
|
printf 'tag=%s\n' "$RELEASE_TAG" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Setup Node.js 24
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
|
|
- name: Download Windows Artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: windows-release-assets
|
|
path: release-dist/
|
|
|
|
- name: Download macOS Artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: macos-release-assets
|
|
path: release-dist/
|
|
|
|
- name: Generate SHA-256 Checksums
|
|
run: |
|
|
set -euo pipefail
|
|
find release-dist -type f ! -name SHA256SUMS.txt -print0 \
|
|
| sort -z \
|
|
| xargs -0 sha256sum > release-dist/SHA256SUMS.txt
|
|
cat release-dist/SHA256SUMS.txt
|
|
|
|
- name: Extract Canonical Release Notes
|
|
run: >-
|
|
node scripts/ci/extract-release-notes.mjs
|
|
--version "${{ steps.release-identity.outputs.name }}"
|
|
--output release-notes.md
|
|
|
|
- name: Create GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: |
|
|
release-dist/*
|
|
draft: false
|
|
prerelease: false
|
|
body_path: release-notes.md
|
|
generate_release_notes: false
|
|
tag_name: ${{ steps.release-identity.outputs.tag }}
|
|
fail_on_unmatched_files: true
|
|
overwrite_files: false
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Publish to Forgejo Release and Update Feed
|
|
env:
|
|
FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }}
|
|
FORGEJO_RELEASE_TAG: ${{ steps.release-identity.outputs.tag }}
|
|
FORGEJO_RELEASE_DIR: release-dist
|
|
run: node scripts/ci/publish-forgejo-release.mjs
|