Skip to content

Commit

Permalink
feat: add rule for branch name (#20)
Browse files Browse the repository at this point in the history
* feat: add rule for branch name

* refactor: naming
  • Loading branch information
nejdetkadir authored Oct 25, 2023
1 parent 9265ddf commit 3c6f3e6
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ jobs:
assigneeRequired: true
checklistRequired: true
semanticTitleRequired: true
semanticBranchNameRequired: true
repoToken: ${{ secrets.SECRET_TOKEN }}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@ jobs:
assigneeRequired: true # If true, the PR must have at least one assignee
checklistRequired: true # If true, the PR must have a checklist on the PR body
semanticTitleRequired: true # If true, the PR must have a semantic title. The title must follow the conventional commits specification
semanticBranchNameRequired: true # If true, the PR must have a semantic branch name
repoToken: ${{ secrets.SECRET_TOKEN }} # Personal Access Token with repo scope
```
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ inputs:
description: 'Whether a semantic title is required or not'
required: true
default: 'true'
semanticBranchNameRequired:
description: 'Whether a semantic branch name is required or not'
required: true
default: 'true'
repoToken:
description: 'The repository token'
required: true
Expand Down
73 changes: 71 additions & 2 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import CHECKLIST_KEYS from './checklistKeys';
import COMMIT_KEYS from './commitKeys';
import INPUT_KEYS from './inputKeys';
import QUESTIONS from './questions';
import PULL_REQUEST from './pullRequest';

export { CHECKLIST_KEYS, COMMIT_KEYS, INPUT_KEYS, QUESTIONS };
export { CHECKLIST_KEYS, COMMIT_KEYS, INPUT_KEYS, QUESTIONS, PULL_REQUEST };
1 change: 1 addition & 0 deletions src/constants/inputKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const INPUT_KEYS = Object.freeze({
ASSIGNEE_REQUIRED: 'assigneeRequired',
CHECKLIST_REQUIRED: 'checklistRequired',
SEMANTIC_TITLE_REQUIRED: 'semanticTitleRequired',
SEMANTIC_BRANCH_NAME_REQUIRED: 'semanticBranchNameRequired',
REPO_TOKEN: 'repoToken'
});

Expand Down
17 changes: 17 additions & 0 deletions src/constants/pullRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const PULL_REQUEST = Object.freeze({
PREFIXES: Object.freeze([
'build',
'chore',
'ci',
'docs',
'feature',
'fix',
'perf',
'refactor',
'revert',
'style',
'test'
])
});

export default PULL_REQUEST;
6 changes: 6 additions & 0 deletions src/hooks/useInputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type UseInputsReturnTypes = {
isAssigneeRequired: boolean;
isChecklistRequired: boolean;
isSemanticTitleRequired: boolean;
isSemanticBranchNameRequired: boolean;
repoToken: string;
};

Expand All @@ -27,13 +28,18 @@ export default function useInputs(): UseInputsReturnTypes {
core.getInput(INPUT_KEYS.SEMANTIC_TITLE_REQUIRED, { required: true })
);

const isSemanticBranchNameRequired = Boolean(
core.getInput(INPUT_KEYS.SEMANTIC_BRANCH_NAME_REQUIRED, { required: true })
);

const repoToken = core.getInput(INPUT_KEYS.REPO_TOKEN, { required: true });

return {
isReviewerRequired,
isAssigneeRequired,
isChecklistRequired,
isSemanticTitleRequired,
isSemanticBranchNameRequired,
repoToken
};
}
22 changes: 22 additions & 0 deletions src/lib/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ async function commentErrors(errors: string[]) {
return;
}

if (await pullRequest.missingSemanticBranchName()) {
await commentAndClosePR();
return;
}

await removeOldPRComments();

const octokit = useOctokit();
Expand Down Expand Up @@ -76,4 +81,21 @@ async function commentDraftPR() {
});
}

async function commentAndClosePR() {
const octokit = useOctokit();
const { PROwner } = await pullRequest.getPRInfo();

await octokit.rest.issues.createComment({
...github.context.repo,
issue_number: github.context.issue.number,
body: `BOT MESSAGE :robot:\n\n\nPlease follow the semantic branch naming convention :construction:\n\n\n@${PROwner}`
});

await octokit.rest.pulls.update({
...github.context.repo,
pull_number: github.context.issue.number,
state: 'closed'
});
}

export default { commentErrors };
31 changes: 29 additions & 2 deletions src/lib/pullRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as github from '@actions/github';
import * as core from '@actions/core';

import { useOctokit, useInputs } from '@app/hooks';
import { COMMIT_KEYS } from '@app/constants';
import { COMMIT_KEYS, PULL_REQUEST } from '@app/constants';

async function getPRInfo() {
const octokit = useOctokit();
Expand All @@ -24,6 +24,7 @@ async function getPRInfo() {
isAssigned: !!issue?.data?.assignee,
hasReviewers: !!PR?.data?.requested_reviewers?.length,
PROwner: PR?.data?.user?.login ?? '',
branchName: PR?.data?.head?.ref ?? '',
isMerged: PR?.data?.merged_at !== null
};
}
Expand Down Expand Up @@ -83,11 +84,37 @@ async function missingReviewers() {
return !hasReviewers;
}

async function missingSemanticBranchName() {
const { isSemanticBranchNameRequired } = useInputs();
const { branchName } = await getPRInfo();

if (!isSemanticBranchNameRequired) {
return false;
}

return isInvalidBranchName(branchName);
}

function isInvalidBranchName(title: string) {
if (!PULL_REQUEST.PREFIXES.some(prefix => title.startsWith(prefix))) {
return true;
}

const titleRegex = /^[a-z]+(?:\/[a-z-]+)+$/;

if (!titleRegex.test(title)) {
return true;
}

return false;
}

export default {
getPRInfo,
hasSemanticTitle,
hasTaskNumber,
missingAssignees,
missingSemanticTitle,
missingReviewers
missingReviewers,
missingSemanticBranchName
};

0 comments on commit 3c6f3e6

Please sign in to comment.