name: Develop & PR – Build, Test, and Container Publish on: push: branches: - develop pull_request: branches: - '**' jobs: migration-check: name: Flyway Migration Check uses: ./.github/workflows/migrations-check.yml with: base_ref: 'develop' head_ref: 'HEAD' backend-tests: name: Backend Tests needs: [ migration-check ] if: needs.migration-check.result == 'success' || needs.migration-check.result == 'skipped' runs-on: ubuntu-latest permissions: contents: read checks: write pull-requests: write steps: - name: Checkout Repository uses: actions/checkout@v6 - name: Set Up JDK 25 uses: actions/setup-java@v5 with: java-version: '25' distribution: 'temurin' cache: gradle - name: Execute Backend Tests id: backend_tests working-directory: ./booklore-api run: | echo "Running backend tests..." ./gradlew test --no-daemon --parallel --build-cache continue-on-error: true - name: Publish Backend Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: files: booklore-api/build/test-results/**/*.xml check_name: Backend Test Results - name: Upload Backend Test Reports uses: actions/upload-artifact@v6 if: always() with: name: backend-test-reports path: | booklore-api/build/reports/tests/ booklore-api/build/test-results/ retention-days: 30 - name: Validate Backend Test Results if: steps.backend_tests.outcome == 'failure' run: | echo "❌ Backend tests failed" exit 1 frontend-tests: name: Frontend Tests needs: [ migration-check ] if: needs.migration-check.result == 'success' || needs.migration-check.result == 'skipped' runs-on: ubuntu-latest permissions: contents: read checks: write pull-requests: write steps: - name: Checkout Repository uses: actions/checkout@v6 - name: Set Up Node.js uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' cache-dependency-path: booklore-ui/package-lock.json - name: Install Frontend Dependencies working-directory: ./booklore-ui run: npm ci --force - name: Execute Frontend Tests id: frontend_tests working-directory: ./booklore-ui run: | echo "Running frontend tests..." npx ng test continue-on-error: true - name: Publish Frontend Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: files: booklore-ui/test-results/vitest-results.xml check_name: Frontend Test Results - name: Upload Frontend Test Reports uses: actions/upload-artifact@v6 if: always() with: name: frontend-test-reports path: | booklore-ui/test-results/vitest-results.xml retention-days: 30 - name: Validate Frontend Test Results if: steps.frontend_tests.outcome == 'failure' run: | echo "❌ Frontend tests failed" exit 1 build-and-push: name: Build and Push Container needs: [ backend-tests, frontend-tests ] runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout Repository uses: actions/checkout@v6 with: fetch-depth: 0 # ---------------------------------------- # Image tagging # ---------------------------------------- - name: Generate Image Tag id: set_image_tag run: | short_sha=$(git rev-parse --short HEAD) if [ "${{ github.event_name }}" = "pull_request" ]; then image_tag="pr-${{ github.event.pull_request.number }}-${short_sha}" else image_tag="develop-${short_sha}" fi echo "image_tag=$image_tag" >> $GITHUB_ENV echo "Image tag: $image_tag" # ---------------------------------------- # Native Angular build # ---------------------------------------- - name: Set Up Node.js uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' cache-dependency-path: booklore-ui/package-lock.json - name: Install Frontend Dependencies working-directory: ./booklore-ui run: npm ci --force - name: Build Angular App working-directory: ./booklore-ui run: npx ng build --configuration=production # ---------------------------------------- # Native Gradle build # ---------------------------------------- - name: Set Up JDK 25 uses: actions/setup-java@v5 with: java-version: '25' distribution: 'temurin' cache: gradle - name: Copy Angular Dist to Spring Boot Resources run: cp -r booklore-ui/dist/booklore/browser/. booklore-api/src/main/resources/static/ - name: Inject Version into application.yaml env: APP_VERSION: ${{ env.image_tag }} run: | sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.52.2/yq_linux_amd64 sudo chmod +x /usr/local/bin/yq yq eval '.app.version = strenv(APP_VERSION)' -i booklore-api/src/main/resources/application.yaml - name: Build Spring Boot JAR working-directory: ./booklore-api run: ./gradlew clean build -x test --no-daemon --parallel --build-cache # ---------------------------------------- # Environment setup # ---------------------------------------- - name: Set Up QEMU for Multi-Arch Builds uses: docker/setup-qemu-action@v3 - name: Set Up Docker Buildx uses: docker/setup-buildx-action@v3 # ---------------------------------------- # Docker login (pushes & internal PRs only) # ---------------------------------------- - name: Authenticate to Docker Hub if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Authenticate to GitHub Container Registry if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} # ---------------------------------------- # Docker build & push (push + internal PRs) # ---------------------------------------- - name: Build and push Docker image if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository uses: docker/build-push-action@v6 with: context: . file: Dockerfile.ci platforms: linux/amd64,linux/arm64 push: true tags: | booklore/booklore:${{ env.image_tag }} ghcr.io/booklore-app/booklore:${{ env.image_tag }} build-args: | APP_VERSION=${{ env.image_tag }} APP_REVISION=${{ github.sha }} cache-from: | type=gha type=registry,ref=ghcr.io/booklore-app/booklore:buildcache cache-to: | type=gha,mode=max type=registry,ref=ghcr.io/booklore-app/booklore:buildcache,mode=max