├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app.yml ├── constants.js ├── controllers ├── checks.js ├── marketplace.js └── pullRequest.js ├── helpers ├── checks.js └── pullRequest.js ├── index.js ├── middleware └── verifyWebhooks.js ├── package-lock.json ├── package.json ├── public ├── marketplacePurchase.html └── privacy.html ├── travis.yml └── utilities ├── dataConverter.js └── regexUtil.js /.gitignore: -------------------------------------------------------------------------------- 1 | ### Code ### 2 | .vscode/* 3 | !.vscode/settings.json 4 | !.vscode/tasks.json 5 | !.vscode/launch.json 6 | !.vscode/extensions.json 7 | 8 | ### Node ### 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (https://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | jspm_packages/ 50 | 51 | # TypeScript v1 declaration files 52 | typings/ 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional REPL history 61 | .node_repl_history 62 | 63 | # Output of 'npm pack' 64 | *.tgz 65 | 66 | # Yarn Integrity file 67 | .yarn-integrity 68 | 69 | # dotenv environment variables file 70 | .env 71 | .env.test 72 | 73 | # parcel-bundler cache (https://parceljs.org/) 74 | .cache 75 | 76 | # next.js build output 77 | .next 78 | 79 | # nuxt.js build output 80 | .nuxt 81 | 82 | # vuepress build output 83 | .vuepress/dist 84 | 85 | # Serverless directories 86 | .serverless/ 87 | 88 | # FuseBox cache 89 | .fusebox/ 90 | 91 | # DynamoDB Local files 92 | .dynamodb/ 93 | 94 | ### Serverless ### 95 | # Ignore build directory 96 | .serverless 97 | 98 | ##Apple 99 | .DS_store 100 | 101 | .vscode -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at asoni@isystango.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: /fork 4 | [pr]: /compare 5 | [style]: https://standardjs.com/ 6 | [code-of-conduct]: CODE_OF_CONDUCT.md 7 | 8 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 9 | 10 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 11 | 12 | ## Issues and PRs 13 | 14 | If you have suggestions for how this project could be improved, or want to report a bug, open an issue! We'd love all and any contributions. If you have questions, too, we'd love to hear them. 15 | 16 | We'd also love PRs. If you're thinking of a large PR, we advise opening up an issue first to talk about it, though! Look at the links below if you're not sure how to open a PR. 17 | 18 | ## Submitting a pull request 19 | 20 | 1. [Fork][fork] and clone the repository. 21 | 1. Configure and install the dependencies: `npm install`. 22 | 1. Make sure the tests pass on your machine: `npm test`, note: these tests also apply the linter, so there's no need to lint separately. 23 | 1. Create a new branch: `git checkout -b my-branch-name`. 24 | 1. Make your change, add tests, and make sure the tests still pass. 25 | 1. Push to your fork and [submit a pull request][pr]. 26 | 1. Pat your self on the back and wait for your pull request to be reviewed and merged. 27 | 28 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 29 | 30 | - Follow the [style guide][style] which is using standard. Any linting errors should be shown when running `npm test`. 31 | - Write and update tests. 32 | - Keep your changes as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 33 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 34 | 35 | Work in Progress pull requests are also welcome to get feedback early on, or if there is something blocked you. 36 | 37 | ## Resources 38 | 39 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 40 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 41 | - [GitHub Help](https://help.github.com) 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Systango Technologies Pvt. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Commit Message Lint 2 | Github app to validate commit message and pull request title on a pull request 3 | 4 | ## Description 5 | This app runs a format check on commit messages and pull request title on the creation of a pull request. 6 | 7 | For example, let's say you specify that a commit message should have a format `DDD:message`. Here D stand for numeric digit. The app checks if the commit message follows this format. If all the commit messages follow this format, the check returns successful, otherwise failure. The reviewer can then decide if they want to go ahead with the code merge. 8 | 9 | ### App URL 10 | https://github.com/apps/commit-message-lint 11 | 12 | ## Installation 13 | 14 | Use the Github's app section or above URL to install the app to your repository. 15 | 16 | ## Configuration 17 | 18 | You would need to add a configuration folder named `.github` at the root of your repository. The folder should contain a file named `config.yml`. This file will serve as the configuration and the contents of that file will be: 19 | 20 | ``` 21 | PR_TITLE_REGEX: 22 | COMMIT_MESSAGE_REGEX: 23 | OUTPUT_TITLE_FAIL: Message validation failed!!! 24 | OUTPUT_TITLE_SUCCESS: Message validation passed!!! 25 | VALID_COMMIT_MESSAGE: Commit messages are valid 26 | INVALID_COMMIT_MESSAGE: Commit messages are invalid 27 | SINGLE_OTHER_INVALID_MESSAGE: other message is invalid 28 | MULTIPLE_OTHER_INVALID_MESSAGE: other messages are invalid 29 | VALID_PULL_REQUEST_MESSAGE: Pull request title is valid 30 | INVALID_PULL_REQUEST_MESSAGE: Pull request title is invalid 31 | ``` 32 | 33 | ## Usage 34 | Go to the `checks` section on your PR to see the result of the check run performed by the app. It will show you the result as well as the commit messages which failed. 35 | 36 | ## Local setup 37 | Step 1. Clone the application. 38 | Step 2. Run `npm install` 39 | Step 3. Create `.env` file in the root directory and set the following environment variables in `.env` file : 40 | ``` 41 | APP_ID - Github app id (get from app settings page) 42 | WEBHOOK_PROXY_URL - URL of the hosted application, use ngrok for local 43 | WEBHOOK_SECRET - webhook secret for security, same as the one set in github app settings 44 | PRIVATE_KEY - Get from github app settings page 45 | LOG_LEVEL - Log level 46 | REGEX_CONFIG_FILE_NAME - config file which contains repo config, keep it as config.yml 47 | GITHUB_BASE_PATH - Github API path, keep it as https://api.github.com 48 | ``` 49 | Note : Take values of APP_ID, WEBHOOK_PROXY_URL, WEBHOOK_SECRET, PRIVATE_KEY from https://github.com/settings/apps/commit-message-lint 50 | 51 | Step 4. Run `npm start` to start the application 52 | 53 | ## Resources 54 | [Purchase Flow](https://developer.github.com/marketplace/integrating-with-the-github-marketplace-api/handling-new-purchases-and-free-trials/) 55 | 56 | [Identifying and authorizing users for GitHub Apps](https://developer.github.com/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps/) 57 | 58 | [Integrating with the GitHub Marketplace API](https://developer.github.com/marketplace/integrating-with-the-github-marketplace-api/) 59 | 60 | 61 | ## Contributors 62 | [Anshul Soni](https://www.linkedin.com/in/anshul-soni-3903a2101/) 63 | 64 | [Sumit Singhal](https://www.linkedin.com/in/s-singhal) 65 | 66 | [Vikas Patidar](https://www.linkedin.com/in/vikas-patidar-0106/) -------------------------------------------------------------------------------- /app.yml: -------------------------------------------------------------------------------- 1 | # This is a GitHub App Manifest. These settings will be used by default when 2 | # initially configuring your GitHub App. 3 | # 4 | # NOTE: changing this file will not update your GitHub App settings. 5 | # You must visit github.com/settings/apps/your-app-name to edit them. 6 | # 7 | # Read more about configuring your GitHub App: 8 | # https://probot.github.io/docs/development/#configuring-a-github-app 9 | # 10 | # Read more about GitHub App Manifests: 11 | # https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest/ 12 | 13 | # The list of events the GitHub App subscribes to. 14 | # Uncomment the event names below to enable them. 15 | default_events: 16 | - check_run 17 | - check_suite 18 | # - commit_comment 19 | # - create 20 | # - delete 21 | - deployment 22 | - deployment_status 23 | # - fork 24 | # - gollum 25 | # - issue_comment 26 | # - issues 27 | # - label 28 | # - milestone 29 | # - member 30 | # - membership 31 | # - org_block 32 | # - organization 33 | # - page_build 34 | # - project 35 | # - project_card 36 | # - project_column 37 | # - public 38 | - pull_request 39 | # - pull_request_review 40 | # - pull_request_review_comment 41 | # - push 42 | # - release 43 | # - repository 44 | # - repository_import 45 | # - status 46 | # - team 47 | # - team_add 48 | # - watch 49 | 50 | # The set of permissions needed by the GitHub App. The format of the object uses 51 | # the permission name for the key (for example, issues) and the access type for 52 | # the value (for example, write). 53 | # Valid values are `read`, `write`, and `none` 54 | default_permissions: 55 | # Repository creation, deletion, settings, teams, and collaborators. 56 | # https://developer.github.com/v3/apps/permissions/#permission-on-administration 57 | # administration: read 58 | 59 | # Checks on code. 60 | # https://developer.github.com/v3/apps/permissions/#permission-on-checks 61 | # checks: read 62 | 63 | # Repository contents, commits, branches, downloads, releases, and merges. 64 | # https://developer.github.com/v3/apps/permissions/#permission-on-contents 65 | # contents: read 66 | 67 | # Deployments and deployment statuses. 68 | # https://developer.github.com/v3/apps/permissions/#permission-on-deployments 69 | # deployments: read 70 | 71 | # Issues and related comments, assignees, labels, and milestones. 72 | # https://developer.github.com/v3/apps/permissions/#permission-on-issues 73 | # issues: write 74 | 75 | # Search repositories, list collaborators, and access repository metadata. 76 | # https://developer.github.com/v3/apps/permissions/#metadata-permissions 77 | metadata: read 78 | 79 | # Retrieve Pages statuses, configuration, and builds, as well as create new builds. 80 | # https://developer.github.com/v3/apps/permissions/#permission-on-pages 81 | # pages: read 82 | 83 | # Pull requests and related comments, assignees, labels, milestones, and merges. 84 | # https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests 85 | pull_requests: read 86 | 87 | # Manage the post-receive hooks for a repository. 88 | # https://developer.github.com/v3/apps/permissions/#permission-on-repository-hooks 89 | # repository_hooks: read 90 | 91 | # Manage repository projects, columns, and cards. 92 | # https://developer.github.com/v3/apps/permissions/#permission-on-repository-projects 93 | # repository_projects: read 94 | 95 | # Retrieve security vulnerability alerts. 96 | # https://developer.github.com/v4/object/repositoryvulnerabilityalert/ 97 | # vulnerability_alerts: read 98 | 99 | # Commit statuses. 100 | # https://developer.github.com/v3/apps/permissions/#permission-on-statuses 101 | # statuses: read 102 | 103 | # Organization members and teams. 104 | # https://developer.github.com/v3/apps/permissions/#permission-on-members 105 | # members: read 106 | 107 | # View and manage users blocked by the organization. 108 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking 109 | # organization_user_blocking: read 110 | 111 | # Manage organization projects, columns, and cards. 112 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-projects 113 | # organization_projects: read 114 | 115 | # Manage team discussions and related comments. 116 | # https://developer.github.com/v3/apps/permissions/#permission-on-team-discussions 117 | # team_discussions: read 118 | 119 | # Manage the post-receive hooks for an organization. 120 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-hooks 121 | # organization_hooks: read 122 | 123 | # Get notified of, and update, content references. 124 | # https://developer.github.com/v3/apps/permissions/ 125 | # organization_administration: read 126 | 127 | 128 | # The name of the GitHub App. Defaults to the name specified in package.json 129 | # name: My Probot App 130 | 131 | # The homepage of your GitHub App. 132 | # url: https://example.com/ 133 | 134 | # A description of the GitHub App. 135 | # description: A description of my awesome app 136 | 137 | # Set to true when your GitHub App is available to the public or false when it is only accessible to the owner of the app. 138 | Default: true 139 | # public: false 140 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configFileName: process.env.REGEX_CONFIG_FILE_NAME, 3 | conclusion_status: { 4 | SUCCESS: 'success', 5 | FAILURE: 'failure', 6 | NEUTRAL: 'neutral', 7 | CANCELLED: 'cancelled', 8 | TIMED_OUT: 'timed_out', 9 | ACTION_REQUIRED: 'action_required' 10 | }, 11 | messages: { 12 | valid_commit_message: 'Commit messages are valid', 13 | invalid_commit_message: 'Commit messages are invalid', 14 | valid_pull_request_message: 'Pull request title is valid', 15 | invalid_pull_request_message: 'Pull request title is invalid', 16 | multiple_other_invalid_message: 'other messages are invalid', 17 | single_other_invalid_message: 'other message is invalid', 18 | pr_configuration_not_set: 'Pull request title format is not configured', 19 | commit_message_configuration_not_set: 20 | 'Commit message format is not configured', 21 | pr_and_commit_message_configuration_not_set: 22 | 'Pull request title and commit message format are not configured', 23 | home_page_message: 'Commit Message Lint App' 24 | }, 25 | CHECK_RUN_STATUS_COMPLETED: 'completed', 26 | CHECK_RUN_NAME: 'Commit Message Lint', 27 | output_title_success: 'Message validation passed!!!', 28 | output_title_fail: 'Message validation failed!!!', 29 | INVALID_COMMIT_LIMIT: 3, 30 | USER_AGENT: 'commit-message-lint-app', 31 | invalid_commit_list: { 32 | commit_id: 'sha:', 33 | commit_message: 'message:' 34 | }, 35 | REGEX: { 36 | MERGE_COMMIT_REGEX: '/^(Merge pull request)/' 37 | }, 38 | events: { 39 | PULL_REQUEST_OPEN: 'pull_request.opened', 40 | CHECK_RUN_REREQUESTED: 'check_run.rerequested', 41 | CHECK_SUITE_REREQUESTED: 'check_suite.rerequested', 42 | CHECK_SUITE_REQUESTED: 'check_suite.requested', 43 | MARKETPLACE_PURCHASE: 'marketplace_purchase', 44 | CHECK_SUITE: 'check_suite' 45 | }, 46 | OAUTH_ENDPOINT: 'https://github.com/login/oauth', 47 | INSTALLATION_PATH: 'https://github.com/settings/installations' 48 | }; 49 | -------------------------------------------------------------------------------- /controllers/checks.js: -------------------------------------------------------------------------------- 1 | const { listForSuite } = require('../helpers/checks'); 2 | 3 | module.exports.listForSuite = async (app, context) => { 4 | try { 5 | const owner = context.payload.repository.owner.login; 6 | const repository = context.payload.repository.name; 7 | const checkSuiteId = context.payload.check_suite.id; 8 | const listOfCheckRuns = await listForSuite(context, owner, repository, checkSuiteId); 9 | return listOfCheckRuns; 10 | } catch (error) { 11 | app.log(error); 12 | return error; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /controllers/marketplace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Packages 3 | */ 4 | const rp = require('request-promise'); 5 | /** 6 | * Constants 7 | */ 8 | const { OAUTH_ENDPOINT, INSTALLATION_PATH } = require('../constants'); 9 | 10 | /** 11 | * Controllers 12 | */ 13 | module.exports.marketplaceEventHandlers = async (req, res) => { 14 | try { 15 | let body = req.body; 16 | console.log('marketplaceEventHandlers ==> ', body); 17 | if(body.action && eventHandlers[body.action]) { 18 | await eventHandlers[body.action](req, res, body); 19 | } 20 | } catch (error) { 21 | console.log('Error while marketplaceEventHandlers => ', error); 22 | } 23 | }; 24 | 25 | module.exports.getAccessToken = async (req, res) => { 26 | try { 27 | const query = req.query; 28 | console.log('AccessToken API call started'); 29 | const getAccessToken = `${OAUTH_ENDPOINT}/access_token?client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}&code=${query.code}&redirect_uri=${process.env.REDIRECT_URI}&state=${query.STATE}`; 30 | let accessToken = await rp(getAccessToken); 31 | console.log('AccessToken API call completed'); 32 | res.redirect(INSTALLATION_PATH); 33 | } catch (error) { 34 | console.log('Error while getAccessToken => ', error); 35 | } 36 | }; 37 | 38 | /** 39 | * Market place event handlers 40 | * TODO: Move to separate file after finalizing structure 41 | */ 42 | let eventHandlers = { 43 | purchased: async (req, res, eventData) => { 44 | let purchaserData = eventData.marketplace_purchase; 45 | console.log('Event purchased triggered', purchaserData); 46 | /** 47 | * TODO: Authenticate user with github in future if we maintian user information in our database and we need to call user specific detials from github. 48 | */ 49 | // const oAuthUrl = `${OAUTH_ENDPOINT}/authorize?client_id=${process.env.CLIENT_ID}&redirect_uri=${process.env.REDIRECT_URI}&state=${process.env.STATE}&login=${purchaserData.account.login}`; 50 | // console.log('oAuthUrl', oAuthUrl); 51 | // res.redirect(oAuthUrl); 52 | return res.json().status(200); 53 | }, 54 | cancelled: async (req, res, eventData) => { 55 | let purchaserData = eventData.marketplace_purchase; 56 | console.log('Event cancelled triggered', purchaserData); 57 | return res.json().status(200); 58 | }, 59 | pending_change: async (req, res, eventData) => { 60 | let purchaserData = eventData.marketplace_purchase; 61 | console.log('Event pending_change triggered', purchaserData); 62 | return res.json().status(200); 63 | }, 64 | pending_change_cancelled: async (req, res, eventData) => { 65 | let purchaserData = eventData.marketplace_purchase; 66 | console.log('Event pending_change_cancelled triggered', purchaserData); 67 | return res.json().status(200); 68 | }, 69 | changed: async (req, res, eventData) => { 70 | let purchaserData = eventData.marketplace_purchase; 71 | console.log('Event changed triggered', purchaserData); 72 | return res.json().status(200); 73 | } 74 | }; -------------------------------------------------------------------------------- /controllers/pullRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utils 3 | */ 4 | const { checkRegex } = require('../utilities/regexUtil'); 5 | /** 6 | * Helpers 7 | */ 8 | const { createCheckSuite, createCheckRun, listCheckSuite, updateCheckRun } = require('../helpers/checks'); 9 | const { listCommitsOfPullRequest, getPullRequest } = require('../helpers/pullRequest'); 10 | /** 11 | * Constants 12 | */ 13 | const constants = require('../constants.js'); 14 | const rp = require('request-promise'); 15 | const mergeCommitRegex = constants.REGEX.MERGE_COMMIT_REGEX; 16 | const conclusionStatus = constants.conclusion_status; 17 | const messages = constants.messages; 18 | const checkRunStatusCompleted = constants.CHECK_RUN_STATUS_COMPLETED; 19 | const checkRunName = constants.CHECK_RUN_NAME; 20 | let outputTitleSuccess = constants.output_title_success; 21 | let outputTitleFail = constants.output_title_fail; 22 | 23 | /** 24 | * Commit messages and PR title Validator 25 | * @param {Object} app Probot app object 26 | * @param {Object} context Github event context 27 | * @param {Object} configuration Contains data (i.e regex for PR and Commits) from config.yml file 28 | * @param {Boolean} updateCheckRunFlag Update existing check run 29 | * @param {Boolean} createCheckRunFlag Create existing check run 30 | */ 31 | module.exports.commitAndTitleValidator = async (app, context, configuration, updateCheckRunFlag, createCheckRunFlag) => { 32 | try { 33 | console.log('config received using single file:', configuration) 34 | let { owner, repository, pullRequestTitle, pullNumber } = prDetailsExtractor(context); 35 | 36 | if (!configuration || !configuration.PR_TITLE_REGEX || !configuration.COMMIT_MESSAGE_REGEX) { 37 | console.log('configuration object not found'); 38 | } 39 | let { prTitleRegex, commitTitleRegex } = regexExtractor(configuration); 40 | /** 41 | * Find all commits for a pull request 42 | */ 43 | let commits = await listCommitsOfPullRequest(context, owner, repository, pullNumber); 44 | /** 45 | * If `PR title` is not present in `context` 46 | */ 47 | if (!pullRequestTitle) { 48 | /** 49 | * In case of new check suite requested, check re-run and check suite re-run or Re-run all checks, get 50 | * pull request data 51 | */ 52 | const pullRequestDetails = await getPullRequest(context, owner, repository, pullNumber); 53 | /** 54 | * Get PR title 55 | */ 56 | if (pullRequestDetails && pullRequestDetails.data && pullRequestDetails.data.title) { 57 | pullRequestTitle = pullRequestDetails.data.title; 58 | } 59 | } 60 | let result = checkMessagesFormat(pullRequestTitle, commits.data, prTitleRegex, commitTitleRegex, configuration); 61 | await createOrUpdateCheckRun(context, owner, repository, result, updateCheckRunFlag, createCheckRunFlag); 62 | } catch (error) { 63 | console.log('------error------', error); 64 | app.log(error); 65 | } 66 | }; 67 | 68 | /** 69 | * Check Messages Format 70 | * @param {String} pullRequestTitle 71 | * @param {Array} commits 72 | * @param {String} prTitleRegex 73 | * @param {String} commitMsgRegex 74 | */ 75 | function checkMessagesFormat(pullRequestTitle, commits, prTitleRegex, commitMsgRegex, configuration) { 76 | try { 77 | const commitMsgStatusMsg = configuration.VALID_COMMIT_MESSAGE || messages.valid_commit_message; 78 | let result = {}; 79 | let commitIds = []; 80 | let flags = { 81 | pullReqTitleStatus: false, 82 | pullReqTitleStatusMsg: '', 83 | commitMsgStatus: true, 84 | commitMsgStatusMsg, 85 | invalidCommits: '', 86 | invalidCommitsCount: 0, 87 | otherInvalidCommitMessages: '' 88 | }; 89 | checkPrTitle(pullRequestTitle, prTitleRegex, flags, configuration); 90 | if (commits && Array.isArray(commits) && commits.length) { 91 | /** 92 | * Check all commit messages 93 | */ 94 | checkCommitMessages(commits, commitIds, commitMsgRegex, mergeCommitRegex, flags, configuration); 95 | result = concludeCheckRunParams(prTitleRegex, commitMsgRegex, commitIds, flags, configuration); 96 | } 97 | return result; 98 | } catch (error) { 99 | console.error(error); 100 | } 101 | } 102 | 103 | /** 104 | * Match pull request Title with regex 105 | * @param {String} pullRequestTitle 106 | * @param {String} prTitleRegex 107 | * @param {Object} flags 108 | */ 109 | function checkPrTitle(pullRequestTitle, prTitleRegex, flags, configuration) { 110 | /** 111 | * Check pull request title format 112 | */ 113 | const validPullRequestMessage = configuration.VALID_PULL_REQUEST_MESSAGE || messages.valid_pull_request_message; 114 | const invalidPullRequestMessage = configuration.INVALID_PULL_REQUEST_MESSAGE || messages.invalid_pull_request_message; 115 | 116 | if (checkRegex(pullRequestTitle, prTitleRegex)) { 117 | flags.pullReqTitleStatus = true; 118 | flags.pullReqTitleStatusMsg = validPullRequestMessage; 119 | } else { 120 | /** 121 | * Invalid pull Request title 122 | */ 123 | flags.pullReqTitleStatus = false; 124 | flags.pullReqTitleStatusMsg = invalidPullRequestMessage; 125 | } 126 | } 127 | 128 | /** 129 | * Match commit messages with regex 130 | * @param {Array} commits 131 | * @param {Array} commitIds 132 | * @param {String} commitMsgRegex 133 | * @param {String} mergeCommitRegex 134 | * @param {Object} flags 135 | */ 136 | function checkCommitMessages(commits, commitIds, commitMsgRegex, mergeCommitRegex, flags, configuration) { 137 | /** 138 | * Check all commit messages 139 | */ 140 | const commitMsgStatusMsg = configuration.INVALID_COMMIT_MESSAGE || messages.invalid_commit_message; 141 | const singleOtherInvalidMessage = configuration.SINGLE_OTHER_INVALID_MESSAGE || messages.single_other_invalid_message; 142 | const multipleOtherInvalidMessage = configuration.MULTIPLE_OTHER_INVALID_MESSAGE || messages.multiple_other_invalid_message; 143 | 144 | for (let index = 0; index < commits.length; index++) { 145 | const element = commits[index]; 146 | const commitMessage = element.commit.message; 147 | commitIds.push(commits[index].sha); 148 | if (!checkRegex(commitMessage, commitMsgRegex) && !checkRegex(commitMessage, mergeCommitRegex)) { 149 | flags.invalidCommitsCount++; 150 | flags.commitMsgStatus = false; 151 | flags.commitMsgStatusMsg = commitMsgStatusMsg; 152 | if (flags.invalidCommitsCount <= constants.INVALID_COMMIT_LIMIT) { 153 | flags.invalidCommits += `${constants.invalid_commit_list.commit_id} ${commits[index].sha} | ${constants.invalid_commit_list.commit_message} ${commitMessage}
`; 154 | if (flags.invalidCommitsCount === 1) { 155 | flags.otherInvalidCommitMessages = singleOtherInvalidMessage; 156 | } else { 157 | flags.otherInvalidCommitMessages = multipleOtherInvalidMessage; 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Conclude check run params according to regex match status 166 | * @param {String} prTitleRegex 167 | * @param {String} commitMsgRegex 168 | * @param {Array} commitIds 169 | * @param {Object} flags 170 | */ 171 | function concludeCheckRunParams(prTitleRegex, commitMsgRegex, commitIds, flags, configuration) { 172 | let checkRunParams = {}; 173 | let output = {}; 174 | outputTitleFail = configuration.OUTPUT_TITLE_FAIL || outputTitleFail; 175 | outputTitleSuccess = configuration.OUTPUT_TITLE_SUCCESS || outputTitleSuccess; 176 | console.log('outputTitleFail', outputTitleFail) 177 | console.log('outputTitleSuccess', outputTitleSuccess) 178 | let outputTitle = outputTitleFail; 179 | let conclusion = conclusionStatus.FAILURE; 180 | /** 181 | * check if both the messages are valid for regex 182 | */ 183 | if (flags.pullReqTitleStatus && flags.commitMsgStatus) { 184 | conclusion = conclusionStatus.SUCCESS; 185 | outputTitle = outputTitleSuccess; 186 | } 187 | 188 | /** 189 | * set check run status 190 | */ 191 | output = { 192 | title: outputTitle, 193 | summary: `${flags.pullReqTitleStatusMsg}
${flags.commitMsgStatusMsg}
${flags.invalidCommits}
` 194 | }; 195 | let status = checkRunStatusCompleted; 196 | if (!prTitleRegex && !commitMsgRegex) { 197 | /** 198 | * Pull request and commit message configration regex not set 199 | */ 200 | output.title = `${messages.pr_and_commit_message_configuration_not_set}`; 201 | output.summary = `${messages.pr_and_commit_message_configuration_not_set}
`; 202 | } else if (!commitMsgRegex) { 203 | /** 204 | * Commit message configration regex not set 205 | */ 206 | output.title = `${messages.commit_message_configuration_not_set}`; 207 | output.summary = `${flags.pullReqTitleStatusMsg}
${messages.commit_message_configuration_not_set}
`; 208 | } else if (!prTitleRegex) { 209 | /** 210 | * Pull request configration regex not set 211 | */ 212 | output.title = `${messages.pr_configuration_not_set}`; 213 | output.summary = `${messages.pr_configuration_not_set}
${flags.commitMsgStatusMsg}
${flags.invalidCommits}
`; 214 | } 215 | /** 216 | * Set invalid commit messages and count 217 | */ 218 | if (flags.invalidCommitsCount && flags.invalidCommitsCount >= constants.INVALID_COMMIT_LIMIT) { 219 | output.summary += `${flags.invalidCommitsCount} ${flags.otherInvalidCommitMessages}`; 220 | } 221 | checkRunParams = { 222 | commitIds, 223 | status, 224 | checkRunName, 225 | conclusion, 226 | output 227 | }; 228 | return checkRunParams; 229 | } 230 | 231 | /** 232 | * Extractor pull request details from context 233 | * @param {Object} context 234 | */ 235 | function prDetailsExtractor(context) { 236 | let result = { 237 | owner: '', 238 | repository: '', 239 | pullRequestTitle: '', 240 | pullNumber: 0 241 | }; 242 | if (context.payload) { 243 | /** 244 | * Extract repository details 245 | */ 246 | if (context.payload.repository) { 247 | if (context.payload.repository.owner && context.payload.repository.owner.login) { 248 | result.owner = context.payload.repository.owner.login; 249 | } 250 | if (context.payload.repository.name) { 251 | result.repository = context.payload.repository.name; 252 | } 253 | } 254 | /** 255 | * Extract PR title and pull number 256 | */ 257 | if (context.payload.pull_request && context.payload.pull_request.title) { 258 | result.pullRequestTitle = context.payload.pull_request.title; 259 | } 260 | if (context.payload.number) { 261 | result.pullNumber = context.payload.number; 262 | } else if (context.payload.check_run && context.payload.check_run.check_suite && context.payload.check_run.check_suite.pull_requests && context.payload.check_run.check_suite.pull_requests.length && context.payload.check_run.check_suite.pull_requests[0].number) { 263 | result.pullNumber = context.payload.check_run.check_suite.pull_requests[0].number; 264 | } else if (context.payload.check_suite && context.payload.check_suite.pull_requests.length && context.payload.check_suite.pull_requests[0].number) { 265 | result.pullNumber = context.payload.check_suite.pull_requests[0].number; 266 | } 267 | } 268 | return result; 269 | } 270 | 271 | /** 272 | * Extract regex from configuration file in .github folder in user's repository 273 | * @param {Object} configuration 274 | */ 275 | function regexExtractor(configuration) { 276 | let result = { 277 | prTitleRegex: '', 278 | commitTitleRegex: '' 279 | }; 280 | result.prTitleRegex = (configuration && configuration.PR_TITLE_REGEX) ? configuration.PR_TITLE_REGEX : ''; 281 | result.commitTitleRegex = (configuration && configuration.COMMIT_MESSAGE_REGEX) ? configuration.COMMIT_MESSAGE_REGEX : ''; 282 | return result; 283 | } 284 | 285 | /** 286 | * Create Or Update Check Run as per conditions 287 | * @param {Object} context 288 | * @param {String} owner 289 | * @param {String} repository 290 | * @param {Object} result 291 | * @param {boolean} updateCheckRunFlag 292 | * @param {boolean} createCheckRunFlag 293 | */ 294 | async function createOrUpdateCheckRun(context, owner, repository, result, updateCheckRunFlag, createCheckRunFlag) { 295 | if (result && result.commitIds && Array.isArray(result.commitIds) && result.commitIds.length) { 296 | for (let index = 0; index < result.commitIds.length; index++) { 297 | const commitId = result.commitIds[index]; 298 | if (updateCheckRunFlag) { 299 | /** 300 | * Update existing check run 301 | */ 302 | const checkRunId = context.payload.check_run.id; 303 | await updateCheckRun(context, owner, repository, commitId, result.status, result.conclusion, result.output, checkRunId); 304 | } else if (createCheckRunFlag) { 305 | /** 306 | * Create check new run 307 | */ 308 | /** 309 | * check if checkSuite exists or not for the commit 310 | */ 311 | let checkSuiteList = await listCheckSuite(context, owner, repository, commitId); 312 | if (!checkSuiteList || (checkSuiteList && checkSuiteList.data && checkSuiteList.data.total_count && checkSuiteList.data.total_count === 0)) { 313 | /** 314 | * create check suite for a particular commit 315 | */ 316 | await createCheckSuite(context, owner, repository, commitId); 317 | } 318 | /** 319 | * create check run 320 | */ 321 | await createCheckRun(context, owner, repository, commitId, result.status, result.checkRunName, result.conclusion, result.output); 322 | } 323 | } 324 | } 325 | } -------------------------------------------------------------------------------- /helpers/checks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * create Check Suite 3 | * @param {Object} context 4 | * @param {String} owner 5 | * @param {String} repo 6 | * @param {String} commitId 7 | */ 8 | module.exports.createCheckSuite = async (context, owner, repo, commitId) => { 9 | try { 10 | let params = { 11 | owner: owner, 12 | 13 | repo: repo, 14 | /** 15 | * The sha of the head commit. 16 | */ 17 | head_sha: commitId, 18 | }; 19 | let checkSuite = await context.github.checks.createSuite(params); 20 | return checkSuite; 21 | } catch (error) { 22 | return error; 23 | } 24 | }; 25 | 26 | /** 27 | * create CheckRun for a commit 28 | * @param {Object} context 29 | * @param {String} owner 30 | * @param {String} repo 31 | * @param {String} commitId 32 | * @param {String} status 33 | * @param {String} checkRunName 34 | * @param {String} conclusion 35 | */ 36 | module.exports.createCheckRun = async (context, owner, repo, commitId, status, checkRunName, conclusion, output) => { 37 | try { 38 | let params = { 39 | owner: owner, 40 | repo: repo, 41 | /** 42 | * The name of the check. For example, "code-coverage". 43 | */ 44 | name: checkRunName, 45 | /** 46 | * The SHA of the commit. 47 | */ 48 | head_sha: commitId, 49 | /** 50 | * The current status. Can be one of `queued`, `in_progress`, or `completed`. 51 | */ 52 | status: status, 53 | /** 54 | * The time that the check run began in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`. 55 | */ 56 | started_at: new Date().toISOString(), 57 | conclusion: conclusion, 58 | completed_at: new Date().toISOString(), 59 | output: output 60 | }; 61 | let checkRun = await context.github.checks.create(params); 62 | return checkRun; 63 | } catch (error) { 64 | return error; 65 | } 66 | }; 67 | 68 | /** 69 | * list Check Suite for a commit 70 | * @param {Object} context 71 | * @param {String} owner 72 | * @param {String} repo 73 | * @param {String} ref 74 | */ 75 | module.exports.listCheckSuite = async (context, owner, repo, ref) => { 76 | try { 77 | let params = { 78 | owner: owner, 79 | repo: repo, 80 | ref: ref 81 | }; 82 | let checkRun = await context.github.checks.listSuitesForRef(params); 83 | return checkRun; 84 | } catch (error) { 85 | return error; 86 | } 87 | }; 88 | 89 | /** 90 | * Update CheckRun for a commit 91 | * @param {Object} context 92 | * @param {String} owner 93 | * @param {String} repo 94 | * @param {String} commitId 95 | * @param {String} status 96 | * @param {String} checkRunName 97 | * @param {String} conclusion 98 | */ 99 | module.exports.updateCheckRun = async (context, owner, repo, commitId, status, conclusion, output, checkRunId) => { 100 | try { 101 | let params = { 102 | check_run_id: checkRunId, 103 | owner: owner, 104 | repo: repo, 105 | /** 106 | * The name of the check. For example, "code-coverage". 107 | */ 108 | name: context.payload.check_run.name, 109 | /** 110 | * The SHA of the commit. 111 | */ 112 | head_sha: commitId, 113 | /** 114 | * The current status. Can be one of `queued`, `in_progress`, or `completed`. 115 | */ 116 | status: status, 117 | /** 118 | * The time that the check run began in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`. 119 | */ 120 | started_at: new Date().toISOString(), 121 | conclusion: conclusion, 122 | completed_at: new Date().toISOString(), 123 | output: output 124 | }; 125 | await context.github.checks.update(params); 126 | } catch (error) { 127 | return error; 128 | } 129 | }; 130 | 131 | /** 132 | * list Check runs for a check suite 133 | * @param {Object} context 134 | * @param {String} owner 135 | * @param {String} repo 136 | * @param {String} checkSuiteId 137 | */ 138 | module.exports.listCheckSuite = async (context, owner, repo, checkSuiteId) => { 139 | try { 140 | let params = { 141 | owner: owner, 142 | repo: repo, 143 | check_suite_id: checkSuiteId 144 | }; 145 | let checkRunList = await context.github.checks.listForSuite(params); 146 | return checkRunList; 147 | } catch (error) { 148 | return error; 149 | } 150 | }; 151 | 152 | /** 153 | * list of check runs For a check Suite 154 | * @param {Object} context 155 | * @param {String} owner 156 | * @param {String} repo 157 | * @param {String} checkSuiteId 158 | */ 159 | module.exports.listForSuite = async (context, owner, repo, checkSuiteId) => { 160 | try { 161 | let params = { 162 | owner: owner, 163 | repo: repo, 164 | check_suite_id: checkSuiteId 165 | }; 166 | let listForSuite = await context.github.checks.listForSuite(params); 167 | return listForSuite; 168 | } catch (error) { 169 | return error; 170 | } 171 | }; 172 | -------------------------------------------------------------------------------- /helpers/pullRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * list Commits Of Pull Request 3 | * @param {Object} context 4 | * @param {String} owner 5 | * @param {String} repo 6 | * @param {String} pullNumber 7 | * @param {String} perPage 8 | * @param {String} pageNo 9 | */ 10 | module.exports.listCommitsOfPullRequest = async (context, owner, repo, pullNumber, perPage, pageNo) => { 11 | try { 12 | let params = { 13 | owner: owner, 14 | repo: repo, 15 | pull_number: pullNumber 16 | }; 17 | if (perPage) { 18 | params.per_page = perPage; 19 | } 20 | if (pageNo) { 21 | params.page = pageNo; 22 | } 23 | // find commits 24 | let commits = await context.github.pullRequests.listCommits(params); 25 | return commits; 26 | } catch (error) { 27 | return error; 28 | } 29 | }; 30 | 31 | /** 32 | * Get Pull Request 33 | * @param {Object} context 34 | * @param {String} owner 35 | * @param {String} repo 36 | * @param {String} pullNumber 37 | */ 38 | module.exports.getPullRequest = async (context, owner, repo, pullNumber) => { 39 | try { 40 | let params = { 41 | owner: owner, 42 | repo: repo, 43 | pull_number: pullNumber 44 | }; 45 | // find PR 46 | let pullRequestDetails = await context.github.pullRequests.get(params); 47 | return pullRequestDetails; 48 | } catch (error) { 49 | return error; 50 | } 51 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Packages 3 | */ 4 | const express = require('express'); 5 | const expressApp = express(); 6 | const path = require('path'); 7 | const bodyParser = require('body-parser'); 8 | const { 9 | verifyWebhookData 10 | } = require('./middleware/verifyWebhooks'); 11 | /** 12 | * Controllers 13 | */ 14 | const { 15 | commitAndTitleValidator 16 | } = require('./controllers/pullRequest'); 17 | const { 18 | marketplaceEventHandlers, 19 | getAccessToken 20 | } = require('./controllers/marketplace'); 21 | const { 22 | listForSuite 23 | } = require('./controllers/checks'); 24 | 25 | /** 26 | * Constants 27 | */ 28 | const { 29 | configFileName, 30 | messages, 31 | events 32 | } = require('./constants.js'); 33 | const publicDirectory = path.join(`${__dirname}`, 'public'); 34 | 35 | /** 36 | * This is the main entrypoint to Probot app 37 | * @param {import('probot').Application} app 38 | */ 39 | module.exports = async app => { 40 | /** 41 | * Created pull request event listener 42 | */ 43 | app.on(events.PULL_REQUEST_OPEN, async (context) => { 44 | try { 45 | const configuration = await context.config(configFileName); 46 | await commitAndTitleValidator(app, context, configuration, false, true); 47 | } catch (error) { 48 | console.log('Error while performing operation in Event PULL_REQUEST_OPEN', error); 49 | app.log(error); 50 | } 51 | }); 52 | /** 53 | * Check re-run event listener 54 | */ 55 | app.on(events.CHECK_RUN_REREQUESTED, async (context) => { 56 | try { 57 | const configuration = await context.config(configFileName); 58 | await commitAndTitleValidator(app, context, configuration, true, false); 59 | } catch (error) { 60 | console.log('Error while performing operation in Event CHECK_RUN_REREQUESTED', error); 61 | app.log(error); 62 | } 63 | }); 64 | /** 65 | * Re-run all checks (Check Suite Re-requested) event listener 66 | */ 67 | app.on(events.CHECK_SUITE_REREQUESTED, async (context) => { 68 | try { 69 | const listOfCheckRuns = await listForSuite(app, context); 70 | context.payload.check_run = { 71 | id: listOfCheckRuns.data.check_runs[0].id 72 | }; 73 | const configuration = await context.config(configFileName); 74 | await commitAndTitleValidator(app, context, configuration, true, false); 75 | } catch (error) { 76 | console.log('Error while performing operation in Event CHECK_SUITE_REREQUESTED', error); 77 | app.log(error); 78 | } 79 | }); 80 | /** 81 | * Run all checks (Check Suite Requested) event listener 82 | */ 83 | app.on(events.CHECK_SUITE_REQUESTED, async (context) => { 84 | try { 85 | const configuration = await context.config(configFileName); 86 | await commitAndTitleValidator(app, context, configuration, false, true); 87 | } catch (error) { 88 | console.log('Error while performing operation in Event CHECK_SUITE_REQUESTED', error); 89 | app.log(error); 90 | } 91 | }); 92 | 93 | app.on(events.CHECK_SUITE, async context => { 94 | try { 95 | const configuration = await context.config(configFileName); 96 | await commitAndTitleValidator(app, context, configuration, false, true); 97 | } catch (error) { 98 | console.log('Error while performing operation in Event CHECK_SUITE', error); 99 | app.log(error); 100 | } 101 | }); 102 | 103 | /** 104 | * Middlewares 105 | */ 106 | expressApp.use(bodyParser.json()); 107 | /** 108 | * Web APIs 109 | */ 110 | /** 111 | * Home page API 112 | */ 113 | expressApp.get('/', (req, res) => { 114 | res.send(messages.home_page_message); 115 | }); 116 | /** 117 | * Privacy page API 118 | */ 119 | expressApp.get('/privacy', (req, res) => { 120 | res.sendFile(path.join(`${publicDirectory}`, 'privacy.html')); 121 | }); 122 | /** 123 | * Marketplace API 124 | * This API gets hit by github on any marketplace event 125 | */ 126 | expressApp.post('/marketplace', verifyWebhookData, marketplaceEventHandlers); 127 | 128 | /** 129 | * This API gets hit when redirected by `POST: /marketplace` API 130 | * This API exchanges code with github for accessToken 131 | */ 132 | expressApp.get('/auth', getAccessToken); 133 | 134 | app.router.use('/', expressApp); 135 | }; -------------------------------------------------------------------------------- /middleware/verifyWebhooks.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | const secret = process.env.WEBHOOK_SECRET; 3 | const sigHeaderName = 'x-hub-signature' 4 | 5 | module.exports.verifyWebhookData = function(req, res, next) { 6 | const payload = JSON.stringify(req.body) 7 | if (!payload) { 8 | return next('Request body empty') 9 | } 10 | 11 | const sig = req.get(sigHeaderName) || '' 12 | const hmac = crypto.createHmac('sha1', secret) 13 | const digest = Buffer.from('sha1=' + hmac.update(payload).digest('hex'), 'utf8') 14 | const checksum = Buffer.from(sig, 'utf8') 15 | if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) { 16 | return next(`Request body digest (${digest}) did not match ${sigHeaderName} (${checksum})`) 17 | } 18 | return next() 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commit-message-lint-app", 3 | "version": "3.1.1", 4 | "description": "An app for validating commit messages and pull requests title", 5 | "author": "Anshul Soni ", 6 | "license": "ISC", 7 | "repository": "https://github.com/SystangoTechnologies/commit-message-lint.git", 8 | "homepage": "https://github.com/SystangoTechnologies/commit-message-lint", 9 | "bugs": "https://github.com/SystangoTechnologies/commit-message-lint/issues", 10 | "keywords": [ 11 | "probot", 12 | "github", 13 | "probot-app", 14 | "commit-message-lint" 15 | ], 16 | "scripts": { 17 | "dev": "nodemon", 18 | "start": "probot run ./index.js", 19 | "lint": "standard --fix" 20 | }, 21 | "dependencies": { 22 | "handlebars": "^4.7.2", 23 | "lodash": "^4.17.14", 24 | "moment": "^2.24.0", 25 | "pg": "^7.12.1", 26 | "probot": "^9.5.0", 27 | "request": "^2.88.0", 28 | "request-promise": "^4.2.4" 29 | }, 30 | "devDependencies": { 31 | "jest": "^24.0.0", 32 | "nock": "^10.0.0", 33 | "nodemon": "^1.17.2", 34 | "smee-client": "^1.0.2", 35 | "standard": "^12.0.1" 36 | }, 37 | "engines": { 38 | "node": ">= 8.3.0" 39 | }, 40 | "standard": { 41 | "env": [ 42 | "jest" 43 | ] 44 | }, 45 | "nodemonConfig": { 46 | "exec": "npm start", 47 | "watch": [ 48 | ".env", 49 | "." 50 | ] 51 | }, 52 | "jest": { 53 | "testEnvironment": "node" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /public/marketplacePurchase.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Privacy Policy 4 | 5 | 6 | 7 |

Commit Message Validator

8 |

Privacy Policy

9 |
    10 |
  • 11 |

    We capture

    12 |

    13 |

      14 |
    • Github Installation ID
    • 15 |
    • Name, ID and URL of each repositories you have given rights
    • 16 |
    • Branch name / SHA / Checksuite ID / Checkrun ID / Status of all Pulls Requests and commits you have given rights
    • 17 |
    • Commit messages and Pull request titles
    • 18 |
    19 |

    20 |
  • 21 |
  • 22 |

    We save

    23 |

    24 |

      25 |
    • We do not save or persist anything. As soon as the app finishes it's processing, we delete everything.
    • 26 |
    27 |

    28 |
  • 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /public/privacy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Privacy Policy 4 | 5 | 6 | 7 |

Commit Message Validator

8 |

Privacy Policy

9 |
    10 |
  • 11 |

    We capture

    12 |

    13 |

      14 |
    • Github Installation ID
    • 15 |
    • Name, ID and URL of each repositories you have given rights
    • 16 |
    • Branch name / SHA / Checksuite ID / Checkrun ID / Status of all Pulls Requests and commits you have given rights
    • 17 |
    • Commit messages and Pull request titles
    • 18 |
    19 |

    20 |
  • 21 |
  • 22 |

    We save

    23 |

    24 |

      25 |
    • We do not save or persist anything. As soon as the app finishes it's processing, we delete everything.
    • 26 |
    27 |

    28 |
  • 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "8.3" 5 | notifications: 6 | disabled: true 7 | -------------------------------------------------------------------------------- /utilities/dataConverter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * base64 To String converter 3 | * @param {string} data 4 | */ 5 | module.exports.base64ToString = (data) => { 6 | try { 7 | let buff = new Buffer(data, 'base64'); 8 | return buff.toString(); 9 | } catch (error) { 10 | return error; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /utilities/regexUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Match input string with regex 3 | * @param {String} inputString 4 | * @param {String} pattern 5 | */ 6 | module.exports.checkRegex = (inputString, pattern) => { 7 | try { 8 | let patt = ''; 9 | let status = false; 10 | if (pattern) { 11 | let regexObj = module.exports.regexExtractor(pattern); 12 | patt = new RegExp(regexObj.regexPattern, regexObj.flags); 13 | status = patt.test(inputString); 14 | } else { 15 | status = true; 16 | } 17 | return status; 18 | } catch (error) { 19 | return false; 20 | } 21 | }; 22 | 23 | /** 24 | * Extracts regex pattern and flags from input string 25 | * @param {String} rawRegexString contains regex 26 | */ 27 | module.exports.regexExtractor = (rawRegexString) => { 28 | try { 29 | let result = { 30 | regexPattern: '', 31 | flags: '' 32 | }; 33 | let splited = rawRegexString.split('/'); 34 | result.flags = splited[splited.length - 1]; 35 | splited[splited.length - 1] = ''; 36 | result.regexPattern = splited.join(''); 37 | return result; 38 | } catch (error) { 39 | return error; 40 | } 41 | }; 42 | --------------------------------------------------------------------------------