diff --git a/.github/workflows/conventional-commit.yaml b/.github/workflows/conventional-commit.yaml new file mode 100644 index 0000000..e485bff --- /dev/null +++ b/.github/workflows/conventional-commit.yaml @@ -0,0 +1,114 @@ +name: Conventional Commit + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + branches: ["main"] + +jobs: + check-title: + name: Check PR Title + runs-on: ubuntu-latest + + permissions: + pull-requests: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Install commitlint + run: npm install --no-save @commitlint/cli@20 @commitlint/config-conventional@20 + + - name: Lint PR title + id: commitlint + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + set -o pipefail + echo '{"extends":["@commitlint/config-conventional"]}' > .commitlintrc.json + echo "$PR_TITLE" | npx commitlint --color false 2>&1 | tee /tmp/commitlint-output.txt + + - name: Delete outdated comment on success + if: success() + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + }); + + const marker = "**PR title does not follow [Conventional Commits]"; + const existing = comments.find( + (c) => c.user.type === "Bot" && c.body.includes(marker) + ); + + if (existing) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + }); + } + + - name: Comment on failure + if: failure() + uses: actions/github-script@v7 + with: + script: | + const fs = require("fs"); + const output = fs.readFileSync("/tmp/commitlint-output.txt", "utf8").trim(); + + const body = [ + `**PR title does not follow [Conventional Commits](https://www.conventionalcommits.org/) format.**`, + ``, + `Please update the PR title to match the format: \`type(optional scope): description\``, + ``, + `Examples:`, + `- \`feat: added new feature\``, + `- \`fix(cli): corrected typo in help message\``, + `- \`docs: updated README.md\``, + ``, + `
`, + `commitlint output`, + ``, + "```", + output, + "```", + `
`, + ].join("\n"); + + // Find existing bot comment to update instead of creating duplicates + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + }); + + const marker = "**PR title does not follow [Conventional Commits]"; + const existing = comments.find( + (c) => c.user.type === "Bot" && c.body.includes(marker) + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body: body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: body, + }); + }