[
  {
    "path": ".github/workflows/main.yml",
    "content": "name: main\n# This workflow is triggered on pushes to the repository.\non:\n  pull_request:\n    types: [opened, edited, synchronize, reopened]\n\njobs:\n  build:\n    # Job name is Greeting\n    name: Check\n    # This job runs on Linux\n    runs-on: 'ubuntu-latest'\n    steps:\n      - uses: actions/checkout@master\n      - uses: actions/setup-node@v1\n        with:\n          node-version: '20'\n      - run: npm install\n      - name: Validate\n        uses: ./\n        with:\n          github_token: ${{ github.token }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018 GitHub, Inc. and contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Branch naming rules\n<img alt=\"GitHub Actions status\" src=\"https://github.com/deepakputhraya/action-pr-title/workflows/main/badge.svg\">\n\nGithub action to enforce Pull Request title conventions\n\n## Usage\n\nSee [action.yml](./action.yml)\n\n```yaml\nsteps:\n- uses: deepakputhraya/action-pr-title@master\n  with:\n    regex: '([a-z])+\\/([a-z])+' # Regex the title should match.\n    allowed_prefixes: 'feature,fix,JIRA' # title should start with the given prefix\n    disallowed_prefixes: 'feat/,hotfix' # title should not start with the given prefix\n    prefix_case_sensitive: false # title prefix are case insensitive\n    min_length: 5 # Min length of the title\n    max_length: 20 # Max length of the title\n    verbal_description: 'Two words with a slash (/) between' # Verbal description of the regex rule\n    github_token: ${{ github.token }} # Default: ${{ github.token }}\n```\n\n### Note:\nEnsure to add `types` to the Pull requests webhook event as by default workflows are triggered only \nfor `opened`, `synchronize`, or `reopened` pull request events. Read more about \nit [here](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#pull_request). \n```yaml\non:\n  pull_request:\n    types: [opened, edited, synchronize, reopened]\n```\nor \n[here](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#pull_request_target). \n```yaml\non:\n  pull_request_target:\n    types: [opened, edited, synchronize, reopened]\n```\n\nTriggering the action on anything other than `pull_request` or `pull_request_target` will cause a failure.\n\n## Permissions\n\nIn case the action fails with the following error:\n\n```\nEvent name: pull_request\nError: Resource not accessible by integration\n```\n\nYou can fix this, by adding the following to your workflow:\n\n\n```yaml\npermissions:\n  pull-requests: read\n```\n\n## License\nThe scripts and documentation in this project are released under the [MIT License](./LICENSE)\n"
  },
  {
    "path": "action.yml",
    "content": "name: 'Pull Request title rules'\ndescription: 'Github action to enforce Pull Request title conventions'\nauthor: 'deepakputhraya'\ninputs:\n  regex:\n    description: 'Regex to validate the pull request title'\n    required: false\n    default: '.+'\n  allowed_prefixes:\n    description: 'Comma separated list of prefix allowed to be used in title. eg: feature,hotfix,JIRA-'\n    required: false\n    default: ''\n  disallowed_prefixes:\n    description: 'Comma separated list of prefix disallowed to be used in title. eg: feat,fix'\n    required: false\n    default: ''\n  prefix_case_sensitive:\n    description: 'Are the allowed & disallowed prefixes case sensitive?'\n    required: false\n    default: 'false'\n  min_length:\n    description: 'Min length of title'\n    required: false\n    default: '1'\n  max_length:\n    description: 'Max length of title. -1 to ignore the rule'\n    required: false\n    default: '-1'\n  verbal_description:\n    description: 'Description of the title rules to display when the action fails'\n    required: false\n    default: ''\n  github_token:\n    description: >\n      Personal access token (PAT) used to fetch the repository. The PAT is configured\n      with the local git config, which enables your scripts to run authenticated git\n      commands. The post-job step removes the PAT.\n      We recommend using a service account with the least permissions necessary.\n      Also when generating a new PAT, select the least scopes necessary.\n    required: false\n    default: ${{ github.token }}\n\nruns:\n  using: 'node20'\n  main: 'index.js'\nbranding:\n  icon: 'alert-triangle'\n  color: 'gray-dark'\n"
  },
  {
    "path": "index.js",
    "content": "const core = require('@actions/core');\nconst github = require('@actions/github');\n\nconst validEvent = ['pull_request','pull_request_target'];\n\nfunction validateTitlePrefix(title, prefix, caseSensitive) {\n    if (!caseSensitive) {\n        prefix = prefix.toLowerCase();\n        title = title.toLowerCase();\n    }\n    return title.startsWith(prefix);\n}\n\nasync function run() {\n    try {\n        const verbalDescription = core.getInput('verbal_description')\n        const authToken = core.getInput('github_token', {required: true})\n        const eventName = github.context.eventName;\n        core.info(`Event name: ${eventName}`);\n        if (validEvent.indexOf(eventName) < 0) {\n            core.setFailed(`Invalid event: ${eventName}`);\n            return;\n        }\n\n        const owner = github.context.payload.pull_request.base.user.login;\n        const repo = github.context.payload.pull_request.base.repo.name;\n\n        const client = new github.getOctokit(authToken);\n        // The pull request info on the context isn't up to date. When\n        // the user updates the title and re-runs the workflow, it would\n        // be outdated. Therefore fetch the pull request via the REST API\n        // to ensure we use the current title.\n        const {data: pullRequest} = await client.rest.pulls.get({\n          owner,\n          repo,\n          pull_number: github.context.payload.pull_request.number\n        });\n\n        const title = pullRequest.title;\n\n        core.info(`Pull Request title: \"${title}\"`);\n\n        // Check if title pass regex\n        const regex = RegExp(core.getInput('regex'));\n        if (!regex.test(title)) {\n            const messageSuffix = verbalDescription ? `rule - ${verbalDescription}` : `match regex - ${regex}`;\n\n            core.setFailed(`Pull Request title \"${title}\" failed to pass ${messageSuffix}`);\n            return\n        }\n\n        // Check min length\n        const minLen = parseInt(core.getInput('min_length'));\n        if (title.length < minLen) {\n            core.setFailed(`Pull Request title \"${title}\" is smaller than min length specified - ${minLen}`);\n            return\n        }\n\n        // Check max length\n        const maxLen = parseInt(core.getInput('max_length'));\n        if (maxLen > 0 && title.length > maxLen) {\n            core.setFailed(`Pull Request title \"${title}\" is greater than max length specified - ${maxLen}`);\n            return\n        }\n\n        // Check if title starts with an allowed prefix\n        let prefixes = core.getInput('allowed_prefixes');\n        const prefixCaseSensitive = (core.getInput('prefix_case_sensitive') === 'true');\n        core.info(`Allowed Prefixes: ${prefixes}`);\n        if (prefixes.length > 0 && !prefixes.split(',').some((el) => validateTitlePrefix(title, el, prefixCaseSensitive))) {\n            core.setFailed(`Pull Request title \"${title}\" did not match any of the prefixes - ${prefixes}`);\n            return\n        }\n\n        // Check if title starts with a disallowed prefix\n        prefixes = core.getInput('disallowed_prefixes');\n        core.info(`Disallowed Prefixes: ${prefixes}`);\n        if (prefixes.length > 0 && prefixes.split(',').some((el) => validateTitlePrefix(title, el, prefixCaseSensitive))) {\n            core.setFailed(`Pull Request title \"${title}\" matched with a disallowed prefix - ${prefixes}`);\n            return\n        }\n\n    } catch (error) {\n        core.setFailed(error.message);\n    }\n}\n\nrun();\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"action-pr-title\",\n  \"version\": \"1.3.0\",\n  \"description\": \"Github action to enforce naming convention on Pull Request titles\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"node index.js\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"keywords\": [\n    \"github\",\n    \"actions\"\n  ],\n  \"author\": \"deepakputhraya\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@actions/core\": \"^1.11.1\",\n    \"@actions/github\": \"^6.0.0\"\n  }\n}\n"
  }
]