diff --git a/.github/scripts/analyze-changes.sh b/.github/scripts/analyze-changes.sh deleted file mode 100644 index 1665f2244..000000000 --- a/.github/scripts/analyze-changes.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -# Exit immediately if a command exits with a non-zero status. -set -e -# Exit if any command in a pipeline fails, not just the last one. -set -o pipefail - -# Define the path where Flyway migration files are stored. -MIGRATION_PATH="booklore-api/src/main/resources/db/migration" - -# Get ALL changes: Added (A), Modified (M), Renamed (R), Copied (C), Deleted (D) -# for SQL files in the migration path between the comparison ref and the current HEAD. -# The output is saved to a temporary file for further processing. -git diff --name-status --diff-filter=AMRCD $COMPARE_REF...HEAD -- "$MIGRATION_PATH/V*.sql" > /tmp/all_changes.txt - -# The check for no changes is now handled in the workflow. -# If this script runs, it's because changes were detected. - -echo "📝 Migration changes detected:" -# Display the detected changes, indented for readability. -cat /tmp/all_changes.txt | sed 's/^/ /' -echo "" - -# Check for deleted files -# Grep for lines starting with 'D' (Deleted). The '|| true' prevents the script from exiting if no matches are found. -DELETED=$(grep "^D" /tmp/all_changes.txt || true) -if [ -n "$DELETED" ]; then - echo "❌ ERROR: Deleted migration files detected!" - echo "$DELETED" | sed 's/^/ /' - echo "" - echo "Flyway migrations should NEVER be deleted after being applied." - echo "If you need to revert changes, create a new migration." - exit 1 -fi - -# Check for renamed files -# Grep for lines starting with 'R' (Renamed). -RENAMED=$(grep "^R" /tmp/all_changes.txt || true) -if [ -n "$RENAMED" ]; then - echo "❌ ERROR: Renamed migration files detected!" - echo "$RENAMED" | sed 's/^/ /' - echo "" - echo "Flyway migrations should NEVER be renamed after being applied." - echo "This will cause issues with migration history tracking." - echo "" - echo "💡 To fix: Revert the rename and create a new migration file instead." - exit 1 -fi - -# Check for modified files -# Grep for lines starting with 'M' (Modified). -MODIFIED=$(grep "^M" /tmp/all_changes.txt || true) -if [ -n "$MODIFIED" ]; then - echo "❌ ERROR: Modified migration files detected!" - echo "$MODIFIED" | sed 's/^/ /' - echo "" - echo "Flyway migrations should NEVER be modified after being applied." - echo "This will cause checksum validation failures in environments where it has already been applied." - echo "" - echo "💡 To fix: Revert the changes and create a new migration file instead." - exit 1 -fi - -# Extract ADDED files for conflict checking in a later step. -# We grep for lines starting with 'A' (Added), then use 'cut' to get just the file path. -# 'touch' ensures the file exists even if there are no added files. -grep "^A" /tmp/all_changes.txt | cut -f2- > /tmp/pr_files.txt || touch /tmp/pr_files.txt - -# Set a GitHub Actions output variable to indicate that migration changes were found. -# This is used by the workflow to decide whether to run subsequent steps. -echo "has_changes=true" >> $GITHUB_OUTPUT diff --git a/.github/scripts/check-conflicts.sh b/.github/scripts/check-conflicts.sh deleted file mode 100644 index 34c843d5d..000000000 --- a/.github/scripts/check-conflicts.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash -# Exit immediately if a command exits with a non-zero status. -set -e -# Exit if any command in a pipeline fails, not just the last one. -set -o pipefail - -# If there are no new versions to check, exit gracefully. -# This file is created by 'validate-versions.sh'. -# This can happen if a PR has changes, but none are new migration files. -if [ ! -s /tmp/versions_pr_unique.txt ]; then - echo "â„šī¸ No new migration versions to check for conflicts." - exit 0 -fi - -# Define the path where Flyway migration files are stored. -MIGRATION_PATH="booklore-api/src/main/resources/db/migration" - -echo "🔍 Fetching migration files from $COMPARE_REF..." - -# Get ALL existing migration files from the comparison ref (e.g., 'develop' or a tag). -# 'git ls-tree' lists the contents of a tree object. -# The output is piped to grep to filter for only Flyway SQL files. -# '|| touch' ensures the temp file exists even if no files are found. -git ls-tree -r --name-only $COMPARE_REF -- "$MIGRATION_PATH/" 2>/dev/null | \ - grep "V.*\.sql$" > /tmp/base_files.txt || touch /tmp/base_files.txt - -# Handle the case where no migration files exist in the base branch. -if [ ! -s /tmp/base_files.txt ]; then - echo "âš ī¸ No migration files found in $COMPARE_REF" - echo "This might be the first migration or the path has changed." - echo "" - echo "✅ Skipping version conflict check." - - PR_COUNT=$(wc -l < /tmp/versions_pr_unique.txt) - echo "" - echo "📊 Migration Summary:" - echo " - Existing migrations in $COMPARE_REF: 0" - echo " - New migrations in this PR: $PR_COUNT" - exit 0 -fi - -echo "📋 Found $(wc -l < /tmp/base_files.txt) migration files in $COMPARE_REF" - -# Extract versions from the base files. -# The loop reads each file path, extracts the version number from the filename, -# and appends it to a temporary file. -> /tmp/versions_base.txt -while IFS= read -r file; do - filename=$(basename "$file") - # sed extracts the version number (e.g., 1.0.0) from a filename like 'V1.0.0__description.sql'. - version=$(echo "$filename" | sed -n 's/^V\([0-9.]*\)__.*/\1/p') - [ -n "$version" ] && echo "$version" >> /tmp/versions_base.txt -done < /tmp/base_files.txt - -# Create a file with only unique, sorted version numbers from the base. -sort -u /tmp/versions_base.txt > /tmp/versions_base_unique.txt - -BASE_COUNT=$(wc -l < /tmp/versions_base_unique.txt) -echo "📊 Found $BASE_COUNT unique versions in $COMPARE_REF" - -# Find conflicts between base versions and versions from NEW PR files. -# 'comm -12' finds lines common to both sorted files. -CONFLICTS=$(comm -12 /tmp/versions_base_unique.txt /tmp/versions_pr_unique.txt) - -# If conflicts are found, report them and exit with an error. -if [ -n "$CONFLICTS" ]; then - echo "❌ Version conflicts detected!" - echo "" - echo "The following versions from your new migration files already exist in $COMPARE_REF:" - echo "$CONFLICTS" | sed 's/^/ V/' - echo "" - - # Show which files have conflicting versions for easier debugging. - echo "Conflicting files:" - while IFS= read -r version; do - echo " Version V$version exists in:" - grep "V${version}__" /tmp/base_files.txt | xargs -n1 basename | sed 's/^/ BASE: /' - # /tmp/pr_files.txt contains only added files from the PR (from analyze-changes.sh). - grep "V${version}__" /tmp/pr_files.txt | xargs -n1 basename | sed 's/^/ PR: /' - done <<< "$CONFLICTS" - - echo "" - echo "💡 To fix: Use a version number that doesn't exist in $COMPARE_REF" - exit 1 -fi - -echo "✅ No version conflicts detected." - -# Get the count of new migrations in the PR. -PR_COUNT=$(wc -l < /tmp/versions_pr_unique.txt) - -# Print a final summary. -echo "" -echo "📊 Migration Summary:" -echo " - Existing migrations in $COMPARE_REF: $BASE_COUNT" -echo " - New migrations in this PR: $PR_COUNT" diff --git a/.github/scripts/determine-compare-ref.sh b/.github/scripts/determine-compare-ref.sh deleted file mode 100644 index 13f8839e1..000000000 --- a/.github/scripts/determine-compare-ref.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# Exit immediately if a command exits with a non-zero status. -set -e -# Exit if any command in a pipeline fails, not just the last one. -set -o pipefail - -# The target branch of the pull request (e.g., 'develop', 'master') is passed as the first argument. -TARGET_BRANCH="$1" -echo "đŸŽ¯ Target branch: $TARGET_BRANCH" - -# Handle cases where the target branch is not specified, such as a direct push to a branch. -if [ -z "$TARGET_BRANCH" ]; then - echo "âš ī¸ No target branch specified (e.g., a direct push event). Defaulting to compare with 'develop'." - TARGET_BRANCH="develop" -fi - -# Logic to determine the comparison reference based on the target branch. -if [ "$TARGET_BRANCH" = "master" ]; then - # For PRs to 'master', we compare against the latest git tag. - # This is common for release workflows where 'master' only contains tagged releases. - if ! LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null); then - echo "âš ī¸ No tags found in repository. Skipping conflict check." - # Set output to signal the workflow to stop. - echo "has_ref=false" >> $GITHUB_OUTPUT - exit 0 - fi - echo "📌 Comparing against last tag: $LAST_TAG" - # Set the COMPARE_REF environment variable for subsequent steps in the job. - echo "COMPARE_REF=$LAST_TAG" >> $GITHUB_ENV -else - # For all other cases (PRs to 'develop', other feature branches, or direct pushes), - # we compare against the 'develop' branch. - echo "🔄 Comparing against head of develop branch" - # Ensure the local 'develop' branch is up-to-date with the remote. - git fetch origin develop:develop - # Set the COMPARE_REF to the remote develop branch. - echo "COMPARE_REF=origin/develop" >> $GITHUB_ENV -fi - -# Set a GitHub Actions output variable to indicate that a valid comparison ref was found. -echo "has_ref=true" >> $GITHUB_OUTPUT diff --git a/.github/scripts/validate-versions.sh b/.github/scripts/validate-versions.sh deleted file mode 100644 index f53968118..000000000 --- a/.github/scripts/validate-versions.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -# Exit immediately if a command exits with a non-zero status. -set -e -# Exit if any command in a pipeline fails, not just the last one. -set -o pipefail - -# Define the path where Flyway migration files are stored. -MIGRATION_PATH="booklore-api/src/main/resources/db/migration" - -# --- Part 1: Check for duplicate versions within the PR branch itself --- - -# Get ALL migration files in the current HEAD of the PR branch for an internal duplicate check. -find "$MIGRATION_PATH" -type f -name "V*.sql" > /tmp/all_pr_files.txt - -# Check for duplicate versions within the PR branch. This prevents merging a branch -# that contains multiple files with the same version number. -echo "🔎 Checking for duplicate versions in the branch..." -> /tmp/versions_all_pr.txt -# Loop through all found migration files and extract their version numbers. -while IFS= read -r file; do - filename=$(basename "$file") - # sed extracts the version number (e.g., 1.0.0) from a filename like 'V1.0.0__description.sql'. - version=$(echo "$filename" | sed -n 's/^V\([0-9.]*\)__.*/\1/p') - [ -n "$version" ] && echo "$version" >> /tmp/versions_all_pr.txt -done < /tmp/all_pr_files.txt - -# 'uniq -d' filters for lines that appear more than once in the sorted list. -sort /tmp/versions_all_pr.txt | uniq -d > /tmp/duplicates_in_pr.txt - -# If the duplicates file is not empty, report the error and exit. -if [ -s /tmp/duplicates_in_pr.txt ]; then - echo "❌ Duplicate migration versions found within the branch!" - echo "" - echo "The following versions are duplicated:" - while IFS= read -r version; do - echo " - Version V$version is used by:" - # Show the conflicting files for easy debugging. - grep "V${version}__" /tmp/all_pr_files.txt | xargs -n1 basename | sed 's/^/ /' - done < /tmp/duplicates_in_pr.txt - echo "" - echo "💡 To fix: Ensure all migration files have a unique version number." - exit 1 -fi - -echo "✅ No duplicate versions found within the branch." - -# --- Part 2: Extract versions from NEWLY ADDED files for conflict checking against the base branch --- - -# /tmp/pr_files.txt is created by analyze-changes.sh and contains only ADDED files. -# If the file doesn't exist or is empty, there's nothing to check. -if [ ! -f /tmp/pr_files.txt ] || [ ! -s /tmp/pr_files.txt ]; then - echo "â„šī¸ No new migration files to check for conflicts." - # Set output to signal the workflow to skip the conflict check step. - echo "has_versions=false" >> $GITHUB_OUTPUT - exit 0 -fi - -echo "🔎 Extracting versions from new files..." -> /tmp/versions_pr.txt -# Loop through only the NEWLY ADDED files and extract their versions. -while IFS= read -r file; do - filename=$(basename "$file") - version=$(echo "$filename" | sed -n 's/^V\([0-9.]*\)__.*/\1/p') - [ -n "$version" ] && echo "$version" >> /tmp/versions_pr.txt -done < /tmp/pr_files.txt - -# If no valid versions were extracted from the new files, exit. -if [ ! -s /tmp/versions_pr.txt ]; then - echo "â„šī¸ No versions found in new migration files." - echo "has_versions=false" >> $GITHUB_OUTPUT - exit 0 -fi - -# Create a sorted, unique list of versions from the new files. -# This file will be used by 'check-conflicts.sh'. -sort -u /tmp/versions_pr.txt > /tmp/versions_pr_unique.txt - -# Set output to signal that there are new versions to check for conflicts. -echo "has_versions=true" >> $GITHUB_OUTPUT diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 8c081be2f..c3418b8d8 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -9,30 +9,28 @@ on: branches: - '**' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: check-for-migrations: name: Check for DB Migrations if: github.event_name == 'pull_request' && (github.base_ref == 'master' || github.base_ref == 'develop') runs-on: ubuntu-latest outputs: - has_migrations: ${{ steps.check_migrations.outputs.has_migrations }} + has_migrations: ${{ steps.filter.outputs.migrations }} steps: - - name: Checkout Repository for Diff + - name: Checkout Repository uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: Detect Flyway Migration Changes - id: check_migrations - run: | - # Compare PR head with the target base branch - if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "booklore-api/src/main/resources/db/migration/V.*.sql"; then - echo "Migration file changes detected. Proceeding with migration preview." - echo "has_migrations=true" >> $GITHUB_OUTPUT - else - echo "No migration file changes detected. Skipping migration preview." - echo "has_migrations=false" >> $GITHUB_OUTPUT - fi + - name: Detect Migration Changes + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + migrations: + - 'booklore-api/src/main/resources/db/migration/V*.sql' flyway-migration-preview: name: Flyway DB Migration Preview @@ -90,7 +88,7 @@ jobs: test-pr: name: Run Tests on Pull Request needs: [check-for-migrations, flyway-migration-preview] - if: always() && github.event_name == 'pull_request' && (needs.flyway-migration-preview.result == 'success' || needs.flyway-migration-preview.result == 'skipped') + if: github.event_name == 'pull_request' && (needs.flyway-migration-preview.result == 'success' || needs.flyway-migration-preview.result == 'skipped') runs-on: ubuntu-latest permissions: @@ -102,7 +100,18 @@ jobs: - name: Checkout Repository uses: actions/checkout@v6 + - name: Detect Changed Paths + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + backend_or_docker: + - 'booklore-api/**' + - 'Dockerfile' + - 'start.sh' + - name: Set Up JDK 21 + if: steps.filter.outputs.backend_or_docker == 'true' uses: actions/setup-java@v5 with: java-version: '21' @@ -110,6 +119,7 @@ jobs: cache: 'gradle' - name: Execute Backend Tests + if: steps.filter.outputs.backend_or_docker == 'true' id: backend_tests working-directory: ./booklore-api run: | @@ -118,8 +128,8 @@ jobs: continue-on-error: true - name: Publish Backend Test Results + if: always() && steps.backend_tests.conclusion != 'skipped' uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() with: files: booklore-api/build/test-results/**/*.xml check_name: Backend Test Results @@ -128,8 +138,8 @@ jobs: report_suite_logs: 'any' - name: Upload Backend Test Reports + if: always() && steps.backend_tests.conclusion != 'skipped' uses: actions/upload-artifact@v6 - if: always() with: name: test-reports-pr-${{ github.event.pull_request.number }} path: | @@ -143,6 +153,11 @@ jobs: echo "❌ Backend tests failed! Check the test results above." exit 1 + - name: Skip Backend Tests + if: steps.filter.outputs.backend_or_docker == 'false' + run: | + echo "✅ No backend changes detected. Skipping backend tests." + - name: PR Validation Complete run: | echo "✅ All PR validation checks passed!" @@ -163,7 +178,23 @@ jobs: with: fetch-depth: 0 + - name: Detect Changed Paths + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + backend_or_docker: + - 'booklore-api/**' + - 'Dockerfile' + - 'start.sh' + + - name: Skip Build and Push + if: steps.filter.outputs.backend_or_docker == 'false' + run: | + echo "✅ No backend/Docker changes detected. Skipping image build and publish." + - name: Set Up JDK 21 + if: steps.filter.outputs.backend_or_docker == 'true' uses: actions/setup-java@v5 with: java-version: '21' @@ -171,18 +202,21 @@ jobs: cache: 'gradle' - name: Execute Backend Tests + if: steps.filter.outputs.backend_or_docker == 'true' working-directory: ./booklore-api run: | echo "Running backend tests before building image..." ./gradlew test --no-daemon --parallel --build-cache - - name: Authenticate to Docker Hub + - name: Authenticate to Docker Registries + if: steps.filter.outputs.backend_or_docker == 'true' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Authenticate to GitHub Container Registry + if: steps.filter.outputs.backend_or_docker == 'true' uses: docker/login-action@v3 with: registry: ghcr.io @@ -190,12 +224,15 @@ jobs: password: ${{ github.token }} - name: Set Up QEMU for Multi-Architecture Builds + if: steps.filter.outputs.backend_or_docker == 'true' uses: docker/setup-qemu-action@v3 - name: Set Up Docker Buildx + if: steps.filter.outputs.backend_or_docker == 'true' uses: docker/setup-buildx-action@v3 - name: Retrieve Latest Master Version Tag + if: steps.filter.outputs.backend_or_docker == 'true' id: get_version run: | latest_tag=$(git tag --list "v*" --sort=-v:refname | head -n 1) @@ -204,7 +241,7 @@ jobs: echo "Latest master tag: $latest_tag" - name: Determine Version Bump (Master Only) - if: github.ref == 'refs/heads/master' + if: steps.filter.outputs.backend_or_docker == 'true' && github.ref == 'refs/heads/master' id: determine_bump env: GH_TOKEN: ${{ github.token }} @@ -260,6 +297,7 @@ jobs: echo "new_tag=$next_version" >> $GITHUB_ENV - name: Generate Image Tag + if: steps.filter.outputs.backend_or_docker == 'true' id: set_image_tag run: | branch="${GITHUB_REF#refs/heads/}" @@ -275,7 +313,8 @@ jobs: echo "image_tag=$image_tag" >> $GITHUB_ENV echo "Image tag: $image_tag" - - name: Build and push Docker image + - name: Build and Push Docker Images + if: steps.filter.outputs.backend_or_docker == 'true' uses: docker/build-push-action@v6 with: context: . @@ -295,7 +334,7 @@ jobs: type=registry,ref=ghcr.io/booklore-app/booklore:buildcache,mode=max - name: Push Latest Tag (Master Only) - if: github.ref == 'refs/heads/master' + if: steps.filter.outputs.backend_or_docker == 'true' && github.ref == 'refs/heads/master' uses: docker/build-push-action@v6 with: context: . @@ -312,7 +351,7 @@ jobs: cache-from: type=gha - name: Update GitHub Release Draft (Master Only) - if: github.ref == 'refs/heads/master' + if: steps.filter.outputs.backend_or_docker == 'true' && github.ref == 'refs/heads/master' uses: release-drafter/release-drafter@v6 with: tag: ${{ env.new_tag }} @@ -321,7 +360,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Publish GitHub Draft Release (Master Only) - if: github.ref == 'refs/heads/master' + if: steps.filter.outputs.backend_or_docker == 'true' && github.ref == 'refs/heads/master' env: GITHUB_TOKEN: ${{ github.token }} run: |