Skip to content

Release

Release #44

Workflow file for this run

name: LibreFolio Release
run-name: Release ${{ github.event.release.tag_name }}
on:
push:
branches:
- dev
workflow_dispatch:
release:
types: [published]
permissions:
contents: write # Required to push to the gh-pages branch with mkdocs gh-deploy
packages: write # Required to push images to GHCR
jobs:
release-pipeline:
name: Full Release Pipeline
runs-on: ubuntu-latest
steps:
# ==========================================
# 1. BASE SETUP (Checkout & Environments)
# ==========================================
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # Required for mkdocs gh-deploy (history and branches)
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Install Pipenv
run: pip install pipenv
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '24'
# ==========================================
# 2. CACHES
# ==========================================
- name: Cache Pipenv
uses: actions/cache@v5
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-${{ hashFiles('**/Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Cache NPM
uses: actions/cache@v5
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# For Playwright we use a fixed cache per version
- name: Get Playwright version (from package.json)
id: playwright-version
working-directory: ./frontend
run: echo "PLAYWRIGHT_VERSION=$(node -p "require('./package.json').devDependencies['@playwright/test'].replace(/[\^~]/g, '')" || echo 'fallback')" >> $GITHUB_ENV
- name: Cache Playwright Browsers
uses: actions/cache@v5
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}
# ==========================================
# 3. INSTALLATION & ASSETS BUILD
# ==========================================
- name: Install Python Dependencies
run: pipenv install --dev
- name: "[INFO] Environment summary before install"
run: |
pwd
ls -la
node -v
npm -v
python --version
pipenv --version
git log -1 --oneline
git status --short
node -p "require('./package.json').devDependencies?.['@playwright/test'] || require('./package.json').dependencies?.['@playwright/test'] || 'None'" || true
node -p "require('./frontend/package.json').devDependencies?.['@playwright/test'] || require('./frontend/package.json').dependencies?.['@playwright/test'] || 'None'" || true
ls -la ~/.cache/ms-playwright || true
find ~/.cache/ms-playwright -maxdepth 2 -type d | sort || true
- name: Install Frontend & Playwright Dependencies (via dev.py)
timeout-minutes: 15
run: pipenv run ./dev.py install
- name: "[INFO] Playwright installation check"
run: |
npm --prefix frontend exec -- playwright --version || true
- name: Build SvelteKit Frontend
# Essential for the gallery and the Dockerfile which COPYs frontend/build/
run: pipenv run ./dev.py front build
- name: Check MkDocs translation structure consistency
continue-on-error: ${{ github.ref_name == 'dev' }}
run: pipenv run ./dev.py mkdocs translate-diff --issues-only
- name: Validate broken MkDocs translation links
continue-on-error: ${{ github.ref_name == 'dev' }}
run: pipenv run ./dev.py mkdocs translate-validate --hide-localized
- name: Validate cross-boundary links
continue-on-error: ${{ github.ref_name == 'dev' }}
run: pipenv run ./dev.py mkdocs check-links
- name: Generate Screenshots with Playwright
continue-on-error: ${{ github.ref_name == 'dev' }}
timeout-minutes: 120
# ubuntu-latest (public repo) runners have 4 vCPU. --workers 8 was 2x
# oversubscribed, causing heavy CPU contention across Chromium instances
# + backend uvicorn workers + node build processes — this was the root
# cause of inconsistent per-test timeouts (a different test would time
# out on each run). Matched to actual CPU count for stable, predictable
# per-test timing (verified locally: --workers 2-3 gives a constant,
# non-degrading ~21s/interaction rate with zero flakiness).
run: pipenv run ./dev.py mkdocs gallery --workers 4
- name: Build MkDocs Documentation
continue-on-error: ${{ github.ref_name == 'dev' }}
run: pipenv run ./dev.py mkdocs build
- name: Generate requirements.txt for Docker
# The Dockerfile installs dependencies from a native requirements.txt
run: pipenv requirements > requirements.txt
- name: Generate VERSION file for Docker
# The Dockerfile COPYs a VERSION file (gitignored build artifact) so the
# image can report its version at runtime — the image has no .git/, so
# git describe cannot run there. Freeze it here on the host (fetch-depth:0
# gives us tags) reusing the same get_git_version() the app uses.
run: pipenv run python -c "from pathlib import Path; from backend.app.utils.version import get_git_version; get_git_version.cache_clear(); Path('VERSION').write_text(get_git_version())"
# ==========================================
# 4. DOCKER BUILD & PUSH (Single Image)
# ==========================================
- name: Setup QEMU
uses: docker/setup-qemu-action@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v4
- name: GHCR Authentication
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker Metadata (Version)
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/librefolio/librefolio
tags: |
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
type=raw,value=nightly,enable=${{ github.ref_name == 'dev' }}
labels: |
org.opencontainers.image.title=LibreFolio
org.opencontainers.image.description=LibreFolio: Open Source Personal Finance & Portfolio Tracker
- name: Build and Push Docker Image
uses: docker/build-push-action@v7
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ==========================================
# 5. GITHUB PAGES DEPLOY
# ==========================================
- name: Setup Git User
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Deploy to gh-pages branch
if: github.ref_name == 'main' || github.event_name == 'release'
run: pipenv run ./dev.py mkdocs deploy
# ==========================================
# 6. ARTIFACTS (Optional, for archival)
# ==========================================
- name: Archive Generated Screenshots
uses: actions/upload-artifact@v6
with:
name: playwright-screenshots
path: |
mkdocs_src/docs/gallery/**/*.png
retention-days: 1
# ==========================================
# 7. RELEASE NOTES UPDATE
# ==========================================
- name: Append Docker info to Release Notes
if: github.event.release.tag_name != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="${{ github.event.release.tag_name }}"
DOCKER_CMD=$(printf '\n\n## 📦 Docker Installation\n\n```bash\n# Install the latest version\ndocker pull ghcr.io/librefolio/librefolio:latest\n\n# Or install this specific release\ndocker pull ghcr.io/librefolio/librefolio:%s\n```\n' "$TAG_NAME")
# gh CLI has no --notes-append flag: fetch current notes and append manually
CURRENT_NOTES=$(gh release view "$TAG_NAME" --json body -q .body)
gh release edit "$TAG_NAME" --notes "${CURRENT_NOTES}${DOCKER_CMD}"