Skip to content

add more debug info

add more debug info #1

name: Release Desktop
on:
push:
branches:
- main
- windows-installer
paths:
- "apps/desktop/**"
- "apps/windows-installer/**"
- "packages/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/release-desktop.yml"
workflow_dispatch:
inputs:
environment:
description: "Release environment (prod promotes the most recent dev tag)"
type: choice
required: true
default: prod
options:
- prod
- dev
version:
description: "Semver version to promote (prod only, defaults to latest dev tag)"
required: false
type: string
release_context:
description: "Optional extra context to include in the release notes prompt"
required: false
type: string
jobs:
metadata:
name: Determine release metadata
runs-on: ubuntu-latest
permissions:
contents: write
models: read
outputs:
version: ${{ steps.meta.outputs.version }}
tag_name: ${{ steps.meta.outputs.tag_name }}
commit_sha: ${{ steps.meta.outputs.commit_sha }}
release_env: ${{ steps.meta.outputs.release_env }}
release_suffix: ${{ steps.meta.outputs.release_suffix }}
release_flavor: ${{ steps.meta.outputs.release_flavor }}
channel_release_tag: ${{ steps.meta.outputs.channel_release_tag }}
version_path: ${{ steps.meta.outputs.version_path }}
dev_tag_name: ${{ steps.meta.outputs.dev_tag_name }}
release_notes: ${{ steps.release_notes.outputs.release-notes }}
release_body: ${{ steps.release_body.outputs.release_body }}
gpu_tag_name: ${{ steps.meta.outputs.gpu_tag_name }}
gpu_channel_release_tag: ${{ steps.meta.outputs.gpu_channel_release_tag }}
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Compute release metadata
id: meta
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_ENVIRONMENT: ${{ github.event.inputs.environment || '' }}
INPUT_VERSION: ${{ github.event.inputs.version || '' }}
run: |
set -euo pipefail
git fetch --tags --force
if [[ "${EVENT_NAME}" == "push" ]]; then
RELEASE_ENV="dev"
else
case "${INPUT_ENVIRONMENT:-prod}" in
dev|prod)
RELEASE_ENV="${INPUT_ENVIRONMENT:-prod}"
;;
*)
echo "Unsupported environment '${INPUT_ENVIRONMENT}'" >&2
exit 1
;;
esac
fi
SEMVER_REGEX='^[0-9]+\.[0-9]+\.[0-9]+$'
INPUT_VERSION="${INPUT_VERSION:-}"
mapfile -t DEV_TAGS < <(git tag --list 'desktop-dev-v*' --sort=-v:refname)
mapfile -t PROD_TAGS < <(git tag --list 'desktop-v*' --sort=-v:refname)
LAST_DEV_TAG="${DEV_TAGS[0]:-}"
LAST_PROD_TAG="${PROD_TAGS[0]:-}"
if [[ "${RELEASE_ENV}" == "dev" ]]; then
if [[ -n "${INPUT_VERSION}" ]]; then
echo "Ignoring manual version '${INPUT_VERSION}' for dev release (automatic patch bump used)." >&2
fi
if [[ -z "${LAST_DEV_TAG}" ]]; then
VERSION_BASE="0.0.0"
else
VERSION_BASE="${LAST_DEV_TAG#desktop-dev-v}"
fi
if [[ ! "${VERSION_BASE}" =~ ${SEMVER_REGEX} ]]; then
echo "Last dev tag (${LAST_DEV_TAG:-none}) is not in x.y.z format." >&2
exit 1
fi
IFS='.' read -r MAJOR MINOR PATCH <<< "${VERSION_BASE}"
PATCH=$((PATCH + 1))
VERSION="${MAJOR}.${MINOR}.${PATCH}"
TAG_NAME="desktop-dev-v${VERSION}"
COMMIT_SHA="${GITHUB_SHA}"
DEV_TAG_NAME="${TAG_NAME}"
else
if [[ -n "${INPUT_VERSION}" ]]; then
TARGET_VERSION="${INPUT_VERSION}"
if [[ ! "${TARGET_VERSION}" =~ ${SEMVER_REGEX} ]]; then
echo "Provided version '${TARGET_VERSION}' is not in x.y.z format." >&2
exit 1
fi
TARGET_DEV_TAG="desktop-dev-v${TARGET_VERSION}"
if ! git rev-parse -q --verify "refs/tags/${TARGET_DEV_TAG}" >/dev/null; then
echo "Dev tag ${TARGET_DEV_TAG} not found for promotion." >&2
exit 1
fi
else
if [[ -z "${LAST_DEV_TAG}" ]]; then
echo "No dev tag found to promote to prod." >&2
exit 1
fi
TARGET_DEV_TAG="${LAST_DEV_TAG}"
TARGET_VERSION="${TARGET_DEV_TAG#desktop-dev-v}"
if [[ ! "${TARGET_VERSION}" =~ ${SEMVER_REGEX} ]]; then
echo "Dev tag ${TARGET_DEV_TAG} is not in x.y.z format." >&2
exit 1
fi
fi
VERSION="${TARGET_VERSION}"
TAG_NAME="desktop-v${VERSION}"
COMMIT_SHA="$(git rev-parse "${TARGET_DEV_TAG}^{commit}")"
DEV_TAG_NAME="${TARGET_DEV_TAG}"
fi
if git rev-parse -q --verify "refs/tags/${TAG_NAME}" >/dev/null; then
echo "Tag ${TAG_NAME} already exists. Aborting." >&2
exit 1
fi
VERSION_PATH="desktop/${RELEASE_ENV}/${VERSION}"
if [[ "${RELEASE_ENV}" == "dev" ]]; then
PREVIOUS_TAG="${LAST_DEV_TAG}"
else
PREVIOUS_TAG="${LAST_PROD_TAG}"
fi
RELEASE_NOTES_FROM_REF=""
if [[ -n "${PREVIOUS_TAG}" ]]; then
RELEASE_NOTES_FROM_REF="${PREVIOUS_TAG}"
mapfile -t COMMITS_AFTER_PREVIOUS < <(git rev-list --ancestry-path --reverse "${PREVIOUS_TAG}..${COMMIT_SHA}")
FIRST_AFTER_PREVIOUS="${COMMITS_AFTER_PREVIOUS[0]:-}"
if [[ -n "${FIRST_AFTER_PREVIOUS}" ]]; then
RELEASE_NOTES_FROM="${FIRST_AFTER_PREVIOUS}"
else
RELEASE_NOTES_FROM="${COMMIT_SHA}"
fi
else
mapfile -t ROOT_COMMITS < <(git rev-list --max-parents=0 "${COMMIT_SHA}")
ROOT_COUNT=${#ROOT_COMMITS[@]}
if (( ROOT_COUNT > 0 )); then
ROOT_INDEX=$((ROOT_COUNT - 1))
RELEASE_NOTES_FROM="${ROOT_COMMITS[ROOT_INDEX]}"
else
RELEASE_NOTES_FROM="${COMMIT_SHA}"
fi
fi
RELEASE_NOTES_TO="${COMMIT_SHA}"
if [[ "${RELEASE_ENV}" == "dev" ]]; then
CHANNEL_RELEASE_TAG="desktop-dev"
RELEASE_FLAVOR="prod"
RELEASE_SUFFIX="-dev"
GPU_TAG_NAME="desktop-gpu-dev-v${VERSION}"
GPU_CHANNEL_RELEASE_TAG="desktop-gpu-dev"
else
CHANNEL_RELEASE_TAG="desktop-prod"
RELEASE_FLAVOR="prod"
RELEASE_SUFFIX=""
GPU_TAG_NAME="desktop-gpu-v${VERSION}"
GPU_CHANNEL_RELEASE_TAG="desktop-gpu-prod"
fi
{
echo "release_env=${RELEASE_ENV}"
echo "release_suffix=${RELEASE_SUFFIX}"
echo "version=${VERSION}"
echo "tag_name=${TAG_NAME}"
echo "commit_sha=${COMMIT_SHA}"
echo "dev_tag_name=${DEV_TAG_NAME}"
echo "version_path=${VERSION_PATH}"
echo "channel_release_tag=${CHANNEL_RELEASE_TAG}"
echo "release_flavor=${RELEASE_FLAVOR}"
echo "release_notes_from=${RELEASE_NOTES_FROM}"
echo "release_notes_to=${RELEASE_NOTES_TO}"
echo "release_notes_from_ref=${RELEASE_NOTES_FROM_REF}"
echo "gpu_tag_name=${GPU_TAG_NAME}"
echo "gpu_channel_release_tag=${GPU_CHANNEL_RELEASE_TAG}"
} >> "$GITHUB_OUTPUT"
- name: Generate release notes
id: release_notes
uses: josiahsrc/ai-release-notes-builder@v3
env:
GITHUB_TOKEN: ${{ github.token }}
RELEASE_CONTEXT: ${{ github.event.inputs.release_context || '' }}
with:
content_scope: commit_messages
prompt: |
Generate high-level, user-facing release notes based on the diff below.
This is for an AI dictation desktop software application
Follow these rules:
• Focus strictly on user-visible improvements and overall product value.
• Never mention file names, code components, technologies, or internal systems.
• Never describe what changed in the code; describe only the outcome or benefit.
• Use broad, non-technical language.
• Combine related changes into a small number of themes.
• Output no more than 1-4 bullet points.
• Each bullet should be one sentence and written from the user's perspective.
• Avoid technical terms, implementation details, or references to specific subsystems.
• If a change has no user-visible effect, omit it.
• If you're not sure what changed, just say "Various improvements and bug fixes." as a single bullet point.
• Never repeat yourself.
• Your release notes should sound authentic and natural.
• Use direct, factual language. Never be sycophantic or overly enthusiastic.
• Do not start bullets with phrases like "Enjoy", "Experience", "Discover", or similar marketing language.
• State improvements plainly without embellishment (e.g., "Improved performance" not "Enjoy blazing-fast performance").
${{ github.event.inputs.release_context && format('
Additional notes from the release author, PAY SPECIAL ATTENTION TO THESE NOTES.
ONLY MENTION RELEASE NOTES THAT ARE DIRECTLY REFERENCED IN THE FOLLOWING CONTEXT:
{0}', github.event.inputs.release_context) || 'None' }}
from: ${{ steps.meta.outputs.release_notes_from }}
to: ${{ steps.meta.outputs.release_notes_to }}
paths: |
apps/desktop/**
packages/**
- name: Compose release body
id: release_body
env:
RELEASE_NOTES: ${{ steps.release_notes.outputs['release-notes'] }}
RELEASE_ENV: ${{ steps.meta.outputs.release_env }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
default_body="Automated ${RELEASE_ENV} channel build for version ${VERSION}."
notes="${RELEASE_NOTES}"
if [[ -n "${notes}" ]]; then
body="${notes}"
else
body="${default_body}"
fi
printf 'release_body<<EOF\n%s\nEOF\n' "$body" >> "$GITHUB_OUTPUT"
- name: Create release tag
uses: actions/github-script@v7
env:
TAG_NAME: ${{ steps.meta.outputs.tag_name }}
TARGET_SHA: ${{ steps.meta.outputs.commit_sha }}
with:
script: |
const tagName = process.env.TAG_NAME;
const targetSha = process.env.TARGET_SHA;
const owner = context.repo.owner;
const repo = context.repo.repo;
core.info(`Creating tag ${tagName} at ${targetSha}`);
await github.rest.git.createRef({
owner,
repo,
ref: `refs/tags/${tagName}`,
sha: targetSha,
});
- name: Create GPU release tag
uses: actions/github-script@v7
env:
TAG_NAME: ${{ steps.meta.outputs.gpu_tag_name }}
TARGET_SHA: ${{ steps.meta.outputs.commit_sha }}
with:
script: |
const tagName = process.env.TAG_NAME;
const targetSha = process.env.TARGET_SHA;
const owner = context.repo.owner;
const repo = context.repo.repo;
core.info(`Creating GPU tag ${tagName} at ${targetSha}`);
await github.rest.git.createRef({
owner,
repo,
ref: `refs/tags/${tagName}`,
sha: targetSha,
});
build:
name: Build ${{ matrix.label }}
needs: metadata
runs-on: ${{ matrix.os }}
timeout-minutes: 60
permissions:
contents: write
id-token: write
strategy:
fail-fast: false
matrix:
include:
- label: macOS (universal)
os: macos-14
args: "--target universal-apple-darwin"
rust_targets: "aarch64-apple-darwin,x86_64-apple-darwin"
tauri_platform: macos
node_platform: darwin
artifact_name: desktop-macos
gpu_variant: false
- label: Windows (CPU)
os: windows-latest
args: ""
rust_targets: ""
tauri_platform: windows
node_platform: win32
artifact_name: desktop-windows
gpu_variant: false
- label: Windows (GPU)
os: windows-latest
args: "--features windows-gpu"
rust_targets: ""
tauri_platform: windows
node_platform: win32
artifact_name: desktop-windows-gpu
gpu_variant: true
- label: Linux (CPU)
os: ubuntu-22.04
args: ""
rust_targets: ""
tauri_platform: linux
node_platform: linux
artifact_name: desktop-linux
gpu_variant: false
- label: Linux (GPU)
os: ubuntu-22.04
args: "--features linux-gpu"
rust_targets: ""
tauri_platform: linux
node_platform: linux
artifact_name: desktop-linux-gpu
gpu_variant: true
env:
RELEASE_VERSION: ${{ needs.metadata.outputs.version }}
RELEASE_ENV: ${{ needs.metadata.outputs.release_env }}
RELEASE_SUFFIX: ${{ needs.metadata.outputs.release_suffix }}
RELEASE_FLAVOR: ${{ needs.metadata.outputs.release_flavor }}
FLAVOR: ${{ needs.metadata.outputs.release_flavor }}
VITE_FLAVOR: ${{ needs.metadata.outputs.release_flavor }}
VITE_GPU_BUILD: ${{ matrix.gpu_variant }}
VOQUILL_BUILD_FLAVOR: ${{ needs.metadata.outputs.release_flavor }}
VOQUILL_DESKTOP_FLAVOR: ${{ needs.metadata.outputs.release_flavor }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_PRIVATE_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_PRIVATE_KEY_PASSWORD }}
TAURI_UPDATER_PUBLIC_KEY: ${{ secrets.TAURI_UPDATER_PUBLIC_KEY }}
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.metadata.outputs.commit_sha }}
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: package-lock.json
- name: Sync Tauri release configuration
run: node scripts/ci/set-tauri-release-config.mjs
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust_targets }}
- name: Set Windows-only build environment
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
echo "CARGO_TARGET_DIR=D:\cargo" >> $env:GITHUB_ENV
- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: |
apps/desktop/src-tauri -> ${{ matrix.os == 'windows-latest' && 'D:\cargo' || 'target' }}
key: ${{ matrix.os }}-${{ matrix.gpu_variant && 'gpu' || 'cpu' }}-native-off
- name: Set Windows GPU build environment
if: matrix.os == 'windows-latest' && matrix.gpu_variant == true
shell: pwsh
run: |
echo "WHISPER_CMAKE_ARGS=-DGGML_VULKAN=ON -DCMAKE_INSTALL_PREFIX=C:/w" >> $env:GITHUB_ENV
- name: Install system dependencies (Linux)
if: matrix.os == 'ubuntu-22.04'
shell: bash
run: |
set -euo pipefail
sudo apt-get update
# Choose the right WebKitGTK dev package for the runner.
if apt-cache show libwebkit2gtk-4.1-dev >/dev/null 2>&1; then
WEBKIT_PKG=libwebkit2gtk-4.1-dev
else
WEBKIT_PKG=libwebkit2gtk-4.0-dev
fi
sudo apt-get install -y \
build-essential pkg-config cmake \
libgtk-3-dev "$WEBKIT_PKG" \
libayatana-appindicator3-dev librsvg2-dev libasound2-dev libxdo-dev patchelf
- name: Set Linux CPU build environment
if: matrix.os == 'ubuntu-22.04' && matrix.gpu_variant == false
shell: bash
run: |
# Disable -march=native to ensure compatibility with older CPUs (no AVX2 requirement)
echo "WHISPER_CMAKE_ARGS=-DGGML_NATIVE=OFF" >> "$GITHUB_ENV"
- name: Install Vulkan SDK (Linux)
if: matrix.os == 'ubuntu-22.04' && matrix.gpu_variant == true
shell: bash
run: |
set -euo pipefail
sudo apt-get install -y libvulkan1 libvulkan-dev vulkan-tools
# Install glslc from LunarG Vulkan SDK
wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add -
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-jammy.list https://packages.lunarg.com/vulkan/lunarg-vulkan-jammy.list
sudo apt-get update
sudo apt-get install -y vulkan-sdk
- name: Set Linux GPU build environment
if: matrix.os == 'ubuntu-22.04' && matrix.gpu_variant == true
shell: bash
run: |
# Disable -march=native for broader compatibility, enable Vulkan
echo "WHISPER_CMAKE_ARGS=-DGGML_NATIVE=OFF -DGGML_VULKAN=ON" >> "$GITHUB_ENV"
- name: Install dependencies
run: npm ci --include=optional
- name: Install Vulkan SDK (Windows)
if: matrix.os == 'windows-latest' && matrix.gpu_variant == true
uses: jakoch/install-vulkan-sdk-action@v1
with:
version: "1.3.290.0"
cache: true
runtime: true
- name: Install GPU build deps (Windows)
if: matrix.os == 'windows-latest' && matrix.gpu_variant == true
shell: pwsh
run: |
# 1) LLVM / Clang (for bindgen / whisper-rs-sys)
choco install -y llvm
choco install -y cmake --installargs 'ADD_CMAKE_TO_PATH=System' --no-progress
# 2) Wire up clang
"LIBCLANG_PATH=C:\Program Files\LLVM\bin" >> $env:GITHUB_ENV
"CLANG_PATH=C:\Program Files\LLVM\bin\clang.exe" >> $env:GITHUB_ENV
"C:\Program Files\LLVM\bin" >> $env:GITHUB_PATH
- name: Bundle VC++ Runtime DLLs (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
# Copy VC++ 2015-2022 Runtime DLLs to src-tauri for bundling
$srcTauri = Join-Path $env:GITHUB_WORKSPACE "apps/desktop/src-tauri"
Copy-Item "C:\Windows\System32\msvcp140.dll" $srcTauri
Copy-Item "C:\Windows\System32\vcruntime140.dll" $srcTauri
Copy-Item "C:\Windows\System32\vcruntime140_1.dll" $srcTauri
Write-Host "Copied VC++ Runtime DLLs to src-tauri:"
Get-ChildItem $srcTauri -Filter "*.dll" | ForEach-Object { Write-Host " $($_.Name)" }
# Add DLLs to bundle.resources in tauri.conf.json
$configPath = Join-Path $srcTauri "tauri.conf.json"
$config = Get-Content $configPath -Raw | ConvertFrom-Json -Depth 20
$config.bundle.resources = @("msvcp140.dll", "vcruntime140.dll", "vcruntime140_1.dll")
$config | ConvertTo-Json -Depth 20 | Set-Content $configPath -Encoding UTF8
Write-Host "Updated tauri.conf.json with bundle.resources"
- name: Import Apple signing certificate
if: matrix.os == 'macos-14'
id: import-macos-cert
uses: apple-actions/import-codesign-certs@v2
with:
create-keychain: true
keychain: build
keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }}
keychain-timeout: 3600
set-default-keychain: true
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
- name: Allow codesign access to private keys (CI keychain)
if: matrix.os == 'macos-14'
run: |
security set-key-partition-list -S apple-tool:,apple: -s -k "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain-db
- name: Assert Developer ID Application identity is present
if: matrix.os == 'macos-14'
run: |
set -euo pipefail
out="$(security find-identity -v -p codesigning)"
echo "$out"
echo "$out" | grep -q "Developer ID Application" || {
echo "No 'Developer ID Application' identity found. Import a .p12 exported with its private key." >&2
exit 1
}
- name: Prepare Apple notary API key
if: matrix.os == 'macos-14'
run: |
set -euo pipefail
echo "$APPLE_API_KEY_BASE64" | base64 -d > AuthKey.p8
ls -l AuthKey.p8
shell: bash
- name: Verify Apple signing identity
if: matrix.os == 'macos-14'
run: |
set -euo pipefail
keychain_path="${{ steps.import-macos-cert.outputs['keychain-path'] }}"
if [[ -z "${keychain_path}" ]]; then
keychain_path="${HOME}/Library/Keychains/build.keychain-db"
echo "apple-actions/import-codesign-certs did not report a keychain path; falling back to ${keychain_path}." >&2
fi
output="$(security find-identity -v -p codesigning "${keychain_path}" || true)"
printf '%s\n' "$output"
summary_count="$(printf '%s\n' "$output" | awk '/valid identities found$/ { print $1; exit }')"
if [[ -z "${summary_count}" || ! "${summary_count}" =~ ^[0-9]+$ ]]; then
summary_count="$(printf '%s\n' "$output" | awk '/^[[:space:]]*[0-9]+\)/ { count++ } END { print count+0 }')"
fi
if [[ "${summary_count:-0}" -eq 0 ]]; then
echo "No valid code signing identities were imported into ${keychain_path}." >&2
echo "Ensure APPLE_CERTIFICATE contains a base64-encoded .p12 export that includes the private key." >&2
exit 1
fi
- name: Notary connectivity probe
if: matrix.os == 'macos-14'
run: |
xcrun notarytool history \
--key "${GITHUB_WORKSPACE}/AuthKey.p8" \
--key-id "${APPLE_API_KEY_ID}" \
--issuer "${APPLE_API_ISSUER}"
- name: Set Google OAuth credentials
id: google-oauth
shell: bash
run: |
set -euo pipefail
echo "client_id=777461284594-dhgao2eek53ppl4o188ik2i9cigdcmnp.apps.googleusercontent.com" >> "$GITHUB_OUTPUT"
echo "client_secret=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" >> "$GITHUB_OUTPUT"
- name: Compute Tauri CLI args
id: tauri-cli-args
shell: bash
env:
MATRIX_ARGS: ${{ matrix.args }}
RELEASE_ENV: ${{ needs.metadata.outputs.release_env }}
GPU_VARIANT: ${{ matrix.gpu_variant }}
run: |
set -euo pipefail
if [[ "${GPU_VARIANT}" == "true" ]]; then
if [[ "${RELEASE_ENV}" == "prod" ]]; then
config_file="src-tauri/tauri.gpu.prod.conf.json"
else
config_file="src-tauri/tauri.gpu.dev.conf.json"
fi
else
if [[ "${RELEASE_ENV}" == "prod" ]]; then
config_file="src-tauri/tauri.prod.conf.json"
else
config_file="src-tauri/tauri.dev.conf.json"
fi
fi
config_flag="--config ${config_file}"
matrix_args="${MATRIX_ARGS}"
if [[ -z "${matrix_args}" ]]; then
combined_args="${config_flag}"
elif [[ "${matrix_args}" == *" -- "* ]]; then
before="${matrix_args%% -- *}"
after="${matrix_args#* -- }"
if [[ -n "${before}" ]]; then
combined_args="${before} ${config_flag} -- ${after}"
else
combined_args="${config_flag} -- ${after}"
fi
else
combined_args="${matrix_args} ${config_flag}"
fi
printf 'Using %s for %s release.\n' "${config_file}" "${RELEASE_ENV}"
echo "args=${combined_args}" >> "$GITHUB_OUTPUT"
echo "config=${config_file}" >> "$GITHUB_OUTPUT"
- name: Turbo build desktop workspace
env:
MACOSX_DEPLOYMENT_TARGET: "13.3"
CMAKE_OSX_DEPLOYMENT_TARGET: "13.3"
TAURI_PLATFORM: ${{ matrix.tauri_platform }}
VOQUILL_DESKTOP_PLATFORM: ${{ matrix.node_platform }}
run: npm run build -- --filter=desktop...
- name: Build and publish
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PLATFORM: ${{ matrix.tauri_platform }}
VOQUILL_DESKTOP_PLATFORM: ${{ matrix.node_platform }}
MACOSX_DEPLOYMENT_TARGET: "13.3"
CMAKE_OSX_DEPLOYMENT_TARGET: "13.3"
APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }}
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
APPLE_API_KEY: ${{ env.APPLE_API_KEY_ID }}
APPLE_API_KEY_PATH: ${{ github.workspace }}/AuthKey.p8
APPLE_API_ISSUER: ${{ env.APPLE_API_ISSUER }}
TAURI_LOG_LEVEL: ${{ env.TAURI_LOG_LEVEL }}
RUST_LOG: ${{ env.RUST_LOG }}
VOQUILL_GOOGLE_CLIENT_ID: ${{ steps.google-oauth.outputs.client_id }}
VOQUILL_GOOGLE_CLIENT_SECRET: ${{ steps.google-oauth.outputs.client_secret }}
with:
projectPath: apps/desktop
tauriScript: npm run --workspace desktop tauri
args: ${{ steps.tauri-cli-args.outputs.args }}
tagName: ${{ matrix.gpu_variant == true && needs.metadata.outputs.gpu_tag_name || needs.metadata.outputs.tag_name }}
releaseName: ${{ matrix.gpu_variant == true && format('desktop-gpu{0} v{1}', needs.metadata.outputs.release_suffix, needs.metadata.outputs.version) || format('desktop{0} v{1}', needs.metadata.outputs.release_suffix, needs.metadata.outputs.version) }}
releaseBody: ${{ needs.metadata.outputs.release_body }}
releaseDraft: false
prerelease: ${{ needs.metadata.outputs.release_env != 'prod' }}
includeUpdaterJson: true
- name: Azure login for code signing
if: matrix.os == 'windows-latest'
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sign Windows NSIS installer
if: matrix.os == 'windows-latest'
uses: azure/trusted-signing-action@v0
with:
endpoint: https://eus.codesigning.azure.net/
trusted-signing-account-name: ${{ vars.AZURE_CODE_SIGNING_NAME }}
certificate-profile-name: ${{ vars.AZURE_CERT_PROFILE_NAME }}
files-folder: D:\cargo\release\bundle\nsis
files-folder-filter: exe
- name: Verify NSIS installer signature
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
$nsisDir = "D:\cargo\release\bundle\nsis"
$installers = Get-ChildItem -Path $nsisDir -Filter "*.exe"
foreach ($installer in $installers) {
Write-Host "Verifying signature for: $($installer.Name)"
$sig = Get-AuthenticodeSignature -FilePath $installer.FullName
if ($sig.Status -ne "Valid") {
Write-Error "Signature verification failed for $($installer.Name): $($sig.Status)"
Write-Error "Status Message: $($sig.StatusMessage)"
exit 1
}
Write-Host " Status: $($sig.Status)"
Write-Host " Signer: $($sig.SignerCertificate.Subject)"
Write-Host " Timestamp: $($sig.TimeStamperCertificate.Subject)"
}
Write-Host "All NSIS installers signed successfully"
- name: Re-upload signed NSIS installer
if: matrix.os == 'windows-latest'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ needs.metadata.outputs.tag_name }}
run: |
$nsisDir = "D:\cargo\release\bundle\nsis"
$installers = Get-ChildItem -Path $nsisDir -Filter "*.exe"
foreach ($installer in $installers) {
Write-Host "Uploading signed installer: $($installer.Name)"
gh release upload $env:RELEASE_TAG $installer.FullName --clobber
}
- name: Build Windows portable installer
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
$nsisDir = "D:\cargo\release\bundle\nsis"
$nsisInstaller = Get-ChildItem -Path $nsisDir -Filter "*-setup.exe" | Select-Object -First 1
if (-not $nsisInstaller) {
Write-Error "Could not find NSIS installer in $nsisDir"
exit 1
}
Write-Host "Found NSIS installer: $($nsisInstaller.FullName)"
$destDir = "apps/windows-installer/src-tauri/installer"
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
Copy-Item $nsisInstaller.FullName "$destDir/Voquill_Setup.exe"
Write-Host "Building portable Windows installer..."
Set-Location apps/windows-installer
npm install
npm run tauri:build
- name: Sign Windows portable installer
if: matrix.os == 'windows-latest'
uses: azure/trusted-signing-action@v0
with:
endpoint: https://eus.codesigning.azure.net/
trusted-signing-account-name: ${{ vars.AZURE_CODE_SIGNING_NAME }}
certificate-profile-name: ${{ vars.AZURE_CERT_PROFILE_NAME }}
files-folder: D:\cargo\release
files-folder-filter: exe
file-digest: SHA256
- name: Verify portable installer signature
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
$installerExe = "D:\cargo\release\voquill-installer.exe"
Write-Host "Verifying signature for portable installer..."
$sig = Get-AuthenticodeSignature -FilePath $installerExe
if ($sig.Status -ne "Valid") {
Write-Error "Signature verification failed: $($sig.Status)"
Write-Error "Status Message: $($sig.StatusMessage)"
exit 1
}
Write-Host " Status: $($sig.Status)"
Write-Host " Signer: $($sig.SignerCertificate.Subject)"
Write-Host " Timestamp: $($sig.TimeStamperCertificate.Subject)"
Write-Host "Portable installer signed successfully"
- name: Upload Windows portable installer
if: matrix.os == 'windows-latest'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ matrix.gpu_variant == true && needs.metadata.outputs.gpu_tag_name || needs.metadata.outputs.tag_name }}
run: |
$installerExe = "D:\cargo\release\voquill-installer.exe"
if (-not (Test-Path $installerExe)) {
Write-Error "Could not find portable installer executable at $installerExe"
exit 1
}
$destName = "Voquill_Portable_Installer.exe"
Copy-Item $installerExe $destName
Write-Host "Uploading $destName to release $env:RELEASE_TAG"
gh release upload $env:RELEASE_TAG $destName --clobber
update-channel:
name: Update channel release (${{ matrix.variant }})
needs: [metadata, build]
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
matrix:
include:
- variant: cpu
release_tag: ${{ needs.metadata.outputs.tag_name }}
channel_tag: ${{ needs.metadata.outputs.channel_release_tag }}
- variant: gpu
release_tag: ${{ needs.metadata.outputs.gpu_tag_name }}
channel_tag: ${{ needs.metadata.outputs.gpu_channel_release_tag }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CHANNEL_TAG: ${{ matrix.channel_tag }}
RELEASE_TAG: ${{ matrix.release_tag }}
steps:
- name: Download latest.json from release
run: |
set -euo pipefail
mkdir -p dist
gh release download "$RELEASE_TAG" --pattern "latest.json" --dir dist --clobber
if [[ ! -s dist/latest.json ]]; then
echo "latest.json was not downloaded from release ${RELEASE_TAG}" >&2
ls -la dist
exit 1
fi
- name: Validate downloaded latest.json
run: |
set -euo pipefail
version="$(jq -r '.version // empty' dist/latest.json)"
expected="${{ needs.metadata.outputs.version }}"
if [[ -z "${version}" ]]; then
echo "latest.json is missing a 'version' field." >&2
exit 1
fi
if [[ "${version}" != "${expected}" ]]; then
echo "latest.json version '${version}' does not match expected '${expected}'." >&2
exit 1
fi
- name: Upload to channel
run: |
set -euo pipefail
gh release upload "$CHANNEL_TAG" dist/latest.json --clobber