Skip to content

Release

Release #125

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
release_type:
description: "Release type"
type: choice
options:
- patch
- minor
- major
- nightly
- custom
default: patch
custom_version:
description: "Custom semver (used only when release_type=custom)"
required: false
type: string
prerelease:
description: "Mark as prerelease"
required: false
default: false
type: boolean
schedule:
- cron: "0 7 * * *"
jobs:
check-changes:
runs-on: ubuntu-22.04
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for same-day commits (UTC)
id: check
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "Manual dispatch — bypassing change-detection, proceeding with release."
echo "has_changes=true" >> "$GITHUB_OUTPUT"
exit 0
fi
TODAY_UTC=$(date -u +%Y-%m-%dT00:00:00Z)
COMMIT_COUNT=$(git log --oneline --after="$TODAY_UTC" | wc -l | tr -d ' ')
if [[ "$COMMIT_COUNT" -gt 0 ]]; then
echo "Found ${COMMIT_COUNT} commit(s) since ${TODAY_UTC} — proceeding with nightly release."
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
echo "No commits since ${TODAY_UTC} — skipping nightly release (no same-day changes)."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
fi
release:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
permissions:
contents: write
pull-requests: write
strategy:
fail-fast: false
matrix:
platform: [ubuntu-22.04, macos-15]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: Compute release version
id: version
shell: bash
run: |
set -euo pipefail
CURRENT_VERSION=$(node -p "require('./package.json').version")
if [[ "${{ github.event_name }}" == "schedule" ]]; then
RELEASE_TYPE="nightly"
PRERELEASE="true"
else
RELEASE_TYPE="${{ github.event.inputs.release_type }}"
PRERELEASE="${{ github.event.inputs.prerelease }}"
fi
if [[ "$RELEASE_TYPE" == "custom" ]]; then
VERSION="${{ github.event.inputs.custom_version }}"
if [[ -z "$VERSION" ]]; then
echo "custom_version is required when release_type=custom"
exit 1
fi
elif [[ "$RELEASE_TYPE" == "nightly" ]]; then
MAJOR=$(echo "$CURRENT_VERSION" | cut -d. -f1)
MINOR=$(echo "$CURRENT_VERSION" | cut -d. -f2)
PATCH=$(echo "$CURRENT_VERSION" | cut -d. -f3)
NIGHTLY_DATE=$(date -u +%Y%m%d)
VERSION="${MAJOR}.${MINOR}.${PATCH}-nightly.${NIGHTLY_DATE}.${GITHUB_RUN_NUMBER}"
else
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
if [[ -z "${MAJOR:-}" || -z "${MINOR:-}" || -z "${PATCH:-}" ]]; then
echo "Current version must be semver core x.y.z, got: $CURRENT_VERSION"
exit 1
fi
if ! [[ "$MAJOR" =~ ^[0-9]+$ && "$MINOR" =~ ^[0-9]+$ && "$PATCH" =~ ^[0-9]+$ ]]; then
echo "Current version must be numeric semver core x.y.z, got: $CURRENT_VERSION"
exit 1
fi
case "$RELEASE_TYPE" in
patch) VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;;
minor) VERSION="${MAJOR}.$((MINOR + 1)).0" ;;
major) VERSION="$((MAJOR + 1)).0.0" ;;
*)
echo "Unsupported release_type: $RELEASE_TYPE"
exit 1
;;
esac
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT"
echo "release_type=$RELEASE_TYPE" >> "$GITHUB_OUTPUT"
- name: Apply version across project files
shell: bash
run: |
set -euo pipefail
VERSION='${{ steps.version.outputs.version }}'
node -e "
const fs = require('node:fs');
const version = process.argv[1];
const packageJsonPath = 'package.json';
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
packageJson.version = version;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n');
const tauriConfigPath = 'src-tauri/tauri.conf.json';
const tauriConfig = JSON.parse(fs.readFileSync(tauriConfigPath, 'utf8'));
tauriConfig.version = version;
fs.writeFileSync(tauriConfigPath, JSON.stringify(tauriConfig, null, 2) + '\\n');
" "$VERSION"
perl -0pi -e "s/^version = \"[^\"]+\"/version = \"$VERSION\"/m" src-tauri/Cargo.toml
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- run: rustup toolchain install stable --profile minimal
- uses: Swatinem/rust-cache@v2
with:
prefix-key: "v1-release-rust"
workspaces: "src-tauri"
- name: (Ubuntu only) Install dependencies
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install node packages
run: bun install
- name: (macOS only) Prepare Apple notarization key
if: matrix.platform == 'macos-15'
shell: bash
env:
APPLE_API_KEY_SECRET: ${{ secrets.APPLE_API_KEY }}
run: |
set -euo pipefail
if [[ -z "${APPLE_API_KEY_SECRET}" ]]; then
echo "Missing required secret: APPLE_API_KEY"
exit 1
fi
KEY_FILE="$RUNNER_TEMP/apple_api_key.p8"
# Support APPLE_API_KEY being stored either as raw .p8 contents
# or base64-encoded .p8 contents.
if echo "${APPLE_API_KEY_SECRET}" | base64 --decode > "$KEY_FILE" 2>/dev/null \
&& grep -q "BEGIN PRIVATE KEY" "$KEY_FILE"; then
echo "Using base64-decoded APPLE_API_KEY secret."
else
printf "%s" "${APPLE_API_KEY_SECRET}" > "$KEY_FILE"
if ! grep -q "BEGIN PRIVATE KEY" "$KEY_FILE"; then
echo "APPLE_API_KEY is neither valid base64 .p8 nor raw .p8 content."
exit 1
fi
echo "Using raw APPLE_API_KEY secret."
fi
{
echo "APPLE_API_KEY_PATH=$KEY_FILE"
} >> "$GITHUB_ENV"
- name: Build and publish release assets
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.CITADEL_RELEASE_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_KEY_PATH: ${{ env.APPLE_API_KEY_PATH }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
with:
tauriScript: bun tauri
args: --config '{"bundle":{"createUpdaterArtifacts":true}}'
tagName: v${{ steps.version.outputs.version }}
releaseName: Citadel v${{ steps.version.outputs.version }}
releaseBody: |
Automated ${{ steps.version.outputs.release_type }} release.
Includes updater metadata (`latest.json`) for Tauri updater clients.
prerelease: ${{ steps.version.outputs.prerelease == 'true' }}
generateReleaseNotes: true
# Must run after the build: create-pull-request resets the working tree
# to the base branch, which would strip the applied version bump and
# produce artifacts built with the old version.
- name: Open version bump pull request
if: github.event_name == 'workflow_dispatch' && matrix.platform == 'ubuntu-22.04' && steps.version.outputs.release_type != 'nightly'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.CITADEL_RELEASE_TOKEN }}
commit-message: "chore(release): bump version to v${{ steps.version.outputs.version }}"
branch: chore/release-bump-v${{ steps.version.outputs.version }}
delete-branch: true
title: "chore(release): bump version to v${{ steps.version.outputs.version }}"
body: |
Automated release version bump generated by the Release workflow.
Files updated:
- package.json
- src-tauri/Cargo.toml
- src-tauri/tauri.conf.json
add-paths: |
package.json
src-tauri/Cargo.toml
src-tauri/tauri.conf.json