feat: Dashboard contract improvement #29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Conventional Commits Check | |
| on: | |
| pull_request: | |
| branches: [main] | |
| types: [opened, synchronize, reopened, edited] | |
| jobs: | |
| validate-commits: | |
| runs-on: ubuntu-latest | |
| # Skip validation for Release Please PRs (auto-generated) | |
| if: | | |
| !startsWith(github.event.pull_request.title, 'chore(main): release') && | |
| !contains(github.event.pull_request.labels.*.name, 'autorelease: pending') | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| statuses: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Cache npm dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.npm | |
| key: ${{ runner.os }}-commitlint-${{ hashFiles('.github/workflows/pr-conventional-commits.yml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-commitlint- | |
| - name: Install Commitlint | |
| run: npm install --save-dev @commitlint/config-conventional @commitlint/cli | |
| - name: Create Commitlint Config | |
| run: | | |
| cat > commitlint.config.js << 'EOF' | |
| module.exports = { | |
| extends: ['@commitlint/config-conventional'], | |
| rules: { | |
| 'type-enum': [2, 'always', [ | |
| 'build', 'chore', 'ci', 'docs', 'feat', 'fix', | |
| 'perf', 'refactor', 'revert', 'style', 'test' | |
| ]], | |
| 'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']], | |
| 'subject-max-length': [2, 'always', 100], | |
| 'body-max-line-length': [2, 'always', 100], | |
| 'footer-max-line-length': [2, 'always', 100] | |
| } | |
| } | |
| EOF | |
| - name: Check PR Title | |
| id: check_title | |
| run: | | |
| PR_TITLE="${{ github.event.pull_request.title }}" | |
| echo "Checking PR title: $PR_TITLE" | |
| if echo "$PR_TITLE" | npx commitlint; then | |
| echo "title_valid=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "title_valid=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Check PR Commits | |
| id: check_commits | |
| run: | | |
| COMMITS=$(git rev-list --reverse ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}) | |
| COMMIT_COUNT=$(echo "$COMMITS" | wc -l) | |
| VALID=0 | |
| INVALID="" | |
| for commit in $COMMITS; do | |
| MSG=$(git log --format=%B -n 1 "$commit" | head -n 1) | |
| if echo "$MSG" | npx commitlint 2>/dev/null; then | |
| VALID=$((VALID + 1)) | |
| else | |
| INVALID="$INVALID\n- \`${commit:0:7}\`: $MSG" | |
| fi | |
| done | |
| echo "commit_count=$COMMIT_COUNT" >> "$GITHUB_OUTPUT" | |
| echo "valid_commits=$VALID" >> "$GITHUB_OUTPUT" | |
| if [ "$VALID" -eq "$COMMIT_COUNT" ]; then | |
| echo "commits_valid=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "commits_valid=false" >> "$GITHUB_OUTPUT" | |
| { | |
| echo "invalid_commits<<EOF" | |
| echo -e "$INVALID" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Update PR comment | |
| if: always() | |
| run: | | |
| BODY="## Conventional Commits Validation\n\n" | |
| if [ "${{ steps.check_title.outputs.title_valid }}" = "true" ]; then | |
| BODY="${BODY}**PR Title**: valid\n" | |
| else | |
| BODY="${BODY}**PR Title**: invalid — please use \`type(scope): description\`\n" | |
| fi | |
| if [ "${{ steps.check_commits.outputs.commits_valid }}" = "true" ]; then | |
| BODY="${BODY}**Commits**: all ${{ steps.check_commits.outputs.commit_count }} follow conventional format\n" | |
| else | |
| BODY="${BODY}**Commits**: some do not follow conventional format\n" | |
| BODY="${BODY}${{ steps.check_commits.outputs.invalid_commits }}\n" | |
| fi | |
| if [ "${{ steps.check_title.outputs.title_valid }}" != "true" ] || \ | |
| [ "${{ steps.check_commits.outputs.commits_valid }}" != "true" ]; then | |
| BODY="${BODY}\n### Format\n\n\`type(scope): description\`\n\n" | |
| BODY="${BODY}**Types**: \`feat\`, \`fix\`, \`docs\`, \`style\`, \`refactor\`, \`test\`, \`chore\`, \`ci\`, \`build\`, \`perf\`, \`revert\`\n" | |
| fi | |
| COMMENT_ID=$(gh pr view ${{ github.event.pull_request.number }} --json comments \ | |
| --jq '.comments[] | select(.author.login == "github-actions[bot]" and (.body | test("Conventional Commits Validation"))) | .id' \ | |
| | head -n 1) | |
| if [ -n "$COMMENT_ID" ]; then | |
| gh api -X PATCH "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" \ | |
| -f body="$(echo -e "$BODY")" | |
| else | |
| gh pr comment ${{ github.event.pull_request.number }} --body "$(echo -e "$BODY")" | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set commit status | |
| if: always() | |
| run: | | |
| SHA="${{ github.event.pull_request.head.sha }}" | |
| title_state="success" | |
| title_desc="PR title follows conventional commits" | |
| if [ "${{ steps.check_title.outputs.title_valid }}" != "true" ]; then | |
| title_state="failure" | |
| title_desc="PR title does not follow conventional commits" | |
| fi | |
| commits_state="success" | |
| commits_desc="All commits follow conventional commits" | |
| if [ "${{ steps.check_commits.outputs.commits_valid }}" != "true" ]; then | |
| commits_state="failure" | |
| commits_desc="Some commits do not follow conventional commits" | |
| fi | |
| gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \ | |
| -f state="$title_state" \ | |
| -f context="conventional-commits/title" \ | |
| -f description="$title_desc" | |
| gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \ | |
| -f state="$commits_state" \ | |
| -f context="conventional-commits/commits" \ | |
| -f description="$commits_desc" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Label PR | |
| uses: bcoe/conventional-release-labels@v1 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Final validation | |
| run: | | |
| if [ "${{ steps.check_title.outputs.title_valid }}" = "true" ] && \ | |
| [ "${{ steps.check_commits.outputs.commits_valid }}" = "true" ]; then | |
| echo "All conventional commits validations passed" | |
| else | |
| echo "Conventional commits validation failed" | |
| exit 1 | |
| fi |