diff --git a/.github/workflows/pr-body-validator.yml b/.github/workflows/pr-body-validator.yml new file mode 100644 index 0000000..a7f1e0a --- /dev/null +++ b/.github/workflows/pr-body-validator.yml @@ -0,0 +1,65 @@ +name: ๐Ÿ“ Validate PR Description + +on: + pull_request: + types: [opened, edited, reopened] + +jobs: + validate-pr-body: + runs-on: ubuntu-latest + + steps: + - name: Validate PR Description + shell: bash + env: + PR_BODY: ${{ github.event.pull_request.body }} + run: | + CLEAN_BODY=$(echo "$PR_BODY" | sed 's/\r$//') + + if [ -z "${CLEAN_BODY// }" ]; then + echo "โŒ PR description is completely empty." >&2 + exit 1 + fi + + REQUIRED_FIELDS=("## Description of Task?" "## Any background context you want to provide?" "## What are the relevant user stories or tasks links from Azure DevOps?") + OPTIONAL_FIELD="## Screenshots (if appropriate)" + + current_field="" + declare -A field_content + errors=() + + # Parse PR body + while IFS= read -r line; do + trimmed=$(echo "$line" | xargs) + for field in "${REQUIRED_FIELDS[@]}" "$OPTIONAL_FIELD"; do + if [[ "${trimmed,,}" == "${field,,}" ]]; then + current_field="$field" + field_content["$current_field"]="" + continue 2 + fi + done + + if [ -n "$current_field" ]; then + if [ -z "${field_content[$current_field]}" ]; then + field_content["$current_field"]="$trimmed" + else + field_content["$current_field"]="${field_content[$current_field]}"$'\n'"$trimmed" + fi + fi + done <<< "$CLEAN_BODY" + + for field in "${REQUIRED_FIELDS[@]}"; do + content="${field_content[$field]}" + if [ -z "$content" ]; then + errors+=("โŒ Missing content for: $field") + elif echo "$content" | grep -iq "^N/A$"; then + errors+=("โŒ Field '$field' has invalid content: N/A") + fi + done + + if [ ${#errors[@]} -gt 0 ]; then + printf "%s\n" "${errors[@]}" >&2 + exit 1 + fi + + echo "โœ… PR description is valid." diff --git a/.github/workflows/validate-branch-name.yml b/.github/workflows/validate-branch-name.yml new file mode 100644 index 0000000..07dfdf5 --- /dev/null +++ b/.github/workflows/validate-branch-name.yml @@ -0,0 +1,35 @@ +name: ๐Ÿ”€ Validate Branch Name + +on: + pull_request: + types: [opened, reopened] + +jobs: + validate-branch-name: + runs-on: ubuntu-latest + steps: + - name: Validate Branch Name Format + shell: bash + env: + BRANCH_NAME: ${{ github.head_ref }} + run: | + echo "๐Ÿ” Validating branch name: $BRANCH_NAME" + + # Allowed types: ft, bg, ch, rf + # Format: {type}-{summary}-{id} + BRANCH_REGEX='^(ft|bg|ch|rf)-[a-z0-9]+(-[a-z0-9]+){1,2}-[0-9]+$' + + if [[ "$BRANCH_NAME" =~ $BRANCH_REGEX ]]; then + echo "โœ… Branch name format is valid." + else + echo "โŒ Branch name format is invalid: \"$BRANCH_NAME\"" + echo "" + echo "Expected format: {story-type}-{summary}-{task-id}" + echo "Where:" + echo " - story-type: ft, bg, ch, or rf" + echo " - summary: 2โ€“3 lowercase words, dash-separated" + echo " - task-id: numeric Azure DevOps task ID" + echo "" + echo "Example: ft-add-auth-token-4521" + exit 1 + fi diff --git a/.github/workflows/validate-commits.yml b/.github/workflows/validate-commits.yml new file mode 100644 index 0000000..6699e0d --- /dev/null +++ b/.github/workflows/validate-commits.yml @@ -0,0 +1,30 @@ +name: ๐Ÿงช Validate Commit Message + +on: + push: + +jobs: + validate-commit: + runs-on: ubuntu-latest + steps: + - name: Checkout current branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Needed to see full commit history + + - name: Get last non-merge commit message on current branch + id: get_commit + run: | + LAST_COMMIT=$(git log --no-merges -1 --pretty=%s) + echo "๐Ÿ“ Commit subject: $LAST_COMMIT" + echo "commit_msg=$LAST_COMMIT" >> $GITHUB_OUTPUT + + - name: Validate commit message + run: | + echo "Validating: '${{ steps.get_commit.outputs.commit_msg }}'" + if [[ ! "${{ steps.get_commit.outputs.commit_msg }}" =~ ^(feat|fix|chore|docs|style|refactor|test|perf|ci|build|revert)(\(.+\))?:\ .+ ]]; then + echo "โŒ Invalid commit message format." + echo "๐Ÿ’ก Expected format: 'type(scope): description', e.g., 'feat(auth): add login API'" + exit 1 + fi + shell: bash