d3ro-voice/.github/workflows/release.yml
Yun Chan 1af3cf75c7
Some checks failed
deploy-site / deploy (push) Failing after 14m16s
fix(release): stop shipping native modules built for the wrong runtime
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.
2026-09-18 15:45:03 +09:00

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