name: Release on: push: tags: - "v*" # e.g. v1.0.0, v1.2.3 concurrency: group: ${{ gitea.workflow }}-${{ gitea.ref }} cancel-in-progress: true jobs: # ── backend: vet & test ─────────────────────────────────────────────────────── test-backend: name: Test backend runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: backend/go.mod cache-dependency-path: backend/go.sum - name: go vet working-directory: backend run: go vet ./... - name: Run tests working-directory: backend run: go test -short -race -count=1 -timeout=60s ./... # ── ui: type-check & build ──────────────────────────────────────────────────── check-ui: name: Check ui runs-on: ubuntu-latest defaults: run: working-directory: ui steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "22" cache: npm cache-dependency-path: ui/package-lock.json - name: Install dependencies run: npm ci - name: Type check run: npm run check - name: Build run: npm run build - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: ui-build path: ui/build retention-days: 1 # ── docker: backend ─────────────────────────────────────────────────────────── docker-backend: name: Docker / backend runs-on: ubuntu-latest needs: [test-backend] steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ${{ secrets.DOCKER_USER }}/libnovel-backend tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest - name: Build and push uses: docker/build-push-action@v6 with: context: backend target: backend push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | VERSION=${{ steps.meta.outputs.version }} COMMIT=${{ gitea.sha }} cache-from: type=registry,ref=${{ secrets.DOCKER_USER }}/libnovel-backend:latest cache-to: type=inline # ── docker: runner ──────────────────────────────────────────────────────────── docker-runner: name: Docker / runner runs-on: ubuntu-latest needs: [test-backend] steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ${{ secrets.DOCKER_USER }}/libnovel-runner tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest - name: Build and push uses: docker/build-push-action@v6 with: context: backend target: runner push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | VERSION=${{ steps.meta.outputs.version }} COMMIT=${{ gitea.sha }} cache-from: type=registry,ref=${{ secrets.DOCKER_USER }}/libnovel-runner:latest cache-to: type=inline # ── ui: source map upload ───────────────────────────────────────────────────── upload-sourcemaps: name: Upload source maps runs-on: ubuntu-latest needs: [check-ui] steps: - name: Compute release version (strip leading v) id: ver run: | V="${{ gitea.ref_name }}" echo "version=${V#v}" >> "$GITHUB_OUTPUT" - name: Download build artifacts uses: actions/download-artifact@v3 with: name: ui-build path: build - name: Install sentry-cli run: npm install -g @sentry/cli - name: Inject debug IDs into build artifacts run: sentry-cli sourcemaps inject ./build env: SENTRY_URL: https://errors.libnovel.cc/ SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }} SENTRY_ORG: libnovel SENTRY_PROJECT: ui - name: Upload injected build (for docker-ui) uses: actions/upload-artifact@v3 with: name: ui-build-injected path: build retention-days: 1 - name: Create GlitchTip release run: sentry-cli releases new ${{ steps.ver.outputs.version }} env: SENTRY_URL: https://errors.libnovel.cc/ SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }} SENTRY_ORG: libnovel SENTRY_PROJECT: ui - name: Upload source maps to GlitchTip run: sentry-cli sourcemaps upload ./build --release ${{ steps.ver.outputs.version }} env: SENTRY_URL: https://errors.libnovel.cc/ SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }} SENTRY_ORG: libnovel SENTRY_PROJECT: ui - name: Finalize GlitchTip release run: sentry-cli releases finalize ${{ steps.ver.outputs.version }} env: SENTRY_URL: https://errors.libnovel.cc/ SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }} SENTRY_ORG: libnovel SENTRY_PROJECT: ui - name: Prune old GlitchTip releases (keep latest 10) run: | set -euo pipefail KEEP=10 OLD=$(curl -sf \ -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \ "$SENTRY_URL/api/0/organizations/$SENTRY_ORG/releases/?project=$SENTRY_PROJECT&per_page=100" \ | python3 -c " import sys, json releases = json.load(sys.stdin) for r in releases[$KEEP:]: print(r['version']) " KEEP=$KEEP) for ver in $OLD; do echo "Deleting old release: $ver" sentry-cli releases delete "$ver" || true done env: SENTRY_URL: https://errors.libnovel.cc SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }} SENTRY_ORG: libnovel SENTRY_PROJECT: ui # ── docker: ui ──────────────────────────────────────────────────────────────── docker-ui: name: Docker / ui runs-on: ubuntu-latest needs: [check-ui, upload-sourcemaps] steps: - uses: actions/checkout@v4 - name: Download injected build (debug IDs already embedded) uses: actions/download-artifact@v3 with: name: ui-build-injected path: ui/build - name: Allow build/ into Docker context (override .dockerignore) run: | grep -v '^build$' ui/.dockerignore > ui/.dockerignore.tmp mv ui/.dockerignore.tmp ui/.dockerignore - uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ${{ secrets.DOCKER_USER }}/libnovel-ui tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest - name: Build and push uses: docker/build-push-action@v6 with: context: ui push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | BUILD_VERSION=${{ steps.meta.outputs.version }} BUILD_COMMIT=${{ gitea.sha }} BUILD_TIME=${{ gitea.event.head_commit.timestamp }} PREBUILT=1 cache-from: type=registry,ref=${{ secrets.DOCKER_USER }}/libnovel-ui:latest cache-to: type=inline # ── docker: caddy ───────────────────────────────────────────────────────────── docker-caddy: name: Docker / caddy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ${{ secrets.DOCKER_USER }}/libnovel-caddy tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest - name: Build and push uses: docker/build-push-action@v6 with: context: caddy push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=registry,ref=${{ secrets.DOCKER_USER }}/libnovel-caddy:latest cache-to: type=inline # ── Gitea release ───────────────────────────────────────────────────────────── release: name: Gitea Release runs-on: ubuntu-latest needs: [docker-backend, docker-runner, docker-ui, docker-caddy, upload-sourcemaps] steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Extract release notes from tag commit id: notes run: | set -euo pipefail # Subject line (first line of commit message) → release title SUBJECT=$(git log -1 --format="%s" "${{ gitea.sha }}") # Body (everything after the blank line) → release body BODY=$(git log -1 --format="%b" "${{ gitea.sha }}" | sed '/^Co-Authored-By:/d' | sed '/^[[:space:]]*$/{ N; /^\n$/d }' | sed 's/^[[:space:]]*$//' | awk 'NF || !p; {p = !NF}') echo "title=${SUBJECT}" >> "$GITHUB_OUTPUT" # Use a heredoc delimiter to safely handle multi-line body { echo "body<> "$GITHUB_OUTPUT" - name: Create release uses: https://gitea.com/actions/gitea-release-action@v1 with: token: ${{ secrets.GITEA_TOKEN }} title: ${{ steps.notes.outputs.title }} body: ${{ steps.notes.outputs.body }}