├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── basic-validation.yml │ ├── check-dist.yml │ ├── codeql-analysis.yml │ ├── licensed.yml │ ├── publish-immutable-actions.yml │ ├── release-new-action-version.yml │ └── update-config-files.yml ├── .gitignore ├── .licensed.yml ├── .licenses └── npm │ ├── @actions │ ├── core.dep.yml │ ├── exec.dep.yml │ ├── github.dep.yml │ ├── http-client.dep.yml │ └── io.dep.yml │ ├── @fastify │ └── busboy.dep.yml │ ├── @octokit │ ├── auth-token.dep.yml │ ├── core.dep.yml │ ├── endpoint.dep.yml │ ├── graphql.dep.yml │ ├── openapi-types.dep.yml │ ├── plugin-paginate-rest.dep.yml │ ├── plugin-rest-endpoint-methods.dep.yml │ ├── plugin-retry.dep.yml │ ├── request-error.dep.yml │ ├── request.dep.yml │ └── types.dep.yml │ ├── argparse.dep.yml │ ├── balanced-match.dep.yml │ ├── before-after-hook.dep.yml │ ├── bottleneck.dep.yml │ ├── brace-expansion.dep.yml │ ├── deprecation.dep.yml │ ├── is-plain-object.dep.yml │ ├── js-yaml.dep.yml │ ├── lodash.isequal.dep.yml │ ├── minimatch.dep.yml │ ├── once.dep.yml │ ├── tunnel.dep.yml │ ├── undici.dep.yml │ ├── universal-user-agent.dep.yml │ ├── uuid.dep.yml │ └── wrappy.dep.yml ├── .prettierignore ├── .prettierrc.js ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __mocks__ └── @actions │ └── github.ts ├── __tests__ ├── branch.test.ts ├── changedFiles.test.ts ├── fixtures │ ├── all_options.yml │ ├── any_and_all.yml │ ├── branches.yml │ ├── not_supported.yml │ └── only_pdfs.yml ├── labeler.test.ts └── main.test.ts ├── action.yml ├── dist └── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── api │ ├── get-changed-files.ts │ ├── get-changed-pull-requests.ts │ ├── get-content.ts │ ├── get-label-configs.ts │ ├── index.ts │ ├── set-labels.ts │ └── types.ts ├── branch.ts ├── changedFiles.ts ├── get-inputs │ ├── get-inputs.ts │ ├── get-pr-numbers.ts │ └── index.ts ├── labeler.ts ├── main.ts └── utils.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore list 2 | /* 3 | 4 | # Do not ignore these folders: 5 | !__tests__/ 6 | !__mocks__/ 7 | !src/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update. 2 | module.exports = { 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:eslint-plugin-jest/recommended', 7 | 'eslint-config-prettier' 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | plugins: ['@typescript-eslint', 'eslint-plugin-node', 'eslint-plugin-jest'], 11 | rules: { 12 | '@typescript-eslint/no-require-imports': 'error', 13 | '@typescript-eslint/no-non-null-assertion': 'off', 14 | '@typescript-eslint/no-explicit-any': 'off', 15 | '@typescript-eslint/no-empty-function': 'off', 16 | '@typescript-eslint/ban-ts-comment': [ 17 | 'error', 18 | { 19 | 'ts-ignore': 'allow-with-description' 20 | } 21 | ], 22 | 'no-console': 'error', 23 | 'yoda': 'error', 24 | 'prefer-const': [ 25 | 'error', 26 | { 27 | destructuring: 'all' 28 | } 29 | ], 30 | 'no-control-regex': 'off', 31 | 'no-constant-condition': ['error', {checkLoops: false}], 32 | 'node/no-extraneous-import': 'error' 33 | }, 34 | overrides: [ 35 | { 36 | files: ['**/*{test,spec}.ts'], 37 | rules: { 38 | '@typescript-eslint/no-unused-vars': 'off', 39 | 'jest/no-standalone-expect': 'off', 40 | 'jest/no-conditional-expect': 'off', 41 | 'no-console': 'off', 42 | 43 | } 44 | } 45 | ], 46 | env: { 47 | node: true, 48 | es6: true, 49 | 'jest/globals': true 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | .licenses/** -diff linguist-generated=true 3 | 4 | # don't diff machine generated files 5 | dist/index.js -diff 6 | package-lock.json -diff 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report 4 | title: '' 5 | labels: bug, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | **Description:** 14 | A clear and concise description of what the bug is. 15 | 16 | **Action version:** 17 | Specify the action version 18 | 19 | **Platform:** 20 | - [ ] Ubuntu 21 | - [ ] macOS 22 | - [ ] Windows 23 | 24 | **Runner type:** 25 | - [ ] Hosted 26 | - [ ] Self-hosted 27 | 28 | **Repro steps:** 29 | A description with steps to reproduce the issue. If your have a public example or repo to share, please provide the link. 30 | 31 | **Expected behavior:** 32 | A description of what you expected to happen. 33 | 34 | **Actual behavior:** 35 | A description of what is actually happening. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature request, needs triage 6 | assignees: '' 7 | --- 8 | 9 | 10 | 11 | **Description:** 12 | Describe your proposal. 13 | 14 | **Justification:** 15 | Justification or a use case for your proposal. 16 | 17 | **Are you willing to submit a PR?** 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: 'npm' 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: '/' 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: 'daily' 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Description:** 2 | Describe your changes. 3 | 4 | **Related issue:** 5 | Add link to the related issue. 6 | 7 | **Check list:** 8 | - [ ] Mark if documentation changes are required. 9 | - [ ] Mark if tests were added or updated to cover the changes. -------------------------------------------------------------------------------- /.github/workflows/basic-validation.yml: -------------------------------------------------------------------------------- 1 | name: Basic validation 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - '**.md' 7 | push: 8 | branches: 9 | - main 10 | - releases/* 11 | paths-ignore: 12 | - '**.md' 13 | 14 | jobs: 15 | call-basic-validation: 16 | name: Basic validation 17 | uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@main 18 | with: 19 | node-version: '20.x' 20 | -------------------------------------------------------------------------------- /.github/workflows/check-dist.yml: -------------------------------------------------------------------------------- 1 | name: Check dist/ 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**.md' 9 | pull_request: 10 | paths-ignore: 11 | - '**.md' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | call-check-dist: 16 | name: Check dist/ 17 | uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main 18 | with: 19 | node-version: '20.x' 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL analysis 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | schedule: 9 | - cron: '0 3 * * 0' 10 | 11 | jobs: 12 | call-codeQL-analysis: 13 | name: CodeQL analysis 14 | uses: actions/reusable-workflows/.github/workflows/codeql-analysis.yml@main 15 | -------------------------------------------------------------------------------- /.github/workflows/licensed.yml: -------------------------------------------------------------------------------- 1 | name: Licensed 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | call-licensed: 14 | name: Licensed 15 | uses: actions/reusable-workflows/.github/workflows/licensed.yml@main 16 | -------------------------------------------------------------------------------- /.github/workflows/publish-immutable-actions.yml: -------------------------------------------------------------------------------- 1 | name: 'Publish Immutable Action Version' 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | packages: write 14 | 15 | steps: 16 | - name: Checking out 17 | uses: actions/checkout@v4 18 | - name: Publish 19 | id: publish 20 | uses: actions/publish-immutable-action@0.0.3 21 | -------------------------------------------------------------------------------- /.github/workflows/release-new-action-version.yml: -------------------------------------------------------------------------------- 1 | name: Release new action version 2 | 3 | on: 4 | release: 5 | types: [released] 6 | workflow_dispatch: 7 | inputs: 8 | TAG_NAME: 9 | description: 'Tag name that the major tag will point to' 10 | required: true 11 | 12 | env: 13 | TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | update_tag: 19 | name: Update the major tag to include the ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} changes 20 | environment: 21 | name: releaseNewActionVersion 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Update the ${{ env.TAG_NAME }} tag 25 | uses: actions/publish-action@v0.2.2 26 | with: 27 | source-tag: ${{ env.TAG_NAME }} 28 | slack-webhook: ${{ secrets.SLACK_WEBHOOK }} 29 | -------------------------------------------------------------------------------- /.github/workflows/update-config-files.yml: -------------------------------------------------------------------------------- 1 | name: Update configuration files 2 | 3 | on: 4 | schedule: 5 | - cron: '0 3 * * 0' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | call-update-configuration-files: 10 | name: Update configuration files 11 | uses: actions/reusable-workflows/.github/workflows/update-config-files.yml@main 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | lib/ 4 | .idea 5 | -------------------------------------------------------------------------------- /.licensed.yml: -------------------------------------------------------------------------------- 1 | sources: 2 | npm: true 3 | 4 | allowed: 5 | - apache-2.0 6 | - bsd-2-clause 7 | - bsd-3-clause 8 | - isc 9 | - mit 10 | - cc0-1.0 11 | - unlicense 12 | 13 | reviewed: 14 | npm: -------------------------------------------------------------------------------- /.licenses/npm/@actions/core.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/core" 3 | version: 1.11.1 4 | type: npm 5 | summary: Actions core lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/core 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/exec.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/exec" 3 | version: 1.1.1 4 | type: npm 5 | summary: Actions exec lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/exec 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | notices: [] -------------------------------------------------------------------------------- /.licenses/npm/@actions/github.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/github" 3 | version: 6.0.0 4 | type: npm 5 | summary: Actions github lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/github 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/http-client.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/http-client" 3 | version: 2.2.0 4 | type: npm 5 | summary: Actions Http Client 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/http-client 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Actions Http Client for Node.js 12 | 13 | Copyright (c) GitHub, Inc. 14 | 15 | All rights reserved. 16 | 17 | MIT License 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 20 | associated documentation files (the "Software"), to deal in the Software without restriction, 21 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 22 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 23 | subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 28 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 29 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/io.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/io" 3 | version: 1.1.3 4 | type: npm 5 | summary: Actions io lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/io 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | notices: [] -------------------------------------------------------------------------------- /.licenses/npm/@fastify/busboy.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@fastify/busboy" 3 | version: 2.0.0 4 | type: npm 5 | summary: A streaming parser for HTML form data for node.js 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: |- 11 | Copyright Brian White. All rights reserved. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to 15 | deal in the Software without restriction, including without limitation the 16 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 29 | IN THE SOFTWARE. 30 | notices: [] 31 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/auth-token.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/auth-token" 3 | version: 4.0.0 4 | type: npm 5 | summary: GitHub API token authentication for browsers and Node.js 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License 12 | 13 | Copyright (c) 2019 Octokit contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: "[MIT](LICENSE)" 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/core.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/core" 3 | version: 5.0.1 4 | type: npm 5 | summary: Extendable client for GitHub's REST & GraphQL APIs 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License 12 | 13 | Copyright (c) 2019 Octokit contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: "[MIT](LICENSE)" 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/endpoint.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/endpoint" 3 | version: 9.0.1 4 | type: npm 5 | summary: Turns REST API endpoints into generic request options 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License 12 | 13 | Copyright (c) 2018 Octokit contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: "[MIT](LICENSE)" 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/graphql.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/graphql" 3 | version: 7.0.2 4 | type: npm 5 | summary: GitHub GraphQL API client for browsers and Node 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License 12 | 13 | Copyright (c) 2018 Octokit contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: "[MIT](LICENSE)" 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/openapi-types.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/openapi-types" 3 | version: 19.0.1 4 | type: npm 5 | summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: |- 11 | Copyright 2020 Gregor Martynus 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | - sources: README.md 19 | text: "[MIT](LICENSE)" 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/plugin-paginate-rest.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/plugin-paginate-rest" 3 | version: 9.1.0 4 | type: npm 5 | summary: Octokit plugin to paginate REST API endpoint responses 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License Copyright (c) 2019 Octokit contributors 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | - sources: README.md 19 | text: "[MIT](LICENSE)" 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/plugin-rest-endpoint-methods.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/plugin-rest-endpoint-methods" 3 | version: 10.1.0 4 | type: npm 5 | summary: Octokit plugin adding one method for all of api.github.com REST API endpoints 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License Copyright (c) 2019 Octokit contributors 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | - sources: README.md 19 | text: "[MIT](LICENSE)" 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/plugin-retry.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/plugin-retry" 3 | version: 6.0.1 4 | type: npm 5 | summary: Automatic retry plugin for octokit 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License 12 | 13 | Copyright (c) 2018 Octokit contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: "[MIT](LICENSE)" 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/request-error.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/request-error" 3 | version: 5.0.1 4 | type: npm 5 | summary: Error class for Octokit request errors 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License 12 | 13 | Copyright (c) 2019 Octokit contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: "[MIT](LICENSE)" 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/request.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/request" 3 | version: 8.1.4 4 | type: npm 5 | summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers 6 | and Node 7 | homepage: 8 | license: mit 9 | licenses: 10 | - sources: LICENSE 11 | text: | 12 | The MIT License 13 | 14 | Copyright (c) 2018 Octokit contributors 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in 24 | all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | THE SOFTWARE. 33 | - sources: README.md 34 | text: "[MIT](LICENSE)" 35 | notices: [] 36 | -------------------------------------------------------------------------------- /.licenses/npm/@octokit/types.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@octokit/types" 3 | version: 12.1.0 4 | type: npm 5 | summary: Shared TypeScript definitions for Octokit projects 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License Copyright (c) 2019 Octokit contributors 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | - sources: README.md 19 | text: "[MIT](LICENSE)" 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/argparse.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: argparse 3 | version: 2.0.1 4 | type: npm 5 | summary: CLI arguments parser. Native port of python's argparse. 6 | homepage: https://github.com/nodeca/argparse#readme 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | A. HISTORY OF THE SOFTWARE 12 | ========================== 13 | 14 | Python was created in the early 1990s by Guido van Rossum at Stichting 15 | Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands 16 | as a successor of a language called ABC. Guido remains Python's 17 | principal author, although it includes many contributions from others. 18 | 19 | In 1995, Guido continued his work on Python at the Corporation for 20 | National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) 21 | in Reston, Virginia where he released several versions of the 22 | software. 23 | 24 | In May 2000, Guido and the Python core development team moved to 25 | BeOpen.com to form the BeOpen PythonLabs team. In October of the same 26 | year, the PythonLabs team moved to Digital Creations, which became 27 | Zope Corporation. In 2001, the Python Software Foundation (PSF, see 28 | https://www.python.org/psf/) was formed, a non-profit organization 29 | created specifically to own Python-related Intellectual Property. 30 | Zope Corporation was a sponsoring member of the PSF. 31 | 32 | All Python releases are Open Source (see http://www.opensource.org for 33 | the Open Source Definition). Historically, most, but not all, Python 34 | releases have also been GPL-compatible; the table below summarizes 35 | the various releases. 36 | 37 | Release Derived Year Owner GPL- 38 | from compatible? (1) 39 | 40 | 0.9.0 thru 1.2 1991-1995 CWI yes 41 | 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 42 | 1.6 1.5.2 2000 CNRI no 43 | 2.0 1.6 2000 BeOpen.com no 44 | 1.6.1 1.6 2001 CNRI yes (2) 45 | 2.1 2.0+1.6.1 2001 PSF no 46 | 2.0.1 2.0+1.6.1 2001 PSF yes 47 | 2.1.1 2.1+2.0.1 2001 PSF yes 48 | 2.1.2 2.1.1 2002 PSF yes 49 | 2.1.3 2.1.2 2002 PSF yes 50 | 2.2 and above 2.1.1 2001-now PSF yes 51 | 52 | Footnotes: 53 | 54 | (1) GPL-compatible doesn't mean that we're distributing Python under 55 | the GPL. All Python licenses, unlike the GPL, let you distribute 56 | a modified version without making your changes open source. The 57 | GPL-compatible licenses make it possible to combine Python with 58 | other software that is released under the GPL; the others don't. 59 | 60 | (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, 61 | because its license has a choice of law clause. According to 62 | CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 63 | is "not incompatible" with the GPL. 64 | 65 | Thanks to the many outside volunteers who have worked under Guido's 66 | direction to make these releases possible. 67 | 68 | 69 | B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON 70 | =============================================================== 71 | 72 | PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 73 | -------------------------------------------- 74 | 75 | 1. This LICENSE AGREEMENT is between the Python Software Foundation 76 | ("PSF"), and the Individual or Organization ("Licensee") accessing and 77 | otherwise using this software ("Python") in source or binary form and 78 | its associated documentation. 79 | 80 | 2. Subject to the terms and conditions of this License Agreement, PSF hereby 81 | grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, 82 | analyze, test, perform and/or display publicly, prepare derivative works, 83 | distribute, and otherwise use Python alone or in any derivative version, 84 | provided, however, that PSF's License Agreement and PSF's notice of copyright, 85 | i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 86 | 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; 87 | All Rights Reserved" are retained in Python alone or in any derivative version 88 | prepared by Licensee. 89 | 90 | 3. In the event Licensee prepares a derivative work that is based on 91 | or incorporates Python or any part thereof, and wants to make 92 | the derivative work available to others as provided herein, then 93 | Licensee hereby agrees to include in any such work a brief summary of 94 | the changes made to Python. 95 | 96 | 4. PSF is making Python available to Licensee on an "AS IS" 97 | basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 98 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND 99 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 100 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT 101 | INFRINGE ANY THIRD PARTY RIGHTS. 102 | 103 | 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 104 | FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 105 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, 106 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 107 | 108 | 6. This License Agreement will automatically terminate upon a material 109 | breach of its terms and conditions. 110 | 111 | 7. Nothing in this License Agreement shall be deemed to create any 112 | relationship of agency, partnership, or joint venture between PSF and 113 | Licensee. This License Agreement does not grant permission to use PSF 114 | trademarks or trade name in a trademark sense to endorse or promote 115 | products or services of Licensee, or any third party. 116 | 117 | 8. By copying, installing or otherwise using Python, Licensee 118 | agrees to be bound by the terms and conditions of this License 119 | Agreement. 120 | 121 | 122 | BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 123 | ------------------------------------------- 124 | 125 | BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 126 | 127 | 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an 128 | office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the 129 | Individual or Organization ("Licensee") accessing and otherwise using 130 | this software in source or binary form and its associated 131 | documentation ("the Software"). 132 | 133 | 2. Subject to the terms and conditions of this BeOpen Python License 134 | Agreement, BeOpen hereby grants Licensee a non-exclusive, 135 | royalty-free, world-wide license to reproduce, analyze, test, perform 136 | and/or display publicly, prepare derivative works, distribute, and 137 | otherwise use the Software alone or in any derivative version, 138 | provided, however, that the BeOpen Python License is retained in the 139 | Software, alone or in any derivative version prepared by Licensee. 140 | 141 | 3. BeOpen is making the Software available to Licensee on an "AS IS" 142 | basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 143 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND 144 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 145 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT 146 | INFRINGE ANY THIRD PARTY RIGHTS. 147 | 148 | 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE 149 | SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS 150 | AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY 151 | DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 152 | 153 | 5. This License Agreement will automatically terminate upon a material 154 | breach of its terms and conditions. 155 | 156 | 6. This License Agreement shall be governed by and interpreted in all 157 | respects by the law of the State of California, excluding conflict of 158 | law provisions. Nothing in this License Agreement shall be deemed to 159 | create any relationship of agency, partnership, or joint venture 160 | between BeOpen and Licensee. This License Agreement does not grant 161 | permission to use BeOpen trademarks or trade names in a trademark 162 | sense to endorse or promote products or services of Licensee, or any 163 | third party. As an exception, the "BeOpen Python" logos available at 164 | http://www.pythonlabs.com/logos.html may be used according to the 165 | permissions granted on that web page. 166 | 167 | 7. By copying, installing or otherwise using the software, Licensee 168 | agrees to be bound by the terms and conditions of this License 169 | Agreement. 170 | 171 | 172 | CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 173 | --------------------------------------- 174 | 175 | 1. This LICENSE AGREEMENT is between the Corporation for National 176 | Research Initiatives, having an office at 1895 Preston White Drive, 177 | Reston, VA 20191 ("CNRI"), and the Individual or Organization 178 | ("Licensee") accessing and otherwise using Python 1.6.1 software in 179 | source or binary form and its associated documentation. 180 | 181 | 2. Subject to the terms and conditions of this License Agreement, CNRI 182 | hereby grants Licensee a nonexclusive, royalty-free, world-wide 183 | license to reproduce, analyze, test, perform and/or display publicly, 184 | prepare derivative works, distribute, and otherwise use Python 1.6.1 185 | alone or in any derivative version, provided, however, that CNRI's 186 | License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 187 | 1995-2001 Corporation for National Research Initiatives; All Rights 188 | Reserved" are retained in Python 1.6.1 alone or in any derivative 189 | version prepared by Licensee. Alternately, in lieu of CNRI's License 190 | Agreement, Licensee may substitute the following text (omitting the 191 | quotes): "Python 1.6.1 is made available subject to the terms and 192 | conditions in CNRI's License Agreement. This Agreement together with 193 | Python 1.6.1 may be located on the Internet using the following 194 | unique, persistent identifier (known as a handle): 1895.22/1013. This 195 | Agreement may also be obtained from a proxy server on the Internet 196 | using the following URL: http://hdl.handle.net/1895.22/1013". 197 | 198 | 3. In the event Licensee prepares a derivative work that is based on 199 | or incorporates Python 1.6.1 or any part thereof, and wants to make 200 | the derivative work available to others as provided herein, then 201 | Licensee hereby agrees to include in any such work a brief summary of 202 | the changes made to Python 1.6.1. 203 | 204 | 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" 205 | basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 206 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND 207 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 208 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT 209 | INFRINGE ANY THIRD PARTY RIGHTS. 210 | 211 | 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 212 | 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 213 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, 214 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 215 | 216 | 6. This License Agreement will automatically terminate upon a material 217 | breach of its terms and conditions. 218 | 219 | 7. This License Agreement shall be governed by the federal 220 | intellectual property law of the United States, including without 221 | limitation the federal copyright law, and, to the extent such 222 | U.S. federal law does not apply, by the law of the Commonwealth of 223 | Virginia, excluding Virginia's conflict of law provisions. 224 | Notwithstanding the foregoing, with regard to derivative works based 225 | on Python 1.6.1 that incorporate non-separable material that was 226 | previously distributed under the GNU General Public License (GPL), the 227 | law of the Commonwealth of Virginia shall govern this License 228 | Agreement only as to issues arising under or with respect to 229 | Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this 230 | License Agreement shall be deemed to create any relationship of 231 | agency, partnership, or joint venture between CNRI and Licensee. This 232 | License Agreement does not grant permission to use CNRI trademarks or 233 | trade name in a trademark sense to endorse or promote products or 234 | services of Licensee, or any third party. 235 | 236 | 8. By clicking on the "ACCEPT" button where indicated, or by copying, 237 | installing or otherwise using Python 1.6.1, Licensee agrees to be 238 | bound by the terms and conditions of this License Agreement. 239 | 240 | ACCEPT 241 | 242 | 243 | CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 244 | -------------------------------------------------- 245 | 246 | Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, 247 | The Netherlands. All rights reserved. 248 | 249 | Permission to use, copy, modify, and distribute this software and its 250 | documentation for any purpose and without fee is hereby granted, 251 | provided that the above copyright notice appear in all copies and that 252 | both that copyright notice and this permission notice appear in 253 | supporting documentation, and that the name of Stichting Mathematisch 254 | Centrum or CWI not be used in advertising or publicity pertaining to 255 | distribution of the software without specific, written prior 256 | permission. 257 | 258 | STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO 259 | THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 260 | FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE 261 | FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 262 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 263 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 264 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 265 | notices: [] 266 | -------------------------------------------------------------------------------- /.licenses/npm/balanced-match.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: balanced-match 3 | version: 1.0.2 4 | type: npm 5 | summary: Match balanced character pairs, like "{" and "}" 6 | homepage: https://github.com/juliangruber/balanced-match 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: | 11 | (MIT) 12 | 13 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of 16 | this software and associated documentation files (the "Software"), to deal in 17 | the Software without restriction, including without limitation the rights to 18 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 19 | of the Software, and to permit persons to whom the Software is furnished to do 20 | so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: |- 34 | (MIT) 35 | 36 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy of 39 | this software and associated documentation files (the "Software"), to deal in 40 | the Software without restriction, including without limitation the rights to 41 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 42 | of the Software, and to permit persons to whom the Software is furnished to do 43 | so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in all 46 | copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 54 | SOFTWARE. 55 | notices: [] 56 | -------------------------------------------------------------------------------- /.licenses/npm/before-after-hook.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: before-after-hook 3 | version: 2.2.3 4 | type: npm 5 | summary: asynchronous before/error/after hooks for internal functionality 6 | homepage: 7 | license: apache-2.0 8 | licenses: 9 | - sources: LICENSE 10 | text: |2 11 | Apache License 12 | Version 2.0, January 2004 13 | http://www.apache.org/licenses/ 14 | 15 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 16 | 17 | 1. Definitions. 18 | 19 | "License" shall mean the terms and conditions for use, reproduction, 20 | and distribution as defined by Sections 1 through 9 of this document. 21 | 22 | "Licensor" shall mean the copyright owner or entity authorized by 23 | the copyright owner that is granting the License. 24 | 25 | "Legal Entity" shall mean the union of the acting entity and all 26 | other entities that control, are controlled by, or are under common 27 | control with that entity. For the purposes of this definition, 28 | "control" means (i) the power, direct or indirect, to cause the 29 | direction or management of such entity, whether by contract or 30 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 31 | outstanding shares, or (iii) beneficial ownership of such entity. 32 | 33 | "You" (or "Your") shall mean an individual or Legal Entity 34 | exercising permissions granted by this License. 35 | 36 | "Source" form shall mean the preferred form for making modifications, 37 | including but not limited to software source code, documentation 38 | source, and configuration files. 39 | 40 | "Object" form shall mean any form resulting from mechanical 41 | transformation or translation of a Source form, including but 42 | not limited to compiled object code, generated documentation, 43 | and conversions to other media types. 44 | 45 | "Work" shall mean the work of authorship, whether in Source or 46 | Object form, made available under the License, as indicated by a 47 | copyright notice that is included in or attached to the work 48 | (an example is provided in the Appendix below). 49 | 50 | "Derivative Works" shall mean any work, whether in Source or Object 51 | form, that is based on (or derived from) the Work and for which the 52 | editorial revisions, annotations, elaborations, or other modifications 53 | represent, as a whole, an original work of authorship. For the purposes 54 | of this License, Derivative Works shall not include works that remain 55 | separable from, or merely link (or bind by name) to the interfaces of, 56 | the Work and Derivative Works thereof. 57 | 58 | "Contribution" shall mean any work of authorship, including 59 | the original version of the Work and any modifications or additions 60 | to that Work or Derivative Works thereof, that is intentionally 61 | submitted to Licensor for inclusion in the Work by the copyright owner 62 | or by an individual or Legal Entity authorized to submit on behalf of 63 | the copyright owner. For the purposes of this definition, "submitted" 64 | means any form of electronic, verbal, or written communication sent 65 | to the Licensor or its representatives, including but not limited to 66 | communication on electronic mailing lists, source code control systems, 67 | and issue tracking systems that are managed by, or on behalf of, the 68 | Licensor for the purpose of discussing and improving the Work, but 69 | excluding communication that is conspicuously marked or otherwise 70 | designated in writing by the copyright owner as "Not a Contribution." 71 | 72 | "Contributor" shall mean Licensor and any individual or Legal Entity 73 | on behalf of whom a Contribution has been received by Licensor and 74 | subsequently incorporated within the Work. 75 | 76 | 2. Grant of Copyright License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | copyright license to reproduce, prepare Derivative Works of, 80 | publicly display, publicly perform, sublicense, and distribute the 81 | Work and such Derivative Works in Source or Object form. 82 | 83 | 3. Grant of Patent License. Subject to the terms and conditions of 84 | this License, each Contributor hereby grants to You a perpetual, 85 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 86 | (except as stated in this section) patent license to make, have made, 87 | use, offer to sell, sell, import, and otherwise transfer the Work, 88 | where such license applies only to those patent claims licensable 89 | by such Contributor that are necessarily infringed by their 90 | Contribution(s) alone or by combination of their Contribution(s) 91 | with the Work to which such Contribution(s) was submitted. If You 92 | institute patent litigation against any entity (including a 93 | cross-claim or counterclaim in a lawsuit) alleging that the Work 94 | or a Contribution incorporated within the Work constitutes direct 95 | or contributory patent infringement, then any patent licenses 96 | granted to You under this License for that Work shall terminate 97 | as of the date such litigation is filed. 98 | 99 | 4. Redistribution. You may reproduce and distribute copies of the 100 | Work or Derivative Works thereof in any medium, with or without 101 | modifications, and in Source or Object form, provided that You 102 | meet the following conditions: 103 | 104 | (a) You must give any other recipients of the Work or 105 | Derivative Works a copy of this License; and 106 | 107 | (b) You must cause any modified files to carry prominent notices 108 | stating that You changed the files; and 109 | 110 | (c) You must retain, in the Source form of any Derivative Works 111 | that You distribute, all copyright, patent, trademark, and 112 | attribution notices from the Source form of the Work, 113 | excluding those notices that do not pertain to any part of 114 | the Derivative Works; and 115 | 116 | (d) If the Work includes a "NOTICE" text file as part of its 117 | distribution, then any Derivative Works that You distribute must 118 | include a readable copy of the attribution notices contained 119 | within such NOTICE file, excluding those notices that do not 120 | pertain to any part of the Derivative Works, in at least one 121 | of the following places: within a NOTICE text file distributed 122 | as part of the Derivative Works; within the Source form or 123 | documentation, if provided along with the Derivative Works; or, 124 | within a display generated by the Derivative Works, if and 125 | wherever such third-party notices normally appear. The contents 126 | of the NOTICE file are for informational purposes only and 127 | do not modify the License. You may add Your own attribution 128 | notices within Derivative Works that You distribute, alongside 129 | or as an addendum to the NOTICE text from the Work, provided 130 | that such additional attribution notices cannot be construed 131 | as modifying the License. 132 | 133 | You may add Your own copyright statement to Your modifications and 134 | may provide additional or different license terms and conditions 135 | for use, reproduction, or distribution of Your modifications, or 136 | for any such Derivative Works as a whole, provided Your use, 137 | reproduction, and distribution of the Work otherwise complies with 138 | the conditions stated in this License. 139 | 140 | 5. Submission of Contributions. Unless You explicitly state otherwise, 141 | any Contribution intentionally submitted for inclusion in the Work 142 | by You to the Licensor shall be under the terms and conditions of 143 | this License, without any additional terms or conditions. 144 | Notwithstanding the above, nothing herein shall supersede or modify 145 | the terms of any separate license agreement you may have executed 146 | with Licensor regarding such Contributions. 147 | 148 | 6. Trademarks. This License does not grant permission to use the trade 149 | names, trademarks, service marks, or product names of the Licensor, 150 | except as required for reasonable and customary use in describing the 151 | origin of the Work and reproducing the content of the NOTICE file. 152 | 153 | 7. Disclaimer of Warranty. Unless required by applicable law or 154 | agreed to in writing, Licensor provides the Work (and each 155 | Contributor provides its Contributions) on an "AS IS" BASIS, 156 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 157 | implied, including, without limitation, any warranties or conditions 158 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 159 | PARTICULAR PURPOSE. You are solely responsible for determining the 160 | appropriateness of using or redistributing the Work and assume any 161 | risks associated with Your exercise of permissions under this License. 162 | 163 | 8. Limitation of Liability. In no event and under no legal theory, 164 | whether in tort (including negligence), contract, or otherwise, 165 | unless required by applicable law (such as deliberate and grossly 166 | negligent acts) or agreed to in writing, shall any Contributor be 167 | liable to You for damages, including any direct, indirect, special, 168 | incidental, or consequential damages of any character arising as a 169 | result of this License or out of the use or inability to use the 170 | Work (including but not limited to damages for loss of goodwill, 171 | work stoppage, computer failure or malfunction, or any and all 172 | other commercial damages or losses), even if such Contributor 173 | has been advised of the possibility of such damages. 174 | 175 | 9. Accepting Warranty or Additional Liability. While redistributing 176 | the Work or Derivative Works thereof, You may choose to offer, 177 | and charge a fee for, acceptance of support, warranty, indemnity, 178 | or other liability obligations and/or rights consistent with this 179 | License. However, in accepting such obligations, You may act only 180 | on Your own behalf and on Your sole responsibility, not on behalf 181 | of any other Contributor, and only if You agree to indemnify, 182 | defend, and hold each Contributor harmless for any liability 183 | incurred by, or claims asserted against, such Contributor by reason 184 | of your accepting any such warranty or additional liability. 185 | 186 | END OF TERMS AND CONDITIONS 187 | 188 | APPENDIX: How to apply the Apache License to your work. 189 | 190 | To apply the Apache License to your work, attach the following 191 | boilerplate notice, with the fields enclosed by brackets "{}" 192 | replaced with your own identifying information. (Don't include 193 | the brackets!) The text should be enclosed in the appropriate 194 | comment syntax for the file format. We also recommend that a 195 | file or class name and description of purpose be included on the 196 | same "printed page" as the copyright notice for easier 197 | identification within third-party archives. 198 | 199 | Copyright 2018 Gregor Martynus and other contributors. 200 | 201 | Licensed under the Apache License, Version 2.0 (the "License"); 202 | you may not use this file except in compliance with the License. 203 | You may obtain a copy of the License at 204 | 205 | http://www.apache.org/licenses/LICENSE-2.0 206 | 207 | Unless required by applicable law or agreed to in writing, software 208 | distributed under the License is distributed on an "AS IS" BASIS, 209 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 210 | See the License for the specific language governing permissions and 211 | limitations under the License. 212 | - sources: README.md 213 | text: "[Apache 2.0](LICENSE)" 214 | notices: [] 215 | -------------------------------------------------------------------------------- /.licenses/npm/bottleneck.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: bottleneck 3 | version: 2.19.5 4 | type: npm 5 | summary: Distributed task scheduler and rate limiter 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2014 Simon Grondin 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of 16 | this software and associated documentation files (the "Software"), to deal in 17 | the Software without restriction, including without limitation the rights to 18 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 19 | the Software, and to permit persons to whom the Software is furnished to do so, 20 | subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 27 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 28 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 29 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 30 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | notices: [] 32 | -------------------------------------------------------------------------------- /.licenses/npm/brace-expansion.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: brace-expansion 3 | version: 2.0.1 4 | type: npm 5 | summary: Brace expansion as known from sh/bash 6 | homepage: https://github.com/juliangruber/brace-expansion 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License 12 | 13 | Copyright (c) 2013 Julian Gruber 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: |- 34 | (MIT) 35 | 36 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy of 39 | this software and associated documentation files (the "Software"), to deal in 40 | the Software without restriction, including without limitation the rights to 41 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 42 | of the Software, and to permit persons to whom the Software is furnished to do 43 | so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in all 46 | copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 54 | SOFTWARE. 55 | notices: [] 56 | -------------------------------------------------------------------------------- /.licenses/npm/deprecation.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: deprecation 3 | version: 2.3.1 4 | type: npm 5 | summary: Log a deprecation message with stack 6 | homepage: 7 | license: isc 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The ISC License 12 | 13 | Copyright (c) Gregor Martynus and contributors 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any 16 | purpose with or without fee is hereby granted, provided that the above 17 | copyright notice and this permission notice appear in all copies. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 25 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | - sources: README.md 27 | text: "[ISC](LICENSE)" 28 | notices: [] 29 | -------------------------------------------------------------------------------- /.licenses/npm/is-plain-object.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: is-plain-object 3 | version: 5.0.0 4 | type: npm 5 | summary: Returns true if an object was created by the `Object` constructor, or Object.create(null). 6 | homepage: https://github.com/jonschlinkert/is-plain-object 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2014-2017, Jon Schlinkert. 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: |- 34 | Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert). 35 | Released under the [MIT License](LICENSE). 36 | 37 | *** 38 | 39 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 28, 2019._ 40 | notices: [] 41 | -------------------------------------------------------------------------------- /.licenses/npm/js-yaml.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: js-yaml 3 | version: 4.1.0 4 | type: npm 5 | summary: YAML 1.2 parser and serializer 6 | homepage: https://github.com/nodeca/js-yaml 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | (The MIT License) 12 | 13 | Copyright (C) 2011-2015 by Vitaly Puzrin 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/lodash.isequal.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: lodash.isequal 3 | version: 4.5.0 4 | type: npm 5 | summary: The Lodash method `_.isEqual` exported as a module. 6 | homepage: https://lodash.com/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright JS Foundation and other contributors 12 | 13 | Based on Underscore.js, copyright Jeremy Ashkenas, 14 | DocumentCloud and Investigative Reporters & Editors 15 | 16 | This software consists of voluntary contributions made by many 17 | individuals. For exact contribution history, see the revision history 18 | available at https://github.com/lodash/lodash 19 | 20 | The following license applies to all parts of this software except as 21 | documented below: 22 | 23 | ==== 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining 26 | a copy of this software and associated documentation files (the 27 | "Software"), to deal in the Software without restriction, including 28 | without limitation the rights to use, copy, modify, merge, publish, 29 | distribute, sublicense, and/or sell copies of the Software, and to 30 | permit persons to whom the Software is furnished to do so, subject to 31 | the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be 34 | included in all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 37 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 38 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 39 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 40 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 41 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 42 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 43 | 44 | ==== 45 | 46 | Copyright and related rights for sample code are waived via CC0. Sample 47 | code is defined as all source code displayed within the prose of the 48 | documentation. 49 | 50 | CC0: http://creativecommons.org/publicdomain/zero/1.0/ 51 | 52 | ==== 53 | 54 | Files located in the node_modules and vendor directories are externally 55 | maintained libraries used by this software which have their own 56 | licenses; we recommend you read them, as their terms may differ from the 57 | terms above. 58 | notices: [] 59 | -------------------------------------------------------------------------------- /.licenses/npm/minimatch.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: minimatch 3 | version: 10.0.1 4 | type: npm 5 | summary: a glob matcher in javascript 6 | homepage: 7 | license: isc 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The ISC License 12 | 13 | Copyright (c) 2011-2022 Isaac Z. Schlueter and Contributors 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any 16 | purpose with or without fee is hereby granted, provided that the above 17 | copyright notice and this permission notice appear in all copies. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 25 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | notices: [] 27 | -------------------------------------------------------------------------------- /.licenses/npm/once.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: once 3 | version: 1.4.0 4 | type: npm 5 | summary: Run a function exactly one time 6 | homepage: 7 | license: isc 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The ISC License 12 | 13 | Copyright (c) Isaac Z. Schlueter and Contributors 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any 16 | purpose with or without fee is hereby granted, provided that the above 17 | copyright notice and this permission notice appear in all copies. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 25 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | notices: [] 27 | -------------------------------------------------------------------------------- /.licenses/npm/tunnel.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tunnel 3 | version: 0.0.6 4 | type: npm 5 | summary: Node HTTP/HTTPS Agents for tunneling proxies 6 | homepage: https://github.com/koichik/node-tunnel/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2012 Koichi Kobayashi 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: Licensed under the [MIT](https://github.com/koichik/node-tunnel/blob/master/LICENSE) 34 | license. 35 | notices: [] 36 | -------------------------------------------------------------------------------- /.licenses/npm/undici.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: undici 3 | version: 5.28.4 4 | type: npm 5 | summary: An HTTP/1.1 client, written from scratch for Node.js 6 | homepage: https://undici.nodejs.org 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License 12 | 13 | Copyright (c) Matteo Collina and Undici contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: MIT 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.licenses/npm/universal-user-agent.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: universal-user-agent 3 | version: 6.0.0 4 | type: npm 5 | summary: Get a user agent string in both browser and node 6 | homepage: 7 | license: isc 8 | licenses: 9 | - sources: LICENSE.md 10 | text: | 11 | # [ISC License](https://spdx.org/licenses/ISC) 12 | 13 | Copyright (c) 2018, Gregor Martynus (https://github.com/gr2m) 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | - sources: README.md 19 | text: "[ISC](LICENSE.md)" 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/uuid.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: uuid 3 | version: 8.3.2 4 | type: npm 5 | summary: RFC4122 (v1, v4, and v5) UUIDs 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2010-2020 Robert Kieffer and other contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/wrappy.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: wrappy 3 | version: 1.0.2 4 | type: npm 5 | summary: Callback wrapping utility 6 | homepage: https://github.com/npm/wrappy 7 | license: isc 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The ISC License 12 | 13 | Copyright (c) Isaac Z. Schlueter and Contributors 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any 16 | purpose with or without fee is hereby granted, provided that the above 17 | copyright notice and this permission notice appear in all copies. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 25 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | notices: [] 27 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore list 2 | /* 3 | 4 | # Do not ignore these folders: 5 | !__tests__/ 6 | !__mocks__/ 7 | !.github/ 8 | !src/ -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update. 2 | module.exports = { 3 | printWidth: 80, 4 | tabWidth: 2, 5 | useTabs: false, 6 | semi: true, 7 | singleQuote: true, 8 | trailingComma: 'none', 9 | bracketSpacing: false, 10 | arrowParens: 'avoid' 11 | }; 12 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @actions/setup-actions-team 2 | -------------------------------------------------------------------------------- /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 make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, 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 within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be 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 opensource@github.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 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: https://github.com/actions/labeler/fork 4 | [pr]: https://github.com/actions/labeler/compare 5 | [code-of-conduct]: CODE_OF_CONDUCT.md 6 | 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 8 | 9 | Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). 10 | 11 | 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. 12 | 13 | ## Found a bug? 14 | 15 | - **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/actions/labeler/issues). 16 | - If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/actions/labeler/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or a **reproducible test case** demonstrating the expected behavior that is not occurring. 17 | - If possible, use the relevant bug report templates to create the issue. 18 | 19 | ## What should I know before submitting a pull request or issue 20 | 21 | This project is written in [TypeScript](https://www.typescriptlang.org/), a typed variant of JavaScript, and we use [Prettier](https://prettier.io/) to get a consistent code style. 22 | 23 | Because of how GitHub Actions are run, the source code of this project is transpiled from TypeScript into JavaScript. The transpiled code (found in `lib/`) is subsequently compiled using [NCC](https://github.com/vercel/ncc/blob/master/readme.md) (found in `dist/`) to avoid having to include the `node_modules/` directory in the repository. 24 | 25 | ## Submitting a pull request 26 | 27 | 1. [Fork][fork] and clone the repository 28 | 1. Configure and install the dependencies: `npm install` 29 | 1. Create a new branch: `git checkout -b my-branch-name` 30 | 1. Make your change, add tests, and make sure the tests still pass: `npm run test` 31 | 1. Make sure your code is correctly formatted: `npm run format` 32 | 1. Update `dist/index.js` using `npm run build`. This creates a single javascript file that is used as an entrypoint for the action 33 | 1. Push to your fork and [submit a pull request][pr] 34 | 1. Pat yourself on the back and wait for your pull request to be reviewed and merged. 35 | 36 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 37 | 38 | - Write tests. 39 | - Keep your change 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. 40 | 41 | ## Releasing a new version 42 | 43 | All the concepts from [the actions/toolkit release docs](https://github.com/actions/toolkit/blob/main/docs/action-versioning.md) apply. Please read that first! 44 | 45 | Once the changes are merged into main, a repo maintainer should: 46 | 47 | 1. Bump the package version by running [`npm version [major|minor|patch]`](https://docs.npmjs.com/cli/v7/commands/npm-version). We adhere to [SemVer 2.0](https://semver.org/spec/v2.0.0.html) to the best of our ability. Commit the changes to `package.json` and `package-lock.json` and push them to main. 48 | 1. [Draft a new release](https://github.com/actions/labeler/releases/new) pointing to the ref of the version bump you just made. Publish the release to the marketplace when complete. 49 | 1. Finally: update the corresponding "major tag" (v1, v2, v3, etc) to point to the specific ref of the release you just made. For example, if we just released `v1.1.0`, we would rewrite the `v1` tag like this: 50 | 51 | ``` 52 | git tag -fa v1 v1.1.0 -m "Update v1 tag to point to v1.1.0" 53 | git push origin v1 --force 54 | ``` 55 | 56 | ## Licensed 57 | 58 | This repository uses a tool called [Licensed](https://github.com/github/licensed) to verify third party dependencies. You may need to locally install licensed and run `licensed cache` to update the dependency cache if you install or update a production dependency. If licensed cache is unable to determine the dependency, you may need to modify the cache file yourself to put the correct license. You should still verify the dependency, licensed is a tool to help, but is not a substitute for human review of dependencies. 59 | 60 | ## Resources 61 | 62 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 63 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 64 | - [GitHub Help](https://help.github.com) 65 | - [Writing good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 66 | 67 | Thanks! :heart: :heart: :heart: 68 | 69 | GitHub Actions Team :octocat: 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 GitHub, Inc. and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pull Request Labeler 2 | 3 | [![Basic validation](https://github.com/actions/labeler/actions/workflows/basic-validation.yml/badge.svg?branch=main)](https://github.com/actions/labeler/actions/workflows/basic-validation.yml) 4 | 5 | Automatically label new pull requests based on the paths of files being changed or the branch name. 6 | 7 | ## Breaking changes in V5 8 | 1) The ability to apply labels based on the names of base and/or head branches was added ([#186](https://github.com/actions/labeler/issues/186) and [#54](https://github.com/actions/labeler/issues/54)). The match object for changed files was expanded with new combinations in order to make it more intuitive and flexible ([#423](https://github.com/actions/labeler/issues/423) and [#101](https://github.com/actions/labeler/issues/101)). As a result, the configuration file structure was significantly redesigned and is not compatible with the structure of the previous version. Please read the documentation below to find out how to adapt your configuration files for use with the new action version. 9 | 10 | 2) The bug related to the `sync-labels` input was fixed ([#112](https://github.com/actions/labeler/issues/112)). Now the input value is read correctly. 11 | 12 | 3) By default, `dot` input is set to `true`. Now, paths starting with a dot (e.g. `.github`) are matched by default. 13 | 14 | 4) Version 5 of this action updated the [runtime to Node.js 20](https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions). All scripts are now run with Node.js 20 instead of Node.js 16 and are affected by any breaking changes between Node.js 16 and 20. 15 | 16 | > [!IMPORTANT] 17 | > Before the update to the v5, please check out [this information](#notes-regarding-pull_request_target-event) about the `pull_request_target` event trigger. 18 | 19 | ## Usage 20 | 21 | ### Create `.github/labeler.yml` 22 | 23 | Create a `.github/labeler.yml` file with a list of labels and config options to match and apply the label. 24 | 25 | The key is the name of the label in your repository that you want to add (eg: "merge conflict", "needs-updating") and the value is a match object. 26 | 27 | #### Match Object 28 | 29 | The match object allows control over the matching options. You can specify the label to be applied based on the files that have changed or the name of either the base branch or the head branch. For the changed files options you provide a [path glob](https://github.com/isaacs/minimatch#minimatch), and for the branches you provide a regexp to match against the branch name. 30 | 31 | The base match object is defined as: 32 | ```yml 33 | - changed-files: 34 | - any-glob-to-any-file: ['list', 'of', 'globs'] 35 | - any-glob-to-all-files: ['list', 'of', 'globs'] 36 | - all-globs-to-any-file: ['list', 'of', 'globs'] 37 | - all-globs-to-all-files: ['list', 'of', 'globs'] 38 | - base-branch: ['list', 'of', 'regexps'] 39 | - head-branch: ['list', 'of', 'regexps'] 40 | ``` 41 | 42 | There are two top-level keys, `any` and `all`, which both accept the same configuration options: 43 | ```yml 44 | - any: 45 | - changed-files: 46 | - any-glob-to-any-file: ['list', 'of', 'globs'] 47 | - any-glob-to-all-files: ['list', 'of', 'globs'] 48 | - all-globs-to-any-file: ['list', 'of', 'globs'] 49 | - all-globs-to-all-files: ['list', 'of', 'globs'] 50 | - base-branch: ['list', 'of', 'regexps'] 51 | - head-branch: ['list', 'of', 'regexps'] 52 | - all: 53 | - changed-files: 54 | - any-glob-to-any-file: ['list', 'of', 'globs'] 55 | - any-glob-to-all-files: ['list', 'of', 'globs'] 56 | - all-globs-to-any-file: ['list', 'of', 'globs'] 57 | - all-globs-to-all-files: ['list', 'of', 'globs'] 58 | - base-branch: ['list', 'of', 'regexps'] 59 | - head-branch: ['list', 'of', 'regexps'] 60 | ``` 61 | 62 | From a boolean logic perspective, top-level match objects, and options within `all` are `AND`-ed together and individual match rules within the `any` object are `OR`-ed. 63 | 64 | One or all fields can be provided for fine-grained matching. 65 | The fields are defined as follows: 66 | - `all`: ALL of the provided options must match for the label to be applied 67 | - `any`: if ANY of the provided options match then the label will be applied 68 | - `base-branch`: match regexps against the base branch name 69 | - `head-branch`: match regexps against the head branch name 70 | - `changed-files`: match glob patterns against the changed paths 71 | - `any-glob-to-any-file`: ANY glob must match against ANY changed file 72 | - `any-glob-to-all-files`: ANY glob must match against ALL changed files 73 | - `all-globs-to-any-file`: ALL globs must match against ANY changed file 74 | - `all-globs-to-all-files`: ALL globs must match against ALL changed files 75 | 76 | If a base option is provided without a top-level key, then it will default to `any`. More specifically, the following two configurations are equivalent: 77 | ```yml 78 | Documentation: 79 | - changed-files: 80 | - any-glob-to-any-file: 'docs/*' 81 | ``` 82 | and 83 | ```yml 84 | Documentation: 85 | - any: 86 | - changed-files: 87 | - any-glob-to-any-file: 'docs/*' 88 | ``` 89 | 90 | If path globs are combined with `!` negation, you can write complex matching rules. See the examples below for more information. 91 | 92 | #### Basic Examples 93 | 94 | ```yml 95 | # Add 'root' label to any root file changes 96 | # Quotation marks are required for the leading asterisk 97 | root: 98 | - changed-files: 99 | - any-glob-to-any-file: '*' 100 | 101 | # Add 'AnyChange' label to any changes within the entire repository 102 | AnyChange: 103 | - changed-files: 104 | - any-glob-to-any-file: '**' 105 | 106 | # Add 'Documentation' label to any changes within 'docs' folder or any subfolders 107 | Documentation: 108 | - changed-files: 109 | - any-glob-to-any-file: docs/** 110 | 111 | # Add 'Documentation' label to any file changes within 'docs' folder 112 | Documentation: 113 | - changed-files: 114 | - any-glob-to-any-file: docs/* 115 | 116 | # Add 'Documentation' label to any file changes within 'docs' or 'guides' folders 117 | Documentation: 118 | - changed-files: 119 | - any-glob-to-any-file: 120 | - docs/* 121 | - guides/* 122 | 123 | ## Equivalent of the above mentioned configuration using another syntax 124 | Documentation: 125 | - changed-files: 126 | - any-glob-to-any-file: ['docs/*', 'guides/*'] 127 | 128 | # Add 'Documentation' label to any change to .md files within the entire repository 129 | Documentation: 130 | - changed-files: 131 | - any-glob-to-any-file: '**/*.md' 132 | 133 | # Add 'source' label to any change to src files within the source dir EXCEPT for the docs sub-folder 134 | source: 135 | - all: 136 | - changed-files: 137 | - any-glob-to-any-file: 'src/**/*' 138 | - all-globs-to-all-files: '!src/docs/*' 139 | 140 | # Add 'feature' label to any PR where the head branch name starts with `feature` or has a `feature` section in the name 141 | feature: 142 | - head-branch: ['^feature', 'feature'] 143 | 144 | # Add 'release' label to any PR that is opened against the `main` branch 145 | release: 146 | - base-branch: 'main' 147 | ``` 148 | 149 | ### Create Workflow 150 | 151 | Create a workflow (e.g. `.github/workflows/labeler.yml` see [Creating a Workflow file](https://docs.github.com/en/actions/writing-workflows/quickstart#creating-your-first-workflow)) to utilize the labeler action with content: 152 | 153 | ```yml 154 | name: "Pull Request Labeler" 155 | on: 156 | - pull_request_target 157 | 158 | jobs: 159 | labeler: 160 | permissions: 161 | contents: read 162 | pull-requests: write 163 | runs-on: ubuntu-latest 164 | steps: 165 | - uses: actions/labeler@v5 166 | ``` 167 | 168 | #### Inputs 169 | 170 | Various inputs are defined in [`action.yml`](action.yml) to let you configure the labeler: 171 | 172 | | Name | Description | Default | 173 | |----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------| 174 | | `repo-token` | Token to use to authorize label changes. Typically the GITHUB_TOKEN secret | `github.token` | 175 | | `configuration-path` | The path to the label configuration file. If the file doesn't exist at the specified path on the runner, action will read from the source repository via the Github API. | `.github/labeler.yml` | 176 | | `sync-labels` | Whether or not to remove labels when matching files are reverted or no longer changed by the PR | `false` | 177 | | `dot` | Whether or not to auto-include paths starting with dot (e.g. `.github`) | `true` | 178 | | `pr-number` | The number(s) of pull request to update, rather than detecting from the workflow context | N/A | 179 | 180 | ##### Using `configuration-path` input together with the `@actions/checkout` action 181 | You might want to use action called [@actions/checkout](https://github.com/actions/checkout) to upload label configuration file onto the runner from the current or any other repositories. See usage example below: 182 | 183 | ```yml 184 | steps: 185 | - uses: actions/checkout@v4 # Uploads repository content to the runner 186 | with: 187 | repository: "owner/repositoryName" # The one of the available inputs, visit https://github.com/actions/checkout#readme to find more 188 | - uses: actions/labeler@v5 189 | with: 190 | configuration-path: 'path/to/the/uploaded/configuration/file' 191 | 192 | ``` 193 | 194 | ##### Example workflow specifying pull request numbers 195 | 196 | ```yml 197 | name: "Label Previous Pull Requests" 198 | on: 199 | schedule: 200 | - cron: "0 1 * * 1" 201 | 202 | jobs: 203 | labeler: 204 | permissions: 205 | contents: read 206 | pull-requests: write 207 | runs-on: ubuntu-latest 208 | steps: 209 | 210 | # Label PRs 1, 2, and 3 211 | - uses: actions/labeler@v5 212 | with: 213 | pr-number: | 214 | 1 215 | 2 216 | 3 217 | ``` 218 | 219 | **Note:** in normal usage the `pr-number` input is not required as the action will detect the PR number from the workflow context. 220 | 221 | #### Outputs 222 | 223 | Labeler provides the following outputs: 224 | 225 | | Name | Description | 226 | |--------------|-----------------------------------------------------------| 227 | | `new-labels` | A comma-separated list of all new labels | 228 | | `all-labels` | A comma-separated list of all labels that the PR contains | 229 | 230 | The following example performs steps based on the output of labeler: 231 | ```yml 232 | name: "Pull Request Labeler" 233 | on: 234 | - pull_request_target 235 | 236 | jobs: 237 | labeler: 238 | permissions: 239 | contents: read 240 | pull-requests: write 241 | runs-on: ubuntu-latest 242 | steps: 243 | - id: label-the-PR 244 | uses: actions/labeler@v5 245 | 246 | - id: run-frontend-tests 247 | if: contains(steps.label-the-PR.outputs.all-labels, 'frontend') 248 | run: | 249 | echo "Running frontend tests..." 250 | # Put your commands for running frontend tests here 251 | 252 | - id: run-backend-tests 253 | if: contains(steps.label-the-PR.outputs.all-labels, 'backend') 254 | run: | 255 | echo "Running backend tests..." 256 | # Put your commands for running backend tests here 257 | ``` 258 | 259 | ## Recommended Permissions 260 | 261 | In order to add labels to pull requests, the GitHub labeler action requires write permissions on the pull-request. However, when the action runs on a pull request from a forked repository, GitHub only grants read access tokens for `pull_request` events, at most. If you encounter an `Error: HttpError: Resource not accessible by integration`, it's likely due to these permission constraints. To resolve this issue, you can modify the `on:` section of your workflow to use 262 | [`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) instead of `pull_request` (see example [above](#create-workflow)). This change allows the action to have write access, because `pull_request_target` alters the [context of the action](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) and safely grants additional permissions. Refer to the [GitHub token permissions documentation](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token) for more details about access levels and event contexts. 263 | 264 | ```yml 265 | permissions: 266 | contents: read 267 | pull-requests: write 268 | ``` 269 | 270 | ## Notes regarding `pull_request_target` event 271 | 272 | Using the `pull_request_target` event trigger involves several peculiarities related to initial set up of the labeler or updating version of the labeler. 273 | 274 | ### Initial set up of the labeler action 275 | 276 | When submitting an initial pull request to a repository using the `pull_request_target` event, the labeler workflow will not run on that pull request because the `pull_request_target` execution runs off the base branch instead of the pull request's branch. Unfortunately this means the introduction of the labeler can not be verified during that pull request and it needs to be committed blindly. 277 | 278 | ### Updating major version of the labeler 279 | 280 | When submitting a pull request that includes updates of the labeler action version and associated configuration files, using the `pull_request_target` event may result in a failed workflow. This is due to the nature of `pull_request_target`, which uses the code from the base branch rather than the branch linked to the pull request — so, potentially outdated configuration files may not be compatible with the updated labeler action. 281 | 282 | To prevent this issue, you can switch to using the `pull_request` event temporarily, before merging. This event execution draws from the code within the branch of your pull request, allowing you to verify the new configuration's compatibility with the updated labeler action. 283 | 284 | ```yml 285 | name: "Pull Request Labeler" 286 | on: 287 | - pull_request 288 | ``` 289 | 290 | Once you confirm that the updated configuration files function as intended, you can then revert to using the `pull_request_target` event before merging the pull request. Following this step ensures that your workflow is robust and free from disruptions. 291 | 292 | ## Contributions 293 | 294 | Contributions are welcome! See the [Contributor's Guide](CONTRIBUTING.md). 295 | -------------------------------------------------------------------------------- /__mocks__/@actions/github.ts: -------------------------------------------------------------------------------- 1 | export const context = { 2 | payload: { 3 | pull_request: { 4 | number: 123, 5 | head: { 6 | ref: 'head-branch-name' 7 | }, 8 | base: { 9 | ref: 'base-branch-name' 10 | } 11 | } 12 | }, 13 | repo: { 14 | owner: 'monalisa', 15 | repo: 'helloworld' 16 | } 17 | }; 18 | 19 | const mockApi = { 20 | rest: { 21 | issues: { 22 | setLabels: jest.fn() 23 | }, 24 | pulls: { 25 | get: jest.fn().mockResolvedValue({ 26 | data: { 27 | labels: [] 28 | } 29 | }), 30 | listFiles: { 31 | endpoint: { 32 | merge: jest.fn().mockReturnValue({}) 33 | } 34 | } 35 | }, 36 | repos: { 37 | getContent: jest.fn() 38 | } 39 | }, 40 | paginate: jest.fn() 41 | }; 42 | 43 | export const getOctokit = jest.fn().mockImplementation(() => mockApi); 44 | -------------------------------------------------------------------------------- /__tests__/branch.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getBranchName, 3 | checkAnyBranch, 4 | checkAllBranch, 5 | toBranchMatchConfig, 6 | BranchMatchConfig 7 | } from '../src/branch'; 8 | import * as github from '@actions/github'; 9 | 10 | jest.mock('@actions/core'); 11 | jest.mock('@actions/github'); 12 | 13 | describe('getBranchName', () => { 14 | describe('when the pull requests base branch is requested', () => { 15 | it('returns the base branch name', () => { 16 | const result = getBranchName('base'); 17 | expect(result).toEqual('base-branch-name'); 18 | }); 19 | }); 20 | 21 | describe('when the pull requests head branch is requested', () => { 22 | it('returns the head branch name', () => { 23 | const result = getBranchName('head'); 24 | expect(result).toEqual('head-branch-name'); 25 | }); 26 | }); 27 | }); 28 | 29 | describe('checkAllBranch', () => { 30 | beforeEach(() => { 31 | github.context.payload.pull_request!.head = { 32 | ref: 'test/feature/123' 33 | }; 34 | github.context.payload.pull_request!.base = { 35 | ref: 'main' 36 | }; 37 | }); 38 | 39 | describe('when a single pattern is provided', () => { 40 | describe('and the pattern matches the head branch', () => { 41 | it('returns true', () => { 42 | const result = checkAllBranch(['^test'], 'head'); 43 | expect(result).toBe(true); 44 | }); 45 | }); 46 | 47 | describe('and the pattern does not match the head branch', () => { 48 | it('returns false', () => { 49 | const result = checkAllBranch(['^feature/'], 'head'); 50 | expect(result).toBe(false); 51 | }); 52 | }); 53 | }); 54 | 55 | describe('when multiple patterns are provided', () => { 56 | describe('and not all patterns matched', () => { 57 | it('returns false', () => { 58 | const result = checkAllBranch(['^test/', '^feature/'], 'head'); 59 | expect(result).toBe(false); 60 | }); 61 | }); 62 | 63 | describe('and all patterns match', () => { 64 | it('returns true', () => { 65 | const result = checkAllBranch(['^test/', '/feature/'], 'head'); 66 | expect(result).toBe(true); 67 | }); 68 | }); 69 | 70 | describe('and no patterns match', () => { 71 | it('returns false', () => { 72 | const result = checkAllBranch(['^feature/', '/test$'], 'head'); 73 | expect(result).toBe(false); 74 | }); 75 | }); 76 | }); 77 | 78 | describe('when the branch to check is specified as the base branch', () => { 79 | describe('and the pattern matches the base branch', () => { 80 | it('returns true', () => { 81 | const result = checkAllBranch(['^main$'], 'base'); 82 | expect(result).toBe(true); 83 | }); 84 | }); 85 | }); 86 | }); 87 | 88 | describe('checkAnyBranch', () => { 89 | beforeEach(() => { 90 | github.context.payload.pull_request!.head = { 91 | ref: 'test/feature/123' 92 | }; 93 | github.context.payload.pull_request!.base = { 94 | ref: 'main' 95 | }; 96 | }); 97 | 98 | describe('when a single pattern is provided', () => { 99 | describe('and the pattern matches the head branch', () => { 100 | it('returns true', () => { 101 | const result = checkAnyBranch(['^test'], 'head'); 102 | expect(result).toBe(true); 103 | }); 104 | }); 105 | 106 | describe('and the pattern does not match the head branch', () => { 107 | it('returns false', () => { 108 | const result = checkAnyBranch(['^feature/'], 'head'); 109 | expect(result).toBe(false); 110 | }); 111 | }); 112 | }); 113 | 114 | describe('when multiple patterns are provided', () => { 115 | describe('and at least one pattern matches', () => { 116 | it('returns true', () => { 117 | const result = checkAnyBranch(['^test/', '^feature/'], 'head'); 118 | expect(result).toBe(true); 119 | }); 120 | }); 121 | 122 | describe('and all patterns match', () => { 123 | it('returns true', () => { 124 | const result = checkAnyBranch(['^test/', '/feature/'], 'head'); 125 | expect(result).toBe(true); 126 | }); 127 | }); 128 | 129 | describe('and no patterns match', () => { 130 | it('returns false', () => { 131 | const result = checkAnyBranch(['^feature/', '/test$'], 'head'); 132 | expect(result).toBe(false); 133 | }); 134 | }); 135 | }); 136 | 137 | describe('when the branch to check is specified as the base branch', () => { 138 | describe('and the pattern matches the base branch', () => { 139 | it('returns true', () => { 140 | const result = checkAnyBranch(['^main$'], 'base'); 141 | expect(result).toBe(true); 142 | }); 143 | }); 144 | }); 145 | }); 146 | 147 | describe('toBranchMatchConfig', () => { 148 | describe('when there are no branch keys in the config', () => { 149 | const config = {'changed-files': [{any: ['testing']}]}; 150 | 151 | it('returns an empty object', () => { 152 | const result = toBranchMatchConfig(config); 153 | expect(result).toEqual({}); 154 | }); 155 | }); 156 | 157 | describe('when the config contains a head-branch option', () => { 158 | const config = {'head-branch': ['testing']}; 159 | 160 | it('sets headBranch in the matchConfig', () => { 161 | const result = toBranchMatchConfig(config); 162 | expect(result).toEqual({ 163 | headBranch: ['testing'] 164 | }); 165 | }); 166 | 167 | describe('and the matching option is a string', () => { 168 | const stringConfig = {'head-branch': 'testing'}; 169 | 170 | it('sets headBranch in the matchConfig', () => { 171 | const result = toBranchMatchConfig(stringConfig); 172 | expect(result).toEqual({ 173 | headBranch: ['testing'] 174 | }); 175 | }); 176 | }); 177 | }); 178 | 179 | describe('when the config contains a base-branch option', () => { 180 | const config = {'base-branch': ['testing']}; 181 | it('sets baseBranch in the matchConfig', () => { 182 | const result = toBranchMatchConfig(config); 183 | expect(result).toEqual({ 184 | baseBranch: ['testing'] 185 | }); 186 | }); 187 | 188 | describe('and the matching option is a string', () => { 189 | const stringConfig = {'base-branch': 'testing'}; 190 | 191 | it('sets baseBranch in the matchConfig', () => { 192 | const result = toBranchMatchConfig(stringConfig); 193 | expect(result).toEqual({ 194 | baseBranch: ['testing'] 195 | }); 196 | }); 197 | }); 198 | }); 199 | 200 | describe('when the config contains both a base-branch and head-branch option', () => { 201 | const config = {'base-branch': ['testing'], 'head-branch': ['testing']}; 202 | it('sets headBranch and baseBranch in the matchConfig', () => { 203 | const result = toBranchMatchConfig(config); 204 | expect(result).toEqual({ 205 | baseBranch: ['testing'], 206 | headBranch: ['testing'] 207 | }); 208 | }); 209 | }); 210 | }); 211 | -------------------------------------------------------------------------------- /__tests__/changedFiles.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangedFilesMatchConfig, 3 | checkAllChangedFiles, 4 | checkAnyChangedFiles, 5 | toChangedFilesMatchConfig, 6 | checkIfAnyGlobMatchesAnyFile, 7 | checkIfAllGlobsMatchAnyFile, 8 | checkIfAnyGlobMatchesAllFiles, 9 | checkIfAllGlobsMatchAllFiles 10 | } from '../src/changedFiles'; 11 | 12 | jest.mock('@actions/core'); 13 | jest.mock('@actions/github'); 14 | 15 | describe('checkAllChangedFiles', () => { 16 | const changedFiles = ['foo.txt', 'bar.txt']; 17 | 18 | describe('when all given glob pattern configs matched', () => { 19 | const globPatternsConfigs = [ 20 | {anyGlobToAnyFile: ['foo.txt']}, 21 | {anyGlobToAllFiles: ['*.txt']}, 22 | {allGlobsToAllFiles: ['**']} 23 | ]; 24 | 25 | it('returns true', () => { 26 | const result = checkAllChangedFiles( 27 | changedFiles, 28 | globPatternsConfigs, 29 | false 30 | ); 31 | expect(result).toBe(true); 32 | }); 33 | }); 34 | 35 | describe(`when some given glob pattern config did not match`, () => { 36 | const globPatternsConfigs = [ 37 | {anyGlobToAnyFile: ['*.md']}, 38 | {anyGlobToAllFiles: ['*.txt']}, 39 | {allGlobsToAllFiles: ['**']} 40 | ]; 41 | 42 | it('returns false', () => { 43 | const result = checkAllChangedFiles( 44 | changedFiles, 45 | globPatternsConfigs, 46 | false 47 | ); 48 | expect(result).toBe(false); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('checkAnyChangedFiles', () => { 54 | const changedFiles = ['foo.txt', 'bar.txt']; 55 | 56 | describe('when any given glob pattern config matched', () => { 57 | const globPatternsConfigs = [ 58 | {anyGlobToAnyFile: ['*.md']}, 59 | {anyGlobToAllFiles: ['*.txt']} 60 | ]; 61 | 62 | it('returns true', () => { 63 | const result = checkAnyChangedFiles( 64 | changedFiles, 65 | globPatternsConfigs, 66 | false 67 | ); 68 | expect(result).toBe(true); 69 | }); 70 | }); 71 | 72 | describe('when none of the given glob pattern configs matched', () => { 73 | const globPatternsConfigs = [ 74 | {anyGlobToAnyFile: ['*.md']}, 75 | {anyGlobToAllFiles: ['!*.txt']} 76 | ]; 77 | 78 | it('returns false', () => { 79 | const result = checkAnyChangedFiles( 80 | changedFiles, 81 | globPatternsConfigs, 82 | false 83 | ); 84 | expect(result).toBe(false); 85 | }); 86 | }); 87 | }); 88 | 89 | describe('toChangedFilesMatchConfig', () => { 90 | describe(`when there is no 'changed-files' key in the config`, () => { 91 | const config = {'head-branch': 'test'}; 92 | 93 | it('returns an empty object', () => { 94 | const result = toChangedFilesMatchConfig(config); 95 | expect(result).toEqual({}); 96 | }); 97 | }); 98 | 99 | describe(`when there is a 'changed-files' key in the config`, () => { 100 | describe('but the glob pattern config key is not provided', () => { 101 | const config = {'changed-files': ['bar']}; 102 | 103 | it('throws the error', () => { 104 | expect(() => { 105 | toChangedFilesMatchConfig(config); 106 | }).toThrow( 107 | `The "changed-files" section must have a valid config structure. Please read the action documentation for more information` 108 | ); 109 | }); 110 | }); 111 | 112 | describe('but the glob pattern config key is not valid', () => { 113 | const config = {'changed-files': [{NotValidConfigKey: ['bar']}]}; 114 | 115 | it('throws the error', () => { 116 | expect(() => { 117 | toChangedFilesMatchConfig(config); 118 | }).toThrow( 119 | `Unknown config options were under "changed-files": NotValidConfigKey` 120 | ); 121 | }); 122 | }); 123 | 124 | describe('and the glob pattern config key is provided', () => { 125 | describe('and the value is an array of strings', () => { 126 | const config = { 127 | 'changed-files': [{'any-glob-to-any-file': ['testing']}] 128 | }; 129 | 130 | it('sets the value in the config object', () => { 131 | const result = toChangedFilesMatchConfig(config); 132 | expect(result).toEqual({ 133 | changedFiles: [{anyGlobToAnyFile: ['testing']}] 134 | }); 135 | }); 136 | }); 137 | 138 | describe('and the value is a string', () => { 139 | const config = {'changed-files': [{'any-glob-to-any-file': 'testing'}]}; 140 | 141 | it(`sets the string as an array in the config object`, () => { 142 | const result = toChangedFilesMatchConfig(config); 143 | expect(result).toEqual({ 144 | changedFiles: [{anyGlobToAnyFile: ['testing']}] 145 | }); 146 | }); 147 | }); 148 | }); 149 | }); 150 | }); 151 | 152 | describe('checkIfAnyGlobMatchesAnyFile', () => { 153 | const changedFiles = ['foo.txt', 'bar.txt']; 154 | 155 | describe('when any given glob pattern matched any file', () => { 156 | const globPatterns = ['*.md', 'foo.txt']; 157 | 158 | it('returns true', () => { 159 | const result = checkIfAnyGlobMatchesAnyFile( 160 | changedFiles, 161 | globPatterns, 162 | false 163 | ); 164 | expect(result).toBe(true); 165 | }); 166 | }); 167 | 168 | describe('when none of the given glob pattern matched any file', () => { 169 | const globPatterns = ['*.md', '!*.txt']; 170 | 171 | it('returns false', () => { 172 | const result = checkIfAnyGlobMatchesAnyFile( 173 | changedFiles, 174 | globPatterns, 175 | false 176 | ); 177 | expect(result).toBe(false); 178 | }); 179 | }); 180 | }); 181 | 182 | describe('checkIfAllGlobsMatchAnyFile', () => { 183 | const changedFiles = ['foo.txt', 'bar.txt']; 184 | 185 | describe('when all given glob patterns matched any file', () => { 186 | const globPatterns = ['**/bar.txt', 'bar.txt']; 187 | 188 | it('returns true', () => { 189 | const result = checkIfAllGlobsMatchAnyFile( 190 | changedFiles, 191 | globPatterns, 192 | false 193 | ); 194 | expect(result).toBe(true); 195 | }); 196 | }); 197 | 198 | describe('when some of the given glob patterns did not match any file', () => { 199 | const globPatterns = ['*.txt', '*.md']; 200 | 201 | it('returns false', () => { 202 | const result = checkIfAllGlobsMatchAnyFile( 203 | changedFiles, 204 | globPatterns, 205 | false 206 | ); 207 | expect(result).toBe(false); 208 | }); 209 | }); 210 | }); 211 | 212 | describe('checkIfAnyGlobMatchesAllFiles', () => { 213 | const changedFiles = ['foo.txt', 'bar.txt']; 214 | 215 | describe('when any given glob pattern matched all files', () => { 216 | const globPatterns = ['*.md', '*.txt']; 217 | 218 | it('returns true', () => { 219 | const result = checkIfAnyGlobMatchesAllFiles( 220 | changedFiles, 221 | globPatterns, 222 | false 223 | ); 224 | expect(result).toBe(true); 225 | }); 226 | }); 227 | 228 | describe('when none of the given glob patterns matched all files', () => { 229 | const globPatterns = ['*.md', 'bar.txt', 'foo.txt']; 230 | 231 | it('returns false', () => { 232 | const result = checkIfAnyGlobMatchesAllFiles( 233 | changedFiles, 234 | globPatterns, 235 | false 236 | ); 237 | expect(result).toBe(false); 238 | }); 239 | }); 240 | }); 241 | 242 | describe('checkIfAllGlobsMatchAllFiles', () => { 243 | const changedFiles = ['foo.txt', 'bar.txt']; 244 | 245 | describe('when all given glob patterns matched all files', () => { 246 | const globPatterns = ['*.txt', '**']; 247 | 248 | it('returns true', () => { 249 | const result = checkIfAllGlobsMatchAllFiles( 250 | changedFiles, 251 | globPatterns, 252 | false 253 | ); 254 | expect(result).toBe(true); 255 | }); 256 | }); 257 | 258 | describe('when some of the given glob patterns did not match all files', () => { 259 | const globPatterns = ['**', 'foo.txt']; 260 | 261 | it('returns false', () => { 262 | const result = checkIfAllGlobsMatchAllFiles( 263 | changedFiles, 264 | globPatterns, 265 | false 266 | ); 267 | expect(result).toBe(false); 268 | }); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /__tests__/fixtures/all_options.yml: -------------------------------------------------------------------------------- 1 | label1: 2 | - any: 3 | - changed-files: 4 | - any-glob-to-any-file: ['glob'] 5 | - head-branch: ['regexp'] 6 | - base-branch: ['regexp'] 7 | - all: 8 | - changed-files: 9 | - all-globs-to-all-files: ['glob'] 10 | - head-branch: ['regexp'] 11 | - base-branch: ['regexp'] 12 | 13 | label2: 14 | - changed-files: 15 | - any-glob-to-any-file: ['glob'] 16 | - head-branch: ['regexp'] 17 | - base-branch: ['regexp'] 18 | -------------------------------------------------------------------------------- /__tests__/fixtures/any_and_all.yml: -------------------------------------------------------------------------------- 1 | tests: 2 | - any: 3 | - head-branch: ['^tests/', '^test/'] 4 | - changed-files: 5 | - any-glob-to-any-file: ['tests/**/*'] 6 | - all: 7 | - changed-files: 8 | - all-globs-to-all-files: ['!tests/requirements.txt'] 9 | -------------------------------------------------------------------------------- /__tests__/fixtures/branches.yml: -------------------------------------------------------------------------------- 1 | test-branch: 2 | - head-branch: '^test/' 3 | 4 | feature-branch: 5 | - head-branch: '/feature/' 6 | 7 | bug-branch: 8 | - head-branch: '^bug/|fix/' 9 | 10 | array-branch: 11 | - head-branch: ['^array/'] 12 | -------------------------------------------------------------------------------- /__tests__/fixtures/not_supported.yml: -------------------------------------------------------------------------------- 1 | label: 2 | - all: 3 | - unknown: 'this-is-not-supported' 4 | -------------------------------------------------------------------------------- /__tests__/fixtures/only_pdfs.yml: -------------------------------------------------------------------------------- 1 | touched-a-pdf-file: 2 | - changed-files: 3 | - any-glob-to-any-file: ['*.pdf'] 4 | -------------------------------------------------------------------------------- /__tests__/labeler.test.ts: -------------------------------------------------------------------------------- 1 | import * as yaml from 'js-yaml'; 2 | import * as core from '@actions/core'; 3 | import * as fs from 'fs'; 4 | import {checkMatchConfigs} from '../src/labeler'; 5 | import { 6 | MatchConfig, 7 | toMatchConfig, 8 | getLabelConfigMapFromObject, 9 | BaseMatchConfig 10 | } from '../src/api/get-label-configs'; 11 | 12 | jest.mock('@actions/core'); 13 | 14 | beforeAll(() => { 15 | jest.spyOn(core, 'getInput').mockImplementation((name, options) => { 16 | return jest.requireActual('@actions/core').getInput(name, options); 17 | }); 18 | }); 19 | 20 | const loadYaml = (filepath: string) => { 21 | const loadedFile = fs.readFileSync(filepath); 22 | const content = Buffer.from(loadedFile).toString(); 23 | return yaml.load(content); 24 | }; 25 | 26 | describe('getLabelConfigMapFromObject', () => { 27 | const yamlObject = loadYaml('__tests__/fixtures/all_options.yml'); 28 | const expected = new Map(); 29 | expected.set('label1', [ 30 | { 31 | any: [ 32 | {changedFiles: [{anyGlobToAnyFile: ['glob']}]}, 33 | {baseBranch: undefined, headBranch: ['regexp']}, 34 | {baseBranch: ['regexp'], headBranch: undefined} 35 | ] 36 | }, 37 | { 38 | all: [ 39 | {changedFiles: [{allGlobsToAllFiles: ['glob']}]}, 40 | {baseBranch: undefined, headBranch: ['regexp']}, 41 | {baseBranch: ['regexp'], headBranch: undefined} 42 | ] 43 | } 44 | ]); 45 | expected.set('label2', [ 46 | { 47 | any: [ 48 | {changedFiles: [{anyGlobToAnyFile: ['glob']}]}, 49 | {baseBranch: undefined, headBranch: ['regexp']}, 50 | {baseBranch: ['regexp'], headBranch: undefined} 51 | ] 52 | } 53 | ]); 54 | 55 | it('returns a MatchConfig', () => { 56 | const result = getLabelConfigMapFromObject(yamlObject); 57 | expect(result).toEqual(expected); 58 | }); 59 | }); 60 | 61 | describe('toMatchConfig', () => { 62 | describe('when all expected config options are present', () => { 63 | const config = { 64 | 'changed-files': [{'any-glob-to-any-file': ['testing-files']}], 65 | 'head-branch': ['testing-head'], 66 | 'base-branch': ['testing-base'] 67 | }; 68 | const expected: BaseMatchConfig = { 69 | changedFiles: [{anyGlobToAnyFile: ['testing-files']}], 70 | headBranch: ['testing-head'], 71 | baseBranch: ['testing-base'] 72 | }; 73 | 74 | it('returns a MatchConfig object with all options', () => { 75 | const result = toMatchConfig(config); 76 | expect(result).toEqual(expected); 77 | }); 78 | 79 | describe('and there are also unexpected options present', () => { 80 | config['test-test'] = 'testing'; 81 | 82 | it('does not include the unexpected items in the returned MatchConfig object', () => { 83 | const result = toMatchConfig(config); 84 | expect(result).toEqual(expected); 85 | }); 86 | }); 87 | }); 88 | }); 89 | 90 | describe('checkMatchConfigs', () => { 91 | describe('when a single match config is provided', () => { 92 | const matchConfig: MatchConfig[] = [ 93 | {any: [{changedFiles: [{anyGlobToAnyFile: ['*.txt']}]}]} 94 | ]; 95 | 96 | it('returns true when our pattern does match changed files', () => { 97 | const changedFiles = ['foo.txt', 'bar.txt']; 98 | const result = checkMatchConfigs(changedFiles, matchConfig, false); 99 | 100 | expect(result).toBeTruthy(); 101 | }); 102 | 103 | it('returns false when our pattern does not match changed files', () => { 104 | const changedFiles = ['foo.docx']; 105 | const result = checkMatchConfigs(changedFiles, matchConfig, false); 106 | 107 | expect(result).toBeFalsy(); 108 | }); 109 | 110 | it('returns true when either the branch or changed files patter matches', () => { 111 | const matchConfig: MatchConfig[] = [ 112 | { 113 | any: [ 114 | {changedFiles: [{anyGlobToAnyFile: ['*.txt']}]}, 115 | {headBranch: ['some-branch']} 116 | ] 117 | } 118 | ]; 119 | const changedFiles = ['foo.txt', 'bar.txt']; 120 | 121 | const result = checkMatchConfigs(changedFiles, matchConfig, false); 122 | expect(result).toBe(true); 123 | }); 124 | 125 | it('returns false for a file starting with dot if `dot` option is false', () => { 126 | const changedFiles = ['.foo.txt']; 127 | const result = checkMatchConfigs(changedFiles, matchConfig, false); 128 | 129 | expect(result).toBeFalsy(); 130 | }); 131 | 132 | it('returns true for a file starting with dot if `dot` option is true', () => { 133 | const changedFiles = ['.foo.txt']; 134 | const result = checkMatchConfigs(changedFiles, matchConfig, true); 135 | 136 | expect(result).toBeTruthy(); 137 | }); 138 | }); 139 | 140 | describe('when multiple MatchConfigs are supplied', () => { 141 | const matchConfig: MatchConfig[] = [ 142 | {any: [{changedFiles: [{anyGlobToAnyFile: ['*.txt']}]}]}, 143 | {any: [{headBranch: ['some-branch']}]} 144 | ]; 145 | const changedFiles = ['foo.txt', 'bar.md']; 146 | 147 | it('returns false when only one config matches', () => { 148 | const result = checkMatchConfigs(changedFiles, matchConfig, false); 149 | expect(result).toBe(false); 150 | }); 151 | 152 | it('returns true when only both config matches', () => { 153 | const matchConfig: MatchConfig[] = [ 154 | {any: [{changedFiles: [{anyGlobToAnyFile: ['*.txt']}]}]}, 155 | {any: [{headBranch: ['head-branch']}]} 156 | ]; 157 | const result = checkMatchConfigs(changedFiles, matchConfig, false); 158 | expect(result).toBe(true); 159 | }); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /__tests__/main.test.ts: -------------------------------------------------------------------------------- 1 | import {run} from '../src/labeler'; 2 | import * as github from '@actions/github'; 3 | import * as core from '@actions/core'; 4 | import path from 'path'; 5 | import fs from 'fs'; 6 | 7 | jest.mock('@actions/core'); 8 | jest.mock('@actions/github'); 9 | 10 | const gh = github.getOctokit('_'); 11 | const setLabelsMock = jest.spyOn(gh.rest.issues, 'setLabels'); 12 | const reposMock = jest.spyOn(gh.rest.repos, 'getContent'); 13 | const paginateMock = jest.spyOn(gh, 'paginate'); 14 | const getPullMock = jest.spyOn(gh.rest.pulls, 'get'); 15 | const readFileSyncMock = jest.spyOn(fs, 'readFileSync'); 16 | const existsSyncMock = jest.spyOn(fs, 'existsSync'); 17 | const coreErrorMock = jest.spyOn(core, 'error'); 18 | const coreWarningMock = jest.spyOn(core, 'warning'); 19 | const coreSetFailedMock = jest.spyOn(core, 'setFailed'); 20 | const setOutputSpy = jest.spyOn(core, 'setOutput'); 21 | 22 | class HttpError extends Error { 23 | constructor(message: string) { 24 | super(message); 25 | this.name = 'HttpError'; 26 | } 27 | } 28 | 29 | class NotFound extends Error { 30 | constructor(message: string) { 31 | super(message); 32 | this.name = 'NotFound'; 33 | } 34 | } 35 | 36 | const yamlFixtures = { 37 | 'branches.yml': fs.readFileSync('__tests__/fixtures/branches.yml'), 38 | 'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml'), 39 | 'not_supported.yml': fs.readFileSync('__tests__/fixtures/not_supported.yml'), 40 | 'any_and_all.yml': fs.readFileSync('__tests__/fixtures/any_and_all.yml') 41 | }; 42 | 43 | const configureInput = ( 44 | mockInput: Partial<{ 45 | 'repo-token': string; 46 | 'configuration-path': string; 47 | 'sync-labels': boolean; 48 | dot: boolean; 49 | 'pr-number': string[]; 50 | }> 51 | ) => { 52 | jest 53 | .spyOn(core, 'getInput') 54 | .mockImplementation((name: string, ...opts) => mockInput[name]); 55 | jest 56 | .spyOn(core, 'getMultilineInput') 57 | .mockImplementation((name: string, ...opts) => mockInput[name]); 58 | jest 59 | .spyOn(core, 'getBooleanInput') 60 | .mockImplementation((name: string, ...opts) => mockInput[name]); 61 | }; 62 | 63 | afterAll(() => jest.restoreAllMocks()); 64 | 65 | describe('run', () => { 66 | it('(with dot: false) adds labels to PRs that match our glob patterns', async () => { 67 | configureInput({}); 68 | usingLabelerConfigYaml('only_pdfs.yml'); 69 | mockGitHubResponseChangedFiles('foo.pdf'); 70 | getPullMock.mockResolvedValue({ 71 | data: { 72 | labels: [] 73 | } 74 | }); 75 | 76 | await run(); 77 | 78 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 79 | 80 | expect(setLabelsMock).toHaveBeenCalledWith({ 81 | owner: 'monalisa', 82 | repo: 'helloworld', 83 | issue_number: 123, 84 | labels: ['touched-a-pdf-file'] 85 | }); 86 | expect(setOutputSpy).toHaveBeenCalledWith( 87 | 'new-labels', 88 | 'touched-a-pdf-file' 89 | ); 90 | expect(setOutputSpy).toHaveBeenCalledWith( 91 | 'all-labels', 92 | 'touched-a-pdf-file' 93 | ); 94 | }); 95 | 96 | it('(with dot: true) adds labels to PRs that match our glob patterns', async () => { 97 | configureInput({dot: true}); 98 | usingLabelerConfigYaml('only_pdfs.yml'); 99 | mockGitHubResponseChangedFiles('.foo.pdf'); 100 | getPullMock.mockResolvedValue({ 101 | data: { 102 | labels: [] 103 | } 104 | }); 105 | 106 | await run(); 107 | 108 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 109 | expect(setLabelsMock).toHaveBeenCalledWith({ 110 | owner: 'monalisa', 111 | repo: 'helloworld', 112 | issue_number: 123, 113 | labels: ['touched-a-pdf-file'] 114 | }); 115 | expect(setOutputSpy).toHaveBeenCalledWith( 116 | 'new-labels', 117 | 'touched-a-pdf-file' 118 | ); 119 | expect(setOutputSpy).toHaveBeenCalledWith( 120 | 'all-labels', 121 | 'touched-a-pdf-file' 122 | ); 123 | }); 124 | 125 | it('(with dot: false) does not add labels to PRs that do not match our glob patterns', async () => { 126 | configureInput({}); 127 | usingLabelerConfigYaml('only_pdfs.yml'); 128 | mockGitHubResponseChangedFiles('.foo.pdf'); 129 | getPullMock.mockResolvedValue({ 130 | data: { 131 | labels: [] 132 | } 133 | }); 134 | 135 | await run(); 136 | 137 | expect(setLabelsMock).toHaveBeenCalledTimes(0); 138 | expect(setOutputSpy).toHaveBeenCalledWith('new-labels', ''); 139 | expect(setOutputSpy).toHaveBeenCalledWith('all-labels', ''); 140 | }); 141 | 142 | it('(with dot: true) does not add labels to PRs that do not match our glob patterns', async () => { 143 | configureInput({dot: true}); 144 | usingLabelerConfigYaml('only_pdfs.yml'); 145 | mockGitHubResponseChangedFiles('foo.txt'); 146 | 147 | await run(); 148 | 149 | expect(setLabelsMock).toHaveBeenCalledTimes(0); 150 | }); 151 | 152 | it('does not add a label when the match config options are not supported', async () => { 153 | configureInput({}); 154 | usingLabelerConfigYaml('not_supported.yml'); 155 | await run(); 156 | 157 | expect(setLabelsMock).toHaveBeenCalledTimes(0); 158 | }); 159 | 160 | it('adds labels based on the branch names that match the regexp pattern', async () => { 161 | configureInput({}); 162 | github.context.payload.pull_request!.head = {ref: 'test/testing-time'}; 163 | usingLabelerConfigYaml('branches.yml'); 164 | await run(); 165 | 166 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 167 | expect(setLabelsMock).toHaveBeenCalledWith({ 168 | owner: 'monalisa', 169 | repo: 'helloworld', 170 | issue_number: 123, 171 | labels: ['test-branch'] 172 | }); 173 | 174 | expect(setOutputSpy).toHaveBeenCalledWith('new-labels', 'test-branch'); 175 | expect(setOutputSpy).toHaveBeenCalledWith('all-labels', 'test-branch'); 176 | }); 177 | 178 | it('adds multiple labels based on branch names that match different regexp patterns', async () => { 179 | configureInput({}); 180 | github.context.payload.pull_request!.head = { 181 | ref: 'test/feature/123' 182 | }; 183 | usingLabelerConfigYaml('branches.yml'); 184 | await run(); 185 | 186 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 187 | expect(setLabelsMock).toHaveBeenCalledWith({ 188 | owner: 'monalisa', 189 | repo: 'helloworld', 190 | issue_number: 123, 191 | labels: ['test-branch', 'feature-branch'] 192 | }); 193 | 194 | expect(setOutputSpy).toHaveBeenCalledWith( 195 | 'new-labels', 196 | 'test-branch,feature-branch' 197 | ); 198 | expect(setOutputSpy).toHaveBeenCalledWith( 199 | 'all-labels', 200 | 'test-branch,feature-branch' 201 | ); 202 | }); 203 | 204 | it('can support multiple branches by batching', async () => { 205 | configureInput({}); 206 | github.context.payload.pull_request!.head = {ref: 'fix/123'}; 207 | usingLabelerConfigYaml('branches.yml'); 208 | await run(); 209 | 210 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 211 | expect(setLabelsMock).toHaveBeenCalledWith({ 212 | owner: 'monalisa', 213 | repo: 'helloworld', 214 | issue_number: 123, 215 | labels: ['bug-branch'] 216 | }); 217 | 218 | expect(setOutputSpy).toHaveBeenCalledWith('new-labels', 'bug-branch'); 219 | expect(setOutputSpy).toHaveBeenCalledWith('all-labels', 'bug-branch'); 220 | }); 221 | 222 | it('can support multiple branches by providing an array', async () => { 223 | configureInput({}); 224 | github.context.payload.pull_request!.head = {ref: 'array/123'}; 225 | usingLabelerConfigYaml('branches.yml'); 226 | await run(); 227 | 228 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 229 | expect(setLabelsMock).toHaveBeenCalledWith({ 230 | owner: 'monalisa', 231 | repo: 'helloworld', 232 | issue_number: 123, 233 | labels: ['array-branch'] 234 | }); 235 | 236 | expect(setOutputSpy).toHaveBeenCalledWith('new-labels', 'array-branch'); 237 | expect(setOutputSpy).toHaveBeenCalledWith('all-labels', 'array-branch'); 238 | }); 239 | 240 | it('adds a label when matching any and all patterns are provided', async () => { 241 | configureInput({}); 242 | usingLabelerConfigYaml('any_and_all.yml'); 243 | mockGitHubResponseChangedFiles('tests/test.ts'); 244 | await run(); 245 | 246 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 247 | expect(setLabelsMock).toHaveBeenCalledWith({ 248 | owner: 'monalisa', 249 | repo: 'helloworld', 250 | issue_number: 123, 251 | labels: ['tests'] 252 | }); 253 | 254 | expect(setOutputSpy).toHaveBeenCalledWith('new-labels', 'tests'); 255 | expect(setOutputSpy).toHaveBeenCalledWith('all-labels', 'tests'); 256 | }); 257 | 258 | it('does not add a label when not all any and all patterns are matched', async () => { 259 | configureInput({}); 260 | usingLabelerConfigYaml('any_and_all.yml'); 261 | mockGitHubResponseChangedFiles('tests/requirements.txt'); 262 | await run(); 263 | 264 | expect(setLabelsMock).toHaveBeenCalledTimes(0); 265 | }); 266 | 267 | it('(with sync-labels: true) it deletes preexisting PR labels that no longer match the glob pattern', async () => { 268 | configureInput({ 269 | 'repo-token': 'foo', 270 | 'configuration-path': 'bar', 271 | 'sync-labels': true 272 | }); 273 | 274 | usingLabelerConfigYaml('only_pdfs.yml'); 275 | mockGitHubResponseChangedFiles('foo.txt'); 276 | getPullMock.mockResolvedValue({ 277 | data: { 278 | labels: [{name: 'touched-a-pdf-file'}, {name: 'manually-added'}] 279 | } 280 | }); 281 | 282 | await run(); 283 | 284 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 285 | expect(setLabelsMock).toHaveBeenCalledWith({ 286 | owner: 'monalisa', 287 | repo: 'helloworld', 288 | issue_number: 123, 289 | labels: ['manually-added'] 290 | }); 291 | 292 | expect(setOutputSpy).toHaveBeenCalledWith('new-labels', ''); 293 | expect(setOutputSpy).toHaveBeenCalledWith('all-labels', 'manually-added'); 294 | }); 295 | 296 | it('(with sync-labels: false) it issues no delete calls even when there are preexisting PR labels that no longer match the glob pattern', async () => { 297 | configureInput({ 298 | 'repo-token': 'foo', 299 | 'configuration-path': 'bar', 300 | 'sync-labels': false 301 | }); 302 | 303 | usingLabelerConfigYaml('only_pdfs.yml'); 304 | mockGitHubResponseChangedFiles('foo.txt'); 305 | getPullMock.mockResolvedValue({ 306 | data: { 307 | labels: [{name: 'touched-a-pdf-file'}, {name: 'manually-added'}] 308 | } 309 | }); 310 | 311 | await run(); 312 | 313 | expect(setLabelsMock).toHaveBeenCalledTimes(0); 314 | expect(setOutputSpy).toHaveBeenCalledWith('new-labels', ''); 315 | expect(setOutputSpy).toHaveBeenCalledWith( 316 | 'all-labels', 317 | 'touched-a-pdf-file,manually-added' 318 | ); 319 | }); 320 | 321 | it('(with sync-labels: false) it only logs the excess labels', async () => { 322 | configureInput({ 323 | 'repo-token': 'foo', 324 | 'configuration-path': 'bar', 325 | 'sync-labels': false 326 | }); 327 | 328 | usingLabelerConfigYaml('only_pdfs.yml'); 329 | mockGitHubResponseChangedFiles('foo.pdf'); 330 | 331 | const existingLabels = Array.from({length: 100}).map((_, idx) => ({ 332 | name: `existing-label-${idx}` 333 | })); 334 | getPullMock.mockResolvedValue({ 335 | data: { 336 | labels: existingLabels 337 | } 338 | }); 339 | 340 | await run(); 341 | 342 | expect(setLabelsMock).toHaveBeenCalledTimes(0); 343 | 344 | expect(coreWarningMock).toHaveBeenCalledTimes(1); 345 | expect(coreWarningMock).toHaveBeenCalledWith( 346 | 'Maximum of 100 labels allowed. Excess labels: touched-a-pdf-file', 347 | {title: 'Label limit for a PR exceeded'} 348 | ); 349 | const allLabels: string = existingLabels.map(i => i.name).join(','); 350 | expect(setOutputSpy).toHaveBeenCalledWith('new-labels', ''); 351 | expect(setOutputSpy).toHaveBeenCalledWith('all-labels', allLabels); 352 | }); 353 | 354 | it('(with pr-number: array of one item, uses the PR number specified in the parameters', async () => { 355 | configureInput({ 356 | 'repo-token': 'foo', 357 | 'configuration-path': 'bar', 358 | 'pr-number': ['104'] 359 | }); 360 | 361 | usingLabelerConfigYaml('only_pdfs.yml'); 362 | mockGitHubResponseChangedFiles('foo.pdf'); 363 | 364 | getPullMock.mockResolvedValue({ 365 | data: { 366 | labels: [{name: 'manually-added'}] 367 | } 368 | }); 369 | 370 | await run(); 371 | expect(setLabelsMock).toHaveBeenCalledTimes(1); 372 | expect(setLabelsMock).toHaveBeenCalledWith({ 373 | owner: 'monalisa', 374 | repo: 'helloworld', 375 | issue_number: 104, 376 | labels: ['manually-added', 'touched-a-pdf-file'] 377 | }); 378 | expect(setOutputSpy).toHaveBeenCalledWith( 379 | 'new-labels', 380 | 'touched-a-pdf-file' 381 | ); 382 | expect(setOutputSpy).toHaveBeenCalledWith( 383 | 'all-labels', 384 | 'manually-added,touched-a-pdf-file' 385 | ); 386 | }); 387 | 388 | it('(with pr-number: array of two items, uses the PR number specified in the parameters', async () => { 389 | configureInput({ 390 | 'repo-token': 'foo', 391 | 'configuration-path': 'bar', 392 | 'pr-number': ['104', '150'] 393 | }); 394 | 395 | usingLabelerConfigYaml('only_pdfs.yml'); 396 | mockGitHubResponseChangedFiles('foo.pdf'); 397 | 398 | getPullMock.mockResolvedValueOnce({ 399 | data: { 400 | labels: [{name: 'manually-added'}] 401 | } 402 | }); 403 | 404 | getPullMock.mockResolvedValueOnce({ 405 | data: { 406 | labels: [] 407 | } 408 | }); 409 | 410 | await run(); 411 | expect(setLabelsMock).toHaveBeenCalledTimes(2); 412 | expect(setLabelsMock).toHaveBeenCalledWith({ 413 | owner: 'monalisa', 414 | repo: 'helloworld', 415 | issue_number: 104, 416 | labels: ['manually-added', 'touched-a-pdf-file'] 417 | }); 418 | expect(setLabelsMock).toHaveBeenCalledWith({ 419 | owner: 'monalisa', 420 | repo: 'helloworld', 421 | issue_number: 150, 422 | labels: ['touched-a-pdf-file'] 423 | }); 424 | expect(setOutputSpy).toHaveBeenCalledWith( 425 | 'new-labels', 426 | 'touched-a-pdf-file' 427 | ); 428 | expect(setOutputSpy).toHaveBeenCalledWith( 429 | 'all-labels', 430 | 'manually-added,touched-a-pdf-file' 431 | ); 432 | }); 433 | 434 | it('does not add labels to PRs that have no changed files', async () => { 435 | usingLabelerConfigYaml('only_pdfs.yml'); 436 | mockGitHubResponseChangedFiles(); 437 | 438 | await run(); 439 | 440 | expect(setLabelsMock).toHaveBeenCalledTimes(0); 441 | }); 442 | 443 | it('should use local configuration file if it exists', async () => { 444 | const configFile = 'only_pdfs.yml'; 445 | const configFilePath = path.join(__dirname, 'fixtures', configFile); 446 | mockGitHubResponseChangedFiles('foo.pdf'); 447 | const readFileSyncOptions = {encoding: 'utf8'}; 448 | 449 | configureInput({ 450 | 'configuration-path': configFilePath 451 | }); 452 | await run(); 453 | 454 | expect(existsSyncMock).toHaveBeenCalledWith(configFilePath); 455 | expect(readFileSyncMock).toHaveBeenCalledWith( 456 | configFilePath, 457 | readFileSyncOptions 458 | ); 459 | expect(reposMock).not.toHaveBeenCalled(); 460 | }); 461 | 462 | it('should fetch configuration file from API if it does not exist locally', async () => { 463 | const configFilePath = 'non_existed_path/labeler.yml'; 464 | mockGitHubResponseChangedFiles('foo.pdf'); 465 | configureInput({ 466 | 'configuration-path': configFilePath 467 | }); 468 | await run(); 469 | expect(existsSyncMock).toHaveBeenCalledWith(configFilePath); 470 | expect(readFileSyncMock).not.toHaveBeenCalled(); 471 | expect(reposMock).toHaveBeenCalled(); 472 | }); 473 | 474 | test.each([ 475 | [new HttpError('Error message')], 476 | [new NotFound('Error message')] 477 | ])( 478 | 'should warn if configuration file could not be fetched through the API, log error and fail the action', 479 | async error => { 480 | const configFilePath = 'non_existed_path/labeler.yml'; 481 | reposMock.mockImplementation(() => { 482 | throw error; 483 | }); 484 | const warningMessage = `The config file was not found at ${configFilePath}. Make sure it exists and that this action has the correct access rights.`; 485 | mockGitHubResponseChangedFiles('foo.pdf'); 486 | configureInput({ 487 | 'configuration-path': configFilePath 488 | }); 489 | 490 | await run(); 491 | 492 | expect(coreWarningMock).toHaveBeenCalledWith(warningMessage); 493 | expect(coreErrorMock).toHaveBeenCalledWith(error); 494 | expect(coreSetFailedMock).toHaveBeenCalledWith(error.message); 495 | } 496 | ); 497 | }); 498 | 499 | function usingLabelerConfigYaml(fixtureName: keyof typeof yamlFixtures): void { 500 | reposMock.mockResolvedValue({ 501 | data: {content: yamlFixtures[fixtureName], encoding: 'utf8'} 502 | }); 503 | } 504 | 505 | function mockGitHubResponseChangedFiles(...files: string[]): void { 506 | const returnValue = files.map(f => ({filename: f})); 507 | paginateMock.mockReturnValue(returnValue); 508 | } 509 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Labeler' 2 | description: 'Automatically label new pull requests based on the paths of files being changed' 3 | author: 'GitHub' 4 | inputs: 5 | repo-token: 6 | description: 'The GitHub token used to manage labels' 7 | required: false 8 | default: ${{ github.token }} 9 | configuration-path: 10 | description: 'The path for the label configurations' 11 | default: '.github/labeler.yml' 12 | required: false 13 | sync-labels: 14 | description: 'Whether or not to remove labels when matching files are reverted' 15 | default: false 16 | required: false 17 | dot: 18 | description: 'Whether or not to auto-include paths starting with dot (e.g. `.github`)' 19 | default: true 20 | required: false 21 | pr-number: 22 | description: 'The pull request number(s)' 23 | required: false 24 | 25 | outputs: 26 | new-labels: 27 | description: 'A comma-separated list of all new labels' 28 | all-labels: 29 | description: 'A comma-separated list of all labels that the PR contains' 30 | runs: 31 | using: 'node20' 32 | main: 'dist/index.js' 33 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | transform: { 7 | '^.+\\.ts$': 'ts-jest' 8 | }, 9 | verbose: true 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "labeler", 3 | "version": "5.0.0", 4 | "description": "Labels pull requests by files altered", 5 | "main": "lib/main.js", 6 | "scripts": { 7 | "build": "tsc && ncc build lib/main.js", 8 | "format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"", 9 | "format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check \"**/*.{ts,yml,yaml}\"", 10 | "lint": "eslint --config ./.eslintrc.js \"**/*.ts\"", 11 | "lint:fix": "eslint --config ./.eslintrc.js \"**/*.ts\" --fix", 12 | "test": "jest" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/actions/labeler.git" 17 | }, 18 | "keywords": [ 19 | "github", 20 | "actions", 21 | "label", 22 | "labeler" 23 | ], 24 | "author": "GitHub", 25 | "license": "MIT", 26 | "dependencies": { 27 | "@actions/core": "^1.11.1", 28 | "@actions/github": "^6.0.0", 29 | "@octokit/plugin-retry": "^6.0.0", 30 | "js-yaml": "^4.1.0", 31 | "lodash.isequal": "^4.5.0", 32 | "minimatch": "^10.0.1" 33 | }, 34 | "devDependencies": { 35 | "@types/jest": "^29.5.14", 36 | "@types/js-yaml": "^4.0.9", 37 | "@types/lodash.isequal": "^4.5.8", 38 | "@types/minimatch": "^5.1.2", 39 | "@types/node": "^20.11.30", 40 | "@typescript-eslint/eslint-plugin": "^7.3.1", 41 | "@typescript-eslint/parser": "^7.18.0", 42 | "@vercel/ncc": "^0.38.3", 43 | "eslint": "^8.57.0", 44 | "eslint-config-prettier": "^9.1.0", 45 | "eslint-plugin-jest": "^28.11.0", 46 | "eslint-plugin-node": "^11.1.0", 47 | "jest": "^29.7.0", 48 | "prettier": "^3.4.2", 49 | "ts-jest": "^29.2.5", 50 | "typescript": "^5.7.3" 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/api/get-changed-files.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | import {ClientType} from './types'; 4 | 5 | export const getChangedFiles = async ( 6 | client: ClientType, 7 | prNumber: number 8 | ): Promise => { 9 | const listFilesOptions = client.rest.pulls.listFiles.endpoint.merge({ 10 | owner: github.context.repo.owner, 11 | repo: github.context.repo.repo, 12 | pull_number: prNumber 13 | }); 14 | 15 | const listFilesResponse = await client.paginate(listFilesOptions); 16 | const changedFiles = listFilesResponse.map((f: any) => f.filename); 17 | 18 | core.debug('found changed files:'); 19 | for (const file of changedFiles) { 20 | core.debug(' ' + file); 21 | } 22 | 23 | return changedFiles; 24 | }; 25 | -------------------------------------------------------------------------------- /src/api/get-changed-pull-requests.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | import {getChangedFiles} from './get-changed-files'; 4 | import {ClientType} from './types'; 5 | 6 | export async function* getPullRequests( 7 | client: ClientType, 8 | prNumbers: number[] 9 | ) { 10 | for (const prNumber of prNumbers) { 11 | core.debug(`looking for pr #${prNumber}`); 12 | let prData: any; 13 | try { 14 | const result = await client.rest.pulls.get({ 15 | owner: github.context.repo.owner, 16 | repo: github.context.repo.repo, 17 | pull_number: prNumber 18 | }); 19 | prData = result.data; 20 | } catch (error: any) { 21 | core.warning(`Could not find pull request #${prNumber}, skipping`); 22 | continue; 23 | } 24 | 25 | core.debug(`fetching changed files for pr #${prNumber}`); 26 | const changedFiles: string[] = await getChangedFiles(client, prNumber); 27 | if (!changedFiles.length) { 28 | core.warning(`Pull request #${prNumber} has no changed files, skipping`); 29 | continue; 30 | } 31 | 32 | yield { 33 | data: prData, 34 | number: prNumber, 35 | changedFiles 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/api/get-content.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github'; 2 | import {ClientType} from './types'; 3 | 4 | export const getContent = async ( 5 | client: ClientType, 6 | repoPath: string 7 | ): Promise => { 8 | const response: any = await client.rest.repos.getContent({ 9 | owner: github.context.repo.owner, 10 | repo: github.context.repo.repo, 11 | path: repoPath, 12 | ref: github.context.sha 13 | }); 14 | 15 | return Buffer.from(response.data.content, response.data.encoding).toString(); 16 | }; 17 | -------------------------------------------------------------------------------- /src/api/get-label-configs.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as yaml from 'js-yaml'; 3 | import fs from 'fs'; 4 | import {ClientType} from './types'; 5 | import {getContent} from './get-content'; 6 | 7 | import { 8 | ChangedFilesMatchConfig, 9 | toChangedFilesMatchConfig 10 | } from '../changedFiles'; 11 | 12 | import {toBranchMatchConfig, BranchMatchConfig} from '../branch'; 13 | 14 | export interface MatchConfig { 15 | all?: BaseMatchConfig[]; 16 | any?: BaseMatchConfig[]; 17 | } 18 | 19 | export type BaseMatchConfig = BranchMatchConfig & ChangedFilesMatchConfig; 20 | 21 | const ALLOWED_CONFIG_KEYS = ['changed-files', 'head-branch', 'base-branch']; 22 | 23 | export const getLabelConfigs = ( 24 | client: ClientType, 25 | configurationPath: string 26 | ): Promise> => 27 | Promise.resolve() 28 | .then(() => { 29 | if (!fs.existsSync(configurationPath)) { 30 | core.info( 31 | `The configuration file (path: ${configurationPath}) was not found locally, fetching via the api` 32 | ); 33 | 34 | return getContent(client, configurationPath); 35 | } 36 | 37 | core.info( 38 | `The configuration file (path: ${configurationPath}) was found locally, reading from the file` 39 | ); 40 | 41 | return fs.readFileSync(configurationPath, { 42 | encoding: 'utf8' 43 | }); 44 | }) 45 | .catch(error => { 46 | if (error.name == 'HttpError' || error.name == 'NotFound') { 47 | core.warning( 48 | `The config file was not found at ${configurationPath}. Make sure it exists and that this action has the correct access rights.` 49 | ); 50 | } 51 | return Promise.reject(error); 52 | }) 53 | .then(configuration => { 54 | // loads (hopefully) a `{[label:string]: MatchConfig[]}`, but is `any`: 55 | const configObject: any = yaml.load(configuration); 56 | 57 | // transform `any` => `Map` or throw if yaml is malformed: 58 | return getLabelConfigMapFromObject(configObject); 59 | }); 60 | 61 | export function getLabelConfigMapFromObject( 62 | configObject: any 63 | ): Map { 64 | const labelMap: Map = new Map(); 65 | for (const label in configObject) { 66 | const configOptions = configObject[label]; 67 | if ( 68 | !Array.isArray(configOptions) || 69 | !configOptions.every(opts => typeof opts === 'object') 70 | ) { 71 | throw Error( 72 | `found unexpected type for label '${label}' (should be array of config options)` 73 | ); 74 | } 75 | const matchConfigs = configOptions.reduce( 76 | (updatedConfig, configValue) => { 77 | if (!configValue) { 78 | return updatedConfig; 79 | } 80 | 81 | Object.entries(configValue).forEach(([key, value]) => { 82 | // If the top level `any` or `all` keys are provided then set them, and convert their values to 83 | // our config objects. 84 | if (key === 'any' || key === 'all') { 85 | if (Array.isArray(value)) { 86 | const newConfigs = value.map(toMatchConfig); 87 | updatedConfig.push({[key]: newConfigs}); 88 | } 89 | } else if (ALLOWED_CONFIG_KEYS.includes(key)) { 90 | const newMatchConfig = toMatchConfig({[key]: value}); 91 | // Find or set the `any` key so that we can add these properties to that rule, 92 | // Or create a new `any` key and add that to our array of configs. 93 | const indexOfAny = updatedConfig.findIndex(mc => !!mc['any']); 94 | if (indexOfAny >= 0) { 95 | updatedConfig[indexOfAny].any?.push(newMatchConfig); 96 | } else { 97 | updatedConfig.push({any: [newMatchConfig]}); 98 | } 99 | } else { 100 | // Log the key that we don't know what to do with. 101 | core.info(`An unknown config option was under ${label}: ${key}`); 102 | } 103 | }); 104 | 105 | return updatedConfig; 106 | }, 107 | [] 108 | ); 109 | 110 | if (matchConfigs.length) { 111 | labelMap.set(label, matchConfigs); 112 | } 113 | } 114 | 115 | return labelMap; 116 | } 117 | 118 | export function toMatchConfig(config: any): BaseMatchConfig { 119 | const changedFilesConfig = toChangedFilesMatchConfig(config); 120 | const branchConfig = toBranchMatchConfig(config); 121 | 122 | return { 123 | ...changedFilesConfig, 124 | ...branchConfig 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-changed-files'; 2 | export * from './get-changed-pull-requests'; 3 | export * from './get-content'; 4 | export * from './get-label-configs'; 5 | export * from './set-labels'; 6 | export * from './types'; 7 | -------------------------------------------------------------------------------- /src/api/set-labels.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github'; 2 | import {ClientType} from './types'; 3 | 4 | export const setLabels = async ( 5 | client: ClientType, 6 | prNumber: number, 7 | labels: string[] 8 | ) => { 9 | await client.rest.issues.setLabels({ 10 | owner: github.context.repo.owner, 11 | repo: github.context.repo.repo, 12 | issue_number: prNumber, 13 | labels: labels 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/api/types.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github'; 2 | export type ClientType = ReturnType; 3 | -------------------------------------------------------------------------------- /src/branch.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | 4 | export interface BranchMatchConfig { 5 | headBranch?: string[]; 6 | baseBranch?: string[]; 7 | } 8 | 9 | type BranchBase = 'base' | 'head'; 10 | 11 | export function toBranchMatchConfig(config: any): BranchMatchConfig { 12 | if (!config['head-branch'] && !config['base-branch']) { 13 | return {}; 14 | } 15 | 16 | const branchConfig = { 17 | headBranch: config['head-branch'], 18 | baseBranch: config['base-branch'] 19 | }; 20 | 21 | for (const branchName in branchConfig) { 22 | if (typeof branchConfig[branchName] === 'string') { 23 | branchConfig[branchName] = [branchConfig[branchName]]; 24 | } 25 | } 26 | 27 | return branchConfig; 28 | } 29 | 30 | export function getBranchName(branchBase: BranchBase): string | undefined { 31 | const pullRequest = github.context.payload.pull_request; 32 | if (!pullRequest) { 33 | return undefined; 34 | } 35 | 36 | if (branchBase === 'base') { 37 | return pullRequest.base?.ref; 38 | } else { 39 | return pullRequest.head?.ref; 40 | } 41 | } 42 | 43 | export function checkAnyBranch( 44 | regexps: string[], 45 | branchBase: BranchBase 46 | ): boolean { 47 | const branchName = getBranchName(branchBase); 48 | if (!branchName) { 49 | core.debug(` no branch name`); 50 | return false; 51 | } 52 | 53 | core.debug(` checking "branch" pattern against ${branchName}`); 54 | const matchers = regexps.map(regexp => new RegExp(regexp)); 55 | for (const matcher of matchers) { 56 | if (matchBranchPattern(matcher, branchName)) { 57 | core.debug(` "branch" patterns matched against ${branchName}`); 58 | return true; 59 | } 60 | } 61 | 62 | core.debug(` "branch" patterns did not match against ${branchName}`); 63 | return false; 64 | } 65 | 66 | export function checkAllBranch( 67 | regexps: string[], 68 | branchBase: BranchBase 69 | ): boolean { 70 | const branchName = getBranchName(branchBase); 71 | if (!branchName) { 72 | core.debug(` cannot fetch branch name from the pull request`); 73 | return false; 74 | } 75 | 76 | core.debug(` checking "branch" pattern against ${branchName}`); 77 | const matchers = regexps.map(regexp => new RegExp(regexp)); 78 | for (const matcher of matchers) { 79 | if (!matchBranchPattern(matcher, branchName)) { 80 | core.debug(` "branch" patterns did not match against ${branchName}`); 81 | return false; 82 | } 83 | } 84 | 85 | core.debug(` "branch" patterns matched against ${branchName}`); 86 | return true; 87 | } 88 | 89 | function matchBranchPattern(matcher: RegExp, branchName: string): boolean { 90 | core.debug(` - ${matcher}`); 91 | if (matcher.test(branchName)) { 92 | core.debug(` "branch" pattern matched`); 93 | return true; 94 | } 95 | 96 | core.debug(` ${matcher} did not match`); 97 | return false; 98 | } 99 | -------------------------------------------------------------------------------- /src/changedFiles.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | import {Minimatch} from 'minimatch'; 4 | import {printPattern, isObject, kebabToCamel} from './utils'; 5 | 6 | export interface ChangedFilesMatchConfig { 7 | changedFiles?: ChangedFilesGlobPatternsConfig[]; 8 | } 9 | 10 | interface ChangedFilesGlobPatternsConfig { 11 | anyGlobToAnyFile?: string[]; 12 | anyGlobToAllFiles?: string[]; 13 | allGlobsToAnyFile?: string[]; 14 | allGlobsToAllFiles?: string[]; 15 | } 16 | 17 | type ClientType = ReturnType; 18 | 19 | const ALLOWED_FILES_CONFIG_KEYS = [ 20 | 'any-glob-to-any-file', 21 | 'any-glob-to-all-files', 22 | 'all-globs-to-any-file', 23 | 'all-globs-to-all-files' 24 | ]; 25 | 26 | export async function getChangedFiles( 27 | client: ClientType, 28 | prNumber: number 29 | ): Promise { 30 | const listFilesOptions = client.rest.pulls.listFiles.endpoint.merge({ 31 | owner: github.context.repo.owner, 32 | repo: github.context.repo.repo, 33 | pull_number: prNumber 34 | }); 35 | 36 | const listFilesResponse = await client.paginate(listFilesOptions); 37 | const changedFiles = listFilesResponse.map((f: any) => f.filename); 38 | 39 | core.debug('found changed files:'); 40 | for (const file of changedFiles) { 41 | core.debug(' ' + file); 42 | } 43 | 44 | return changedFiles; 45 | } 46 | 47 | export function toChangedFilesMatchConfig( 48 | config: any 49 | ): ChangedFilesMatchConfig { 50 | if (!config['changed-files'] || !config['changed-files'].length) { 51 | return {}; 52 | } 53 | const changedFilesConfigs = Array.isArray(config['changed-files']) 54 | ? config['changed-files'] 55 | : [config['changed-files']]; 56 | 57 | const validChangedFilesConfigs: ChangedFilesGlobPatternsConfig[] = []; 58 | 59 | changedFilesConfigs.forEach(changedFilesConfig => { 60 | if (!isObject(changedFilesConfig)) { 61 | throw new Error( 62 | `The "changed-files" section must have a valid config structure. Please read the action documentation for more information` 63 | ); 64 | } 65 | 66 | const changedFilesConfigKeys = Object.keys(changedFilesConfig); 67 | const invalidKeys = changedFilesConfigKeys.filter( 68 | key => !ALLOWED_FILES_CONFIG_KEYS.includes(key) 69 | ); 70 | 71 | if (invalidKeys.length) { 72 | throw new Error( 73 | `Unknown config options were under "changed-files": ${invalidKeys.join( 74 | ', ' 75 | )}` 76 | ); 77 | } 78 | 79 | changedFilesConfigKeys.forEach(key => { 80 | validChangedFilesConfigs.push({ 81 | [kebabToCamel(key)]: Array.isArray(changedFilesConfig[key]) 82 | ? changedFilesConfig[key] 83 | : [changedFilesConfig[key]] 84 | }); 85 | }); 86 | }); 87 | 88 | return { 89 | changedFiles: validChangedFilesConfigs 90 | }; 91 | } 92 | 93 | export function checkAnyChangedFiles( 94 | changedFiles: string[], 95 | globPatternsConfigs: ChangedFilesGlobPatternsConfig[], 96 | dot: boolean 97 | ): boolean { 98 | core.debug(` checking "changed-files" patterns`); 99 | 100 | for (const globPatternsConfig of globPatternsConfigs) { 101 | if (globPatternsConfig.anyGlobToAnyFile) { 102 | if ( 103 | checkIfAnyGlobMatchesAnyFile( 104 | changedFiles, 105 | globPatternsConfig.anyGlobToAnyFile, 106 | dot 107 | ) 108 | ) { 109 | core.debug(` "changed-files" matched`); 110 | return true; 111 | } 112 | } 113 | 114 | if (globPatternsConfig.anyGlobToAllFiles) { 115 | if ( 116 | checkIfAnyGlobMatchesAllFiles( 117 | changedFiles, 118 | globPatternsConfig.anyGlobToAllFiles, 119 | dot 120 | ) 121 | ) { 122 | core.debug(` "changed-files" matched`); 123 | return true; 124 | } 125 | } 126 | 127 | if (globPatternsConfig.allGlobsToAnyFile) { 128 | if ( 129 | checkIfAllGlobsMatchAnyFile( 130 | changedFiles, 131 | globPatternsConfig.allGlobsToAnyFile, 132 | dot 133 | ) 134 | ) { 135 | core.debug(` "changed-files" matched`); 136 | return true; 137 | } 138 | } 139 | 140 | if (globPatternsConfig.allGlobsToAllFiles) { 141 | if ( 142 | checkIfAllGlobsMatchAllFiles( 143 | changedFiles, 144 | globPatternsConfig.allGlobsToAllFiles, 145 | dot 146 | ) 147 | ) { 148 | core.debug(` "changed-files" matched`); 149 | return true; 150 | } 151 | } 152 | } 153 | 154 | core.debug(` "changed-files" did not match`); 155 | return false; 156 | } 157 | 158 | export function checkAllChangedFiles( 159 | changedFiles: string[], 160 | globPatternsConfigs: ChangedFilesGlobPatternsConfig[], 161 | dot: boolean 162 | ): boolean { 163 | core.debug(` checking "changed-files" patterns`); 164 | 165 | for (const globPatternsConfig of globPatternsConfigs) { 166 | if (globPatternsConfig.anyGlobToAnyFile) { 167 | if ( 168 | !checkIfAnyGlobMatchesAnyFile( 169 | changedFiles, 170 | globPatternsConfig.anyGlobToAnyFile, 171 | dot 172 | ) 173 | ) { 174 | core.debug(` "changed-files" did not match`); 175 | return false; 176 | } 177 | } 178 | 179 | if (globPatternsConfig.anyGlobToAllFiles) { 180 | if ( 181 | !checkIfAnyGlobMatchesAllFiles( 182 | changedFiles, 183 | globPatternsConfig.anyGlobToAllFiles, 184 | dot 185 | ) 186 | ) { 187 | core.debug(` "changed-files" did not match`); 188 | return false; 189 | } 190 | } 191 | 192 | if (globPatternsConfig.allGlobsToAnyFile) { 193 | if ( 194 | !checkIfAllGlobsMatchAnyFile( 195 | changedFiles, 196 | globPatternsConfig.allGlobsToAnyFile, 197 | dot 198 | ) 199 | ) { 200 | core.debug(` "changed-files" did not match`); 201 | return false; 202 | } 203 | } 204 | 205 | if (globPatternsConfig.allGlobsToAllFiles) { 206 | if ( 207 | !checkIfAllGlobsMatchAllFiles( 208 | changedFiles, 209 | globPatternsConfig.allGlobsToAllFiles, 210 | dot 211 | ) 212 | ) { 213 | core.debug(` "changed-files" did not match`); 214 | return false; 215 | } 216 | } 217 | } 218 | 219 | core.debug(` "changed-files" patterns matched`); 220 | return true; 221 | } 222 | 223 | export function checkIfAnyGlobMatchesAnyFile( 224 | changedFiles: string[], 225 | globs: string[], 226 | dot: boolean 227 | ): boolean { 228 | core.debug(` checking "any-glob-to-any-file" config patterns`); 229 | const matchers = globs.map(g => new Minimatch(g, {dot})); 230 | 231 | for (const matcher of matchers) { 232 | const matchedFile = changedFiles.find(changedFile => { 233 | core.debug( 234 | ` checking "${printPattern( 235 | matcher 236 | )}" pattern against ${changedFile}` 237 | ); 238 | 239 | return matcher.match(changedFile); 240 | }); 241 | 242 | if (matchedFile) { 243 | core.debug( 244 | ` "${printPattern(matcher)}" pattern matched ${matchedFile}` 245 | ); 246 | return true; 247 | } 248 | } 249 | 250 | core.debug(` none of the patterns matched any of the files`); 251 | return false; 252 | } 253 | 254 | export function checkIfAllGlobsMatchAnyFile( 255 | changedFiles: string[], 256 | globs: string[], 257 | dot: boolean 258 | ): boolean { 259 | core.debug(` checking "all-globs-to-any-file" config patterns`); 260 | const matchers = globs.map(g => new Minimatch(g, {dot})); 261 | 262 | for (const changedFile of changedFiles) { 263 | const mismatchedGlob = matchers.find(matcher => { 264 | core.debug( 265 | ` checking "${printPattern( 266 | matcher 267 | )}" pattern against ${changedFile}` 268 | ); 269 | 270 | return !matcher.match(changedFile); 271 | }); 272 | 273 | if (mismatchedGlob) { 274 | core.debug( 275 | ` "${printPattern( 276 | mismatchedGlob 277 | )}" pattern did not match ${changedFile}` 278 | ); 279 | 280 | continue; 281 | } 282 | 283 | core.debug(` all patterns matched ${changedFile}`); 284 | return true; 285 | } 286 | 287 | core.debug(` none of the files matched all patterns`); 288 | return false; 289 | } 290 | 291 | export function checkIfAnyGlobMatchesAllFiles( 292 | changedFiles: string[], 293 | globs: string[], 294 | dot: boolean 295 | ): boolean { 296 | core.debug(` checking "any-glob-to-all-files" config patterns`); 297 | const matchers = globs.map(g => new Minimatch(g, {dot})); 298 | 299 | for (const matcher of matchers) { 300 | const mismatchedFile = changedFiles.find(changedFile => { 301 | core.debug( 302 | ` checking "${printPattern( 303 | matcher 304 | )}" pattern against ${changedFile}` 305 | ); 306 | 307 | return !matcher.match(changedFile); 308 | }); 309 | 310 | if (mismatchedFile) { 311 | core.debug( 312 | ` "${printPattern( 313 | matcher 314 | )}" pattern did not match ${mismatchedFile}` 315 | ); 316 | 317 | continue; 318 | } 319 | 320 | core.debug(` "${printPattern(matcher)}" pattern matched all files`); 321 | return true; 322 | } 323 | 324 | core.debug(` none of the patterns matched all files`); 325 | return false; 326 | } 327 | 328 | export function checkIfAllGlobsMatchAllFiles( 329 | changedFiles: string[], 330 | globs: string[], 331 | dot: boolean 332 | ): boolean { 333 | core.debug(` checking "all-globs-to-all-files" config patterns`); 334 | const matchers = globs.map(g => new Minimatch(g, {dot})); 335 | 336 | for (const changedFile of changedFiles) { 337 | const mismatchedGlob = matchers.find(matcher => { 338 | core.debug( 339 | ` checking "${printPattern( 340 | matcher 341 | )}" pattern against ${changedFile}` 342 | ); 343 | 344 | return !matcher.match(changedFile); 345 | }); 346 | 347 | if (mismatchedGlob) { 348 | core.debug( 349 | ` "${printPattern( 350 | mismatchedGlob 351 | )}" pattern did not match ${changedFile}` 352 | ); 353 | 354 | return false; 355 | } 356 | } 357 | 358 | core.debug(` all patterns matched all files`); 359 | return true; 360 | } 361 | -------------------------------------------------------------------------------- /src/get-inputs/get-inputs.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import {getPrNumbers} from './get-pr-numbers'; 3 | 4 | export const getInputs = () => ({ 5 | token: core.getInput('repo-token'), 6 | configPath: core.getInput('configuration-path', {required: true}), 7 | syncLabels: core.getBooleanInput('sync-labels'), 8 | dot: core.getBooleanInput('dot'), 9 | prNumbers: getPrNumbers() 10 | }); 11 | -------------------------------------------------------------------------------- /src/get-inputs/get-pr-numbers.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | 4 | const getPrNumberFromContext = () => 5 | github.context.payload.pull_request?.number; 6 | 7 | export const getPrNumbers = (): number[] => { 8 | const prInput = core.getMultilineInput('pr-number'); 9 | 10 | if (!prInput?.length) { 11 | return [getPrNumberFromContext()].filter(Boolean) as number[]; 12 | } 13 | 14 | const result: number[] = []; 15 | 16 | for (const line of prInput) { 17 | const prNumber = parseInt(line, 10); 18 | 19 | if (isNaN(prNumber) && prNumber <= 0) { 20 | core.warning(`'${prNumber}' is not a valid pull request number`); 21 | continue; 22 | } 23 | 24 | result.push(prNumber); 25 | } 26 | 27 | return result; 28 | }; 29 | -------------------------------------------------------------------------------- /src/get-inputs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-inputs'; 2 | -------------------------------------------------------------------------------- /src/labeler.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | import * as pluginRetry from '@octokit/plugin-retry'; 4 | import * as api from './api'; 5 | import isEqual from 'lodash.isequal'; 6 | import {getInputs} from './get-inputs'; 7 | 8 | import {BaseMatchConfig, MatchConfig} from './api/get-label-configs'; 9 | 10 | import {checkAllChangedFiles, checkAnyChangedFiles} from './changedFiles'; 11 | 12 | import {checkAnyBranch, checkAllBranch} from './branch'; 13 | 14 | type ClientType = ReturnType; 15 | 16 | // GitHub Issues cannot have more than 100 labels 17 | const GITHUB_MAX_LABELS = 100; 18 | 19 | export const run = () => 20 | labeler().catch(error => { 21 | core.error(error); 22 | core.setFailed(error.message); 23 | }); 24 | 25 | async function labeler() { 26 | const {token, configPath, syncLabels, dot, prNumbers} = getInputs(); 27 | 28 | if (!prNumbers.length) { 29 | core.warning('Could not get pull request number(s), exiting'); 30 | return; 31 | } 32 | 33 | const client: ClientType = github.getOctokit(token, {}, pluginRetry.retry); 34 | 35 | const pullRequests = api.getPullRequests(client, prNumbers); 36 | 37 | for await (const pullRequest of pullRequests) { 38 | const labelConfigs: Map = await api.getLabelConfigs( 39 | client, 40 | configPath 41 | ); 42 | const preexistingLabels = pullRequest.data.labels.map(l => l.name); 43 | const allLabels: Set = new Set(preexistingLabels); 44 | 45 | for (const [label, configs] of labelConfigs.entries()) { 46 | core.debug(`processing ${label}`); 47 | if (checkMatchConfigs(pullRequest.changedFiles, configs, dot)) { 48 | allLabels.add(label); 49 | } else if (syncLabels) { 50 | allLabels.delete(label); 51 | } 52 | } 53 | 54 | const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS); 55 | const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS); 56 | 57 | let newLabels: string[] = []; 58 | 59 | try { 60 | if (!isEqual(labelsToAdd, preexistingLabels)) { 61 | await api.setLabels(client, pullRequest.number, labelsToAdd); 62 | newLabels = labelsToAdd.filter( 63 | label => !preexistingLabels.includes(label) 64 | ); 65 | } 66 | } catch (error: any) { 67 | if ( 68 | error.name !== 'HttpError' || 69 | error.message !== 'Resource not accessible by integration' 70 | ) { 71 | throw error; 72 | } 73 | 74 | core.warning( 75 | `The action requires write permission to add labels to pull requests. For more information please refer to the action documentation: https://github.com/actions/labeler#recommended-permissions`, 76 | { 77 | title: `${process.env['GITHUB_ACTION_REPOSITORY']} running under '${github.context.eventName}' is misconfigured` 78 | } 79 | ); 80 | 81 | core.setFailed(error.message); 82 | 83 | return; 84 | } 85 | 86 | core.setOutput('new-labels', newLabels.join(',')); 87 | core.setOutput('all-labels', labelsToAdd.join(',')); 88 | 89 | if (excessLabels.length) { 90 | core.warning( 91 | `Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels.join( 92 | ', ' 93 | )}`, 94 | {title: 'Label limit for a PR exceeded'} 95 | ); 96 | } 97 | } 98 | } 99 | 100 | export function checkMatchConfigs( 101 | changedFiles: string[], 102 | matchConfigs: MatchConfig[], 103 | dot: boolean 104 | ): boolean { 105 | for (const config of matchConfigs) { 106 | core.debug(` checking config ${JSON.stringify(config)}`); 107 | if (!checkMatch(changedFiles, config, dot)) { 108 | return false; 109 | } 110 | } 111 | 112 | return true; 113 | } 114 | 115 | function checkMatch( 116 | changedFiles: string[], 117 | matchConfig: MatchConfig, 118 | dot: boolean 119 | ): boolean { 120 | if (!Object.keys(matchConfig).length) { 121 | core.debug(` no "any" or "all" patterns to check`); 122 | return false; 123 | } 124 | 125 | if (matchConfig.all) { 126 | if (!checkAll(matchConfig.all, changedFiles, dot)) { 127 | return false; 128 | } 129 | } 130 | 131 | if (matchConfig.any) { 132 | if (!checkAny(matchConfig.any, changedFiles, dot)) { 133 | return false; 134 | } 135 | } 136 | 137 | return true; 138 | } 139 | 140 | // equivalent to "Array.some()" but expanded for debugging and clarity 141 | export function checkAny( 142 | matchConfigs: BaseMatchConfig[], 143 | changedFiles: string[], 144 | dot: boolean 145 | ): boolean { 146 | core.debug(` checking "any" patterns`); 147 | if ( 148 | !matchConfigs.length || 149 | !matchConfigs.some(configOption => Object.keys(configOption).length) 150 | ) { 151 | core.debug(` no "any" patterns to check`); 152 | return false; 153 | } 154 | 155 | for (const matchConfig of matchConfigs) { 156 | if (matchConfig.baseBranch) { 157 | if (checkAnyBranch(matchConfig.baseBranch, 'base')) { 158 | core.debug(` "any" patterns matched`); 159 | return true; 160 | } 161 | } 162 | 163 | if (matchConfig.changedFiles) { 164 | if (checkAnyChangedFiles(changedFiles, matchConfig.changedFiles, dot)) { 165 | core.debug(` "any" patterns matched`); 166 | return true; 167 | } 168 | } 169 | 170 | if (matchConfig.headBranch) { 171 | if (checkAnyBranch(matchConfig.headBranch, 'head')) { 172 | core.debug(` "any" patterns matched`); 173 | return true; 174 | } 175 | } 176 | } 177 | 178 | core.debug(` "any" patterns did not match any configs`); 179 | return false; 180 | } 181 | 182 | // equivalent to "Array.every()" but expanded for debugging and clarity 183 | export function checkAll( 184 | matchConfigs: BaseMatchConfig[], 185 | changedFiles: string[], 186 | dot: boolean 187 | ): boolean { 188 | core.debug(` checking "all" patterns`); 189 | if ( 190 | !matchConfigs.length || 191 | !matchConfigs.some(configOption => Object.keys(configOption).length) 192 | ) { 193 | core.debug(` no "all" patterns to check`); 194 | return false; 195 | } 196 | 197 | for (const matchConfig of matchConfigs) { 198 | if (matchConfig.baseBranch) { 199 | if (!checkAllBranch(matchConfig.baseBranch, 'base')) { 200 | core.debug(` "all" patterns did not match`); 201 | return false; 202 | } 203 | } 204 | 205 | if (matchConfig.changedFiles) { 206 | if (!changedFiles.length) { 207 | core.debug(` no files to check "changed-files" patterns against`); 208 | return false; 209 | } 210 | 211 | if (!checkAllChangedFiles(changedFiles, matchConfig.changedFiles, dot)) { 212 | core.debug(` "all" patterns did not match`); 213 | return false; 214 | } 215 | } 216 | 217 | if (matchConfig.headBranch) { 218 | if (!checkAllBranch(matchConfig.headBranch, 'head')) { 219 | core.debug(` "all" patterns did not match`); 220 | return false; 221 | } 222 | } 223 | } 224 | 225 | core.debug(` "all" patterns matched all configs`); 226 | return true; 227 | } 228 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {run} from './labeler'; 2 | 3 | run(); 4 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import {Minimatch} from 'minimatch'; 2 | 3 | export const printPattern = (matcher: Minimatch): string => { 4 | return (matcher.negate ? '!' : '') + matcher.pattern; 5 | }; 6 | 7 | export const kebabToCamel = (str: string): string => { 8 | return str.replace(/-./g, m => m.toUpperCase()[1]); 9 | }; 10 | 11 | export function isObject(obj: unknown): obj is object { 12 | return obj !== null && typeof obj === 'object' && !Array.isArray(obj); 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./lib", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | 40 | /* Module Resolution Options */ 41 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 45 | // "typeRoots": [], /* List of folders to include type definitions from. */ 46 | // "types": [], /* Type declaration files to be included in compilation. */ 47 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 48 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 50 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | }, 62 | "exclude": ["node_modules", "__tests__", "__mocks__"] 63 | } 64 | --------------------------------------------------------------------------------