Skip to content

Release

Release #18

Workflow file for this run

name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: 'Tag to release (e.g. v0.1.0)'
required: true
permissions: {}
env:
TAG: ${{ github.event.inputs.tag || github.ref_name }}
jobs:
# ── Prepare version info ──────────────────────────────────
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
electrobun-env: ${{ steps.version.outputs.electrobun_env }}
is-prerelease: ${{ steps.version.outputs.is_prerelease }}
npm-tag: ${{ steps.version.outputs.npm_tag }}
steps:
- name: Parse version from tag
id: version
run: |
TAG="${{ env.TAG }}"
VERSION="${TAG#v}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
if [[ "$TAG" == *"-beta"* ]] || [[ "$TAG" == *"-alpha"* ]] || [[ "$TAG" == *"-rc"* ]]; then
echo "electrobun_env=canary" >> "$GITHUB_OUTPUT"
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
echo "npm_tag=beta" >> "$GITHUB_OUTPUT"
else
echo "electrobun_env=stable" >> "$GITHUB_OUTPUT"
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "npm_tag=latest" >> "$GITHUB_OUTPUT"
fi
# ── Desktop builds (Electrobun) ───────────────────────────
desktop:
needs: prepare
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux-x64
artifact: dotaz-linux-x64
- os: ubuntu-24.04-arm
platform: linux-arm64
artifact: dotaz-linux-arm64
- os: macos-14
platform: macos-arm64
artifact: dotaz-macos-arm64
- os: macos-15-large
platform: macos-x64
artifact: dotaz-macos-x64
- os: windows-latest
platform: win-x64
artifact: dotaz-win-x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build frontend
run: bunx vite build
- name: Build desktop app
run: bunx electrobun build --env=${{ needs.prepare.outputs.electrobun-env }}
- name: Extract app from Electrobun installer (Linux)
if: runner.os == 'Linux'
run: |
BUILD_DIR="build/${{ needs.prepare.outputs.electrobun-env }}-${{ matrix.platform }}"
APP_DIR=$(find "$BUILD_DIR" -maxdepth 1 -type d -name "Dotaz*" | head -1)
ARCHIVE=$(find "$APP_DIR/Resources" -name "*.tar.zst" 2>/dev/null | head -1)
if [ -n "$ARCHIVE" ]; then
echo "Extracting app from installer archive: $ARCHIVE"
EXTRACT_DIR=$(mktemp -d)
tar --zstd -xf "$ARCHIVE" -C "$EXTRACT_DIR"
rm -rf "$APP_DIR"
EXTRACTED=$(find "$EXTRACT_DIR" -maxdepth 1 -type d -name "Dotaz*" | head -1)
mv "$EXTRACTED" "$APP_DIR"
rm -rf "$EXTRACT_DIR"
fi
- name: Extract app from Electrobun installer (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$buildDir = "build/${{ needs.prepare.outputs.electrobun-env }}-${{ matrix.platform }}"
$appDir = Get-ChildItem -Path $buildDir -Directory -Filter "Dotaz*" | Select-Object -First 1
$archive = Get-ChildItem -Path "$($appDir.FullName)\Resources" -Filter "*.tar.zst" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($archive) {
Write-Host "Extracting app from installer archive: $($archive.FullName)"
$extractDir = Join-Path ([System.IO.Path]::GetTempPath()) "dotaz-extract-$(Get-Random)"
New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
tar --zstd -xf $archive.FullName -C $extractDir
Remove-Item -Recurse -Force $appDir.FullName
$extracted = Get-ChildItem -Path $extractDir -Directory -Filter "Dotaz*" | Select-Object -First 1
Move-Item -Path $extracted.FullName -Destination $appDir.FullName
Remove-Item -Recurse -Force $extractDir
}
- name: Embed icon into Windows executables
if: runner.os == 'Windows'
shell: pwsh
run: |
$buildDir = "build/${{ needs.prepare.outputs.electrobun-env }}-${{ matrix.platform }}"
$appDir = Get-ChildItem -Path $buildDir -Directory -Filter "Dotaz*" | Select-Object -First 1
$iconPng = Resolve-Path "assets\icon.png"
# Install rcedit and png-to-ico in isolated temp dir (avoids workspace:* conflict)
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "icon-tools"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
Push-Location $tempDir
npm init -y | Out-Null
npm install rcedit png-to-ico --silent
Pop-Location
# Convert PNG to ICO
$iconIco = Join-Path $buildDir "app-icon.ico"
node -e "const m=require('$($tempDir -replace '\\','/')/node_modules/png-to-ico');const p=m.default||m;const fs=require('fs');p('$($iconPng -replace '\\','/')').then(b=>fs.writeFileSync('$($iconIco -replace '\\','/')',b))"
# Embed icon into executables and copy .ico for shortcuts
$rcedit = Join-Path $tempDir "node_modules\rcedit\bin\rcedit-x64.exe"
& $rcedit "$($appDir.FullName)\bin\launcher.exe" --set-icon $iconIco
& $rcedit "$($appDir.FullName)\bin\bun.exe" --set-icon $iconIco
Copy-Item $iconIco "$($appDir.FullName)\Resources\app.ico"
Write-Host "Icon embedded into launcher.exe and bun.exe"
- name: Package artifact (Unix)
if: runner.os != 'Windows'
run: |
BUILD_DIR="build/${{ needs.prepare.outputs.electrobun-env }}-${{ matrix.platform }}"
tar -czf ${{ matrix.artifact }}.tar.gz -C "$BUILD_DIR" .
- name: Package artifact (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$buildDir = "build/${{ needs.prepare.outputs.electrobun-env }}-${{ matrix.platform }}"
Compress-Archive -Path "$buildDir\*" -DestinationPath "${{ matrix.artifact }}.zip"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: |
${{ matrix.artifact }}.tar.gz
${{ matrix.artifact }}.zip
if-no-files-found: error
- name: Upload update artifacts
if: hashFiles('artifacts/*') != ''
uses: actions/upload-artifact@v4
with:
name: update-${{ matrix.platform }}
path: artifacts/*
# ── Docker image ──────────────────────────────────────────
docker:
needs: prepare
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- 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: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ needs.prepare.outputs.is-prerelease == 'false' }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ── npm package (trusted publisher — no NPM_TOKEN needed) ─
npm:
needs: prepare
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: '24.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build server package
run: bun run build:server
env:
VERSION: ${{ needs.prepare.outputs.version }}
- name: Publish to npm
run: npm publish --tag ${{ needs.prepare.outputs.npm-tag }} --access public --provenance
working-directory: dist-server
# ── GitHub Release ────────────────────────────────────────
release:
needs: [prepare, desktop, docker, npm]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download all desktop artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: dotaz-*
merge-multiple: true
- name: Download update artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: update-*
merge-multiple: true
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG }}
name: Dotaz ${{ needs.prepare.outputs.version }}
prerelease: ${{ needs.prepare.outputs.is-prerelease == 'true' }}
generate_release_notes: true
files: |
artifacts/*