diff --git a/.github/actions/validate-pr/action.yml b/.github/actions/validate-pr/action.yml new file mode 100644 index 0000000..f9e1157 --- /dev/null +++ b/.github/actions/validate-pr/action.yml @@ -0,0 +1,38 @@ +name: 'Validate PR' +description: 'Validate PR title and branch' +inputs: + pr_title: + description: 'The title of the pull request' + required: true + type: string + pr_branch: + description: 'The branch name of the pull request' + required: true + type: string + latest_release: + description: 'The latest release version' + required: true + type: string + package_version: + description: 'The version of the package' + required: true + type: string + target_branch: + description: 'The target branch for the pull request' + required: true + type: string +runs: + using: 'composite' + steps: + - id: composite + run: | + echo "$JSON" + bash ${{ github.action_path }}/validate_pr.sh + shell: bash + env: + JSON: ${{ toJson(inputs) }} + PR_TITLE: ${{ inputs.pr_title }} + PR_BRANCH: ${{ inputs.pr_branch }} + LATEST_RELEASE: ${{ inputs.latest_release }} + PACKAGE_VERSION: ${{ inputs.package_version }} + TARGET_BRANCH: ${{ inputs.target_branch }} diff --git a/.github/actions/validate-pr/validate_pr.sh b/.github/actions/validate-pr/validate_pr.sh new file mode 100755 index 0000000..ad58693 --- /dev/null +++ b/.github/actions/validate-pr/validate_pr.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +if [ "$PR_TITLE" == "" ]; then + echo "env variable PR_TITLE is required" + exit 1 +fi +if [ "$PR_BRANCH" == "" ]; then + echo "env variable PR_BRANCH is required" + exit 1 +fi +if [ "$LATEST_RELEASE" == "" ]; then + echo "env variable LATEST_RELEASE is required" + exit 1 +fi +if [ "$PACKAGE_VERSION" == "" ]; then + echo "env variable PACKAGE_VERSION is required" + exit 1 +fi +if [ "$TARGET_BRANCH" == "" ]; then + echo "env variable TARGET_BRANCH is required" + exit 1 +fi + +SEMANTIC_PREFIXES="^(feat|fix|chore|docs|style|refactor|perf|test):" +JIRA_TICKET="([A-Z]+-[0-9]+)" +VERSION_REGEX="^v([0-9]+)\.([0-9]+)\.([0-9]+)$" + +if [[ "$TARGET_BRANCH" == "develop" ]] || [[ "$TARGET_BRANCH" =~ ^release/v ]] || [[ "$TARGET_BRANCH" =~ ^hotfix/v ]]; then + if [[ "$PR_BRANCH" =~ ^release/v ]] || [[ "$PR_BRANCH" =~ ^hotfix/v ]]; then + echo "PR title and branch name validation passed." + exit 0 + fi + if [[ ! "$PR_TITLE" =~ $SEMANTIC_PREFIXES ]]; then + echo "PR title must start with a valid semantic prefix (e.g., feat:, fix:)." + exit 1 + fi + + if [[ ! "$PR_TITLE" =~ $JIRA_TICKET ]]; then + echo "PR title must contain a valid Jira ticket ID (e.g., ABC-123)." + exit 1 + fi +fi + +if [[ "$TARGET_BRANCH" == "main" ]]; then + if [[ "$PR_BRANCH" =~ ^release/v ]] || [[ "$PR_BRANCH" =~ ^hotfix/v ]]; then + if [[ ! "$PR_BRANCH" =~ ^release/v$PACKAGE_VERSION ]] && [[ ! "$PR_BRANCH" =~ ^hotfix/v$PACKAGE_VERSION ]]; then + echo "PR branch and package version must match" + exit 1 + elif [[ "$(printf '%s\n' "$PACKAGE_VERSION" "$LATEST_RELEASE" | sort | tail -n1)" == "$LATEST_RELEASE" ]]; then + echo "Next predicted version must be higher than the latest release." + exit 1 + fi + else + echo "PR branch must be release/v$PACKAGE_VERSION or hotfix/v$PACKAGE_VERSION" + exit 1 + fi + if [[ ! "$PR_BRANCH" =~ ^release/v$PACKAGE_VERSION ]] && [[ ! "$PR_BRANCH" =~ ^hotfix/v$PACKAGE_VERSION ]]; then + echo "PR branch must be release/v$PACKAGE_VERSION or hotfix/v$PACKAGE_VERSION" + exit 1 + fi +fi + +echo "PR title and branch name validation passed." \ No newline at end of file diff --git a/.github/actions/validate-pr/validate_pr_test.sh b/.github/actions/validate-pr/validate_pr_test.sh new file mode 100755 index 0000000..f870655 --- /dev/null +++ b/.github/actions/validate-pr/validate_pr_test.sh @@ -0,0 +1,206 @@ + +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +function expect() { + if [ "$1" != "$2" ]; then + echo "Expected: $2" + echo "Actual: $1" + exit 1 + else + echo "OK" + fi +} + +beforeAll() { + export TESTING=true +} + +beforeEach() { + export PR_TITLE="feat: This is a PR title (ISSUE-1234)" + export PR_BRANCH="feature/ISSUE-1234" + export LATEST_RELEASE="1.0.0" + export PACKAGE_VERSION="1.1.0" + export TARGET_BRANCH="develop" +} + +beforeAll + +echo Scenario: PR title missing semantic prefix for feature branch +beforeEach + +# GIVEN +export PR_TITLE="This is a PR title (ISSUE-1234)" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "1" +expect "$ACTUAL" "PR title must start with a valid semantic prefix (e.g., feat:, fix:)." + +echo Scenario: PR title missing Jira ticket for feature branch +beforeEach + +# GIVEN +export PR_TITLE="feat: This is a PR title" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "1" +expect "$ACTUAL" "PR title must contain a valid Jira ticket ID (e.g., ABC-123)." + +echo Scenario: PR title missing Jira ticket for feature branch targeting hotfix branch +beforeEach + +# GIVEN +export PR_TITLE="feat: This is a PR title" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "1" +expect "$ACTUAL" "PR title must contain a valid Jira ticket ID (e.g., ABC-123)." + +echo Scenario: Valid PR title for feature branch +beforeEach + +# GIVEN +export PR_TITLE="feat: This is a PR title (ISSUE-1234)" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "0" +expect "PR title and branch name validation passed." "$ACTUAL" + +echo Scenario: Non release or hotfix branch targeting the main branch +beforeEach + +# GIVEN +export PR_BRANCH="TICKET-123" +export TARGET_BRANCH="main" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "1" +expect "$ACTUAL" "PR branch must be release/v1.1.0 or hotfix/v1.1.0" + +echo Scenario: Package version and branch name mismatch +beforeEach + +# GIVEN +export PR_BRANCH="release/v1.1.0" +export TARGET_BRANCH="main" +export PACKAGE_VERSION="1.0.0" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "1" +expect "$ACTUAL" "PR branch and package version must match" + +echo Scenario: Next Release version is not higher than the latest version +beforeEach + +# GIVEN +export PR_BRANCH="release/v1.0.0" +export TARGET_BRANCH="main" +export PACKAGE_VERSION="1.0.0" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "1" +expect "$ACTUAL" "Next predicted version must be higher than the latest release." + +echo Scenario: Valid feature branch to develop branch +beforeEach + +# GIVEN +export PR_BRANCH="feature/ISSUE-1234" +export TARGET_BRANCH="develop" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "0" + +echo Scenario: Valid feature branch to release branch +beforeEach + +# GIVEN +export PR_BRANCH="feature/ISSUE-1234" +export TARGET_BRANCH="release/v1.1.0" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "0" + +echo Scenario: Valid feature branch to hotfix branch +beforeEach + +# GIVEN +export PR_BRANCH="feature/ISSUE-1234" +export TARGET_BRANCH="hotfix/v1.1.0" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "0" + +echo Scenario: Valid release branch to main branch +beforeEach + +# GIVEN +export PR_BRANCH="release/v1.1.0" +export TARGET_BRANCH="main" +export PR_TITLE="release v1.1.0 to main" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "0" + +echo Scenario: Valid hotfix branch to main branch +beforeEach + +# GIVEN +export PR_BRANCH="hotfix/v1.1.0" +export TARGET_BRANCH="main" +export PR_TITLE="hotfix v1.1.0 to main" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "0" + +echo Scenario: Valid release branch merging to develop +beforeEach + +# GIVEN +export PR_BRANCH="release/v1.1.0" +export TARGET_BRANCH="develop" +export PR_TITLE="release v1.1.0 to develop" + +# WHEN +ACTUAL="$($SCRIPT_DIR/validate_pr.sh)" + +# THEN +expect "$?" "0" + diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml new file mode 100644 index 0000000..2949952 --- /dev/null +++ b/.github/workflows/validate-pr.yml @@ -0,0 +1,44 @@ +name: Validate PR + +on: + workflow_call: + inputs: + pr_title: + description: "The title of the pull request." + type: string + required: true + pr_branch: + description: "The branch name of the pull request." + type: string + required: true + target_branch: + description: "The target branch for the pull request." + type: string + required: true + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Get Latest Release + uses: VirdocsSoftware/github-actions/.github/actions/predict-next-version@main + - name: Get Package + run: | + PACKAGE_VERSION=$(cat package.json | jq -r '.version') + echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> $GITHUB_ENV + echo "LATEST_RELEASE=$(cat latest_version.txt)" >> $GITHUB_ENV + - name: Print github json + run: echo "$JSON" + env: + JSON: ${{ toJson(inputs) }} + - name: Validate PR title and branch + uses: VirdocsSoftware/github-actions/.github/actions/validate-pr@main + with: + pr_title: ${{ inputs.pr_title }} + pr_branch: ${{ inputs.pr_branch }} + latest_release: ${{ env.LATEST_RELEASE }} + package_version: ${{ env.PACKAGE_VERSION }} + target_branch: ${{ inputs.target_branch }} + diff --git a/tooling/scripts/run_tests.sh b/tooling/scripts/run_tests.sh index 0e72f52..7a087d9 100755 --- a/tooling/scripts/run_tests.sh +++ b/tooling/scripts/run_tests.sh @@ -17,4 +17,10 @@ function run_tests() { done } +function validate_tests() { + set -e + ./.github/actions/validate-pr/validate_pr_test.sh +} + +validate_tests run_tests "$FILES"