Compare commits

..

2 Commits

Author SHA1 Message Date
HarithaVattikuti 38922d90b5 Update warning 2025-01-15 15:48:30 -06:00
HarithaVattikuti e7d9c02dc8 Update Section 2025-01-15 15:45:15 -06:00
43 changed files with 2106 additions and 3753 deletions
+1 -1
View File
@@ -16,4 +16,4 @@ jobs:
name: Basic validation
uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@main
with:
node-version: '24.x'
node-version: '20.x'
+1 -1
View File
@@ -16,4 +16,4 @@ jobs:
name: Check dist/
uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main
with:
node-version: '24.x'
node-version: '20.x'
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checking out
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Publish
id: publish
uses: actions/publish-immutable-action@0.0.3
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Update the ${{ env.TAG_NAME }} tag
uses: actions/publish-action@v0.4.0
uses: actions/publish-action@v0.2.2
with:
source-tag: ${{ env.TAG_NAME }}
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
+1 -4
View File
@@ -11,7 +11,4 @@ allowed:
- unlicense
reviewed:
npm:
- balanced-match
- brace-expansion
- minimatch
npm:
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/github"
version: 6.0.1
version: 6.0.0
type: npm
summary: Actions github lib
homepage: https://github.com/actions/toolkit/tree/main/packages/github
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/http-client"
version: 2.2.3
version: 2.2.0
type: npm
summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@octokit/endpoint"
version: 9.0.6
version: 9.0.1
type: npm
summary: Turns REST API endpoints into generic request options
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
-20
View File
@@ -1,20 +0,0 @@
---
name: "@octokit/openapi-types"
version: 23.0.1
type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright 2020 Gregor Martynus
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []
-20
View File
@@ -1,20 +0,0 @@
---
name: "@octokit/openapi-types"
version: 24.2.0
type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright 2020 Gregor Martynus
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []
@@ -3,7 +3,7 @@ name: "@octokit/openapi-types"
version: 19.0.1
type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@octokit/plugin-paginate-rest"
version: 9.2.2
version: 9.1.0
type: npm
summary: Octokit plugin to paginate REST API endpoint responses
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@octokit/plugin-rest-endpoint-methods"
version: 10.4.1
version: 10.1.0
type: npm
summary: Octokit plugin adding one method for all of api.github.com REST API endpoints
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@octokit/request-error"
version: 5.1.1
version: 5.0.1
type: npm
summary: Error class for Octokit request errors
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+2 -2
View File
@@ -1,10 +1,10 @@
---
name: "@octokit/request"
version: 8.4.1
version: 8.1.4
type: npm
summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers
and Node
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
-20
View File
@@ -1,20 +0,0 @@
---
name: "@octokit/types"
version: 12.6.0
type: npm
summary: Shared TypeScript definitions for Octokit projects
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License Copyright (c) 2019 Octokit contributors
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:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
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.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []
-20
View File
@@ -1,20 +0,0 @@
---
name: "@octokit/types"
version: 13.10.0
type: npm
summary: Shared TypeScript definitions for Octokit projects
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License Copyright (c) 2019 Octokit contributors
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:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
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.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []
-20
View File
@@ -1,20 +0,0 @@
---
name: "@octokit/types"
version: 13.8.0
type: npm
summary: Shared TypeScript definitions for Octokit projects
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License Copyright (c) 2019 Octokit contributors
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:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
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.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []
@@ -3,7 +3,7 @@ name: "@octokit/types"
version: 12.1.0
type: npm
summary: Shared TypeScript definitions for Octokit projects
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+26 -5
View File
@@ -1,18 +1,39 @@
---
name: balanced-match
version: 4.0.4
version: 1.0.2
type: npm
summary: Match balanced character pairs, like "{" and "}"
homepage:
license: other
homepage: https://github.com/juliangruber/balanced-match
license: mit
licenses:
- sources: LICENSE.md
text: |
(MIT)
Original code Copyright Julian Gruber <julian@juliangruber.com>
Copyright (c) 2013 Julian Gruber &lt;julian@juliangruber.com&gt;
Port to TypeScript Copyright Isaac Z. Schlueter <i@izs.me>
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
- sources: README.md
text: |-
(MIT)
Copyright (c) 2013 Julian Gruber &lt;julian@juliangruber.com&gt;
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
+27 -6
View File
@@ -1,18 +1,16 @@
---
name: brace-expansion
version: 5.0.4
version: 2.0.1
type: npm
summary: Brace expansion as known from sh/bash
homepage:
license: other
homepage: https://github.com/juliangruber/brace-expansion
license: mit
licenses:
- sources: LICENSE
text: |
MIT License
Copyright Julian Gruber <julian@juliangruber.com>
TypeScript port Copyright Isaac Z. Schlueter <i@izs.me>
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -24,6 +22,29 @@ licenses:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
- sources: README.md
text: |-
(MIT)
Copyright (c) 2013 Julian Gruber &lt;julian@juliangruber.com&gt;
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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
+40
View File
@@ -0,0 +1,40 @@
---
name: is-plain-object
version: 5.0.0
type: npm
summary: Returns true if an object was created by the `Object` constructor, or Object.create(null).
homepage: https://github.com/jonschlinkert/is-plain-object
license: mit
licenses:
- sources: LICENSE
text: |
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert.
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:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
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.
- sources: README.md
text: |-
Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 28, 2019._
notices: []
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: js-yaml
version: 4.1.1
version: 4.1.0
type: npm
summary: YAML 1.2 parser and serializer
homepage:
homepage: https://github.com/nodeca/js-yaml
license: mit
licenses:
- sources: LICENSE
+16 -56
View File
@@ -1,66 +1,26 @@
---
name: minimatch
version: 10.2.3
version: 10.0.1
type: npm
summary: a glob matcher in javascript
homepage:
license: blueoak-1.0.0
homepage:
license: isc
licenses:
- sources: LICENSE.md
- sources: LICENSE
text: |
# Blue Oak Model License
The ISC License
Version 1.0.0
Copyright (c) 2011-2022 Isaac Z. Schlueter and Contributors
## Purpose
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.
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
**_As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim._**
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.
notices: []
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: undici
version: 5.29.0
version: 5.28.4
type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org
@@ -1,20 +1,20 @@
---
name: "@octokit/openapi-types"
version: 20.0.0
name: uuid
version: 8.3.2
type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage:
summary: RFC4122 (v1, v4, and v5) UUIDs
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright 2020 Gregor Martynus
- sources: LICENSE.md
text: |
The MIT License (MIT)
Copyright (c) 2010-2020 Robert Kieffer and other contributors
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []
+16 -134
View File
@@ -4,13 +4,6 @@
Automatically label new pull requests based on the paths of files being changed or the branch name.
## Breaking changes in V6
- Upgraded action from node20 to node24.
> Make sure your runner is on version v2.327.1 or later to ensure compatibility with this release. [Release Notes](https://github.com/actions/runner/releases/tag/v2.327.1)
For more details, see the full release notes on the [release page](https://github.com/actions/labeler/releases/tag/v6.0.0)
## Breaking changes in V5
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.
@@ -37,7 +30,7 @@ The match object allows control over the matching options. You can specify the l
The base match object is defined as:
```yml
- changed-files:
- changed-files:
- any-glob-to-any-file: ['list', 'of', 'globs']
- any-glob-to-all-files: ['list', 'of', 'globs']
- all-globs-to-any-file: ['list', 'of', 'globs']
@@ -132,7 +125,7 @@ Documentation:
- changed-files:
- any-glob-to-any-file: ['docs/*', 'guides/*']
# Add 'Documentation' label to any change to .md files within the entire repository
# Add 'Documentation' label to any change to .md files within the entire repository
Documentation:
- changed-files:
- any-glob-to-any-file: '**/*.md'
@@ -153,96 +146,6 @@ release:
- base-branch: 'main'
```
#### Configuration Options
The labeler configuration file (`.github/labeler.yml`) supports the following top-level options:
| Name | Description |
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `changed-files-labels-limit` | Maximum number of new labels to apply based on changed files (must be a non-negative integer). If exceeded, no changed-files labels are applied for that run. |
| `max-files-changed` | Maximum number of total changed files (must be a non-negative integer). If exceeded, all file-based labeling is skipped. |
##### Limiting changed-files labels
When working with large PRs (e.g., tree-wide refactors) that touch many components, you may want to prevent the labeler from adding too many labels. Set `changed-files-labels-limit` in your `.github/labeler.yml` configuration file to limit the number of labels that can be applied based on changed files patterns.
**Important behaviors:**
- The limit counts only **new** labels that would be added by changed-files rules. Labels already present on the PR are not counted toward the limit.
- If the number of new changed-files labels **exceeds** the limit, **all** new changed-files labels are skipped for that run.
- If the number of new changed-files labels **equals** the limit, labels are still applied normally.
- Labels based on branch conditions (`head-branch`, `base-branch`) are **not affected** by the limit.
- **Any label definition that includes a `changed-files` rule is considered a changed-files label** and is subject to the limit, regardless of which condition caused the label to match. For example, a label with both `head-branch` and `changed-files` rules will be subject to the limit even if it matches via the branch rule.
- If both `max-files-changed` and `changed-files-labels-limit` are configured at the same time, `max-files-changed` is evaluated first, and if it triggers, `changed-files-labels-limit` is not evaluated.
##### Example
```yml
# .github/labeler.yml
# Limit changed-files based labels to 5
changed-files-labels-limit: 5
# Label definitions - these are subject to the limit
frontend:
- changed-files:
- any-glob-to-any-file: 'src/frontend/**'
backend:
- changed-files:
- any-glob-to-any-file: 'src/backend/**'
docs:
- changed-files:
- any-glob-to-any-file: 'docs/**'
# This label has both branch and changed-files rules.
# It is still subject to the limit because it includes changed-files.
mixed:
- any:
- head-branch: '^feature/'
- changed-files:
- any-glob-to-any-file: 'src/mixed/**'
# Branch-based labels are NOT affected by the limit
feature:
- head-branch: '^feature/'
```
##### Skipping labeling for large PRs
When a PR modifies a very large number of files (e.g., tree-wide refactors, automated code formatting), you may want to skip file-based labeling entirely. Set `max-files-changed` in your `.github/labeler.yml` configuration file to skip all file-based labeling when the total number of changed files exceeds the threshold.
**Important behaviors:**
- If the total number of changed files **exceeds** the limit, all file-based labeling is skipped entirely.
- If the total number of changed files **equals** the limit, labels are still applied normally.
- Labels based **only** on branch conditions (`head-branch`, `base-branch`) are **not affected** by the limit.
- **Any label definition that includes a `changed-files` rule is considered a file-based label** and will be skipped, regardless of which condition caused the label to match. For example, a label with both `head-branch` and `changed-files` rules will be skipped even if it would match via the branch rule.
- Pre-existing labels on the PR are **preserved** — changed-files configs are not evaluated at all, so `sync-labels` will not remove them.
##### Example
```yml
# .github/labeler.yml
# Skip file-based labeling if more than 100 files changed
max-files-changed: 100
# These labels will be skipped if > 100 files changed
frontend:
- changed-files:
- any-glob-to-any-file: 'src/frontend/**'
backend:
- changed-files:
- any-glob-to-any-file: 'src/backend/**'
# Branch-based labels are NOT affected
release:
- base-branch: 'main'
```
### Create Workflow
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:
@@ -259,7 +162,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v6
- uses: actions/labeler@v5
```
#### Inputs
@@ -279,10 +182,10 @@ You might want to use action called [@actions/checkout](https://github.com/actio
```yml
steps:
- uses: actions/checkout@v5 # Uploads repository content to the runner
- uses: actions/checkout@v4 # Uploads repository content to the runner
with:
repository: "owner/repositoryName" # The one of the available inputs, visit https://github.com/actions/checkout#readme to find more
- uses: actions/labeler@v6
- uses: actions/labeler@v5
with:
configuration-path: 'path/to/the/uploaded/configuration/file'
@@ -303,10 +206,10 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
# Label PRs 1, 2, and 3
- uses: actions/labeler@v6
with:
- uses: actions/labeler@v5
with:
pr-number: |
1
2
@@ -315,9 +218,9 @@ jobs:
**Note:** in normal usage the `pr-number` input is not required as the action will detect the PR number from the workflow context.
#### Outputs
#### Outputs
Labeler provides the following outputs:
Labeler provides the following outputs:
| Name | Description |
|--------------|-----------------------------------------------------------|
@@ -338,14 +241,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: label-the-PR
uses: actions/labeler@v6
uses: actions/labeler@v5
- id: run-frontend-tests
if: contains(steps.label-the-PR.outputs.all-labels, 'frontend')
run: |
echo "Running frontend tests..."
# Put your commands for running frontend tests here
- id: run-backend-tests
if: contains(steps.label-the-PR.outputs.all-labels, 'backend')
run: |
@@ -355,36 +258,15 @@ jobs:
## Recommended Permissions
To successfully add labels to pull requests using the GitHub Labeler Action, specific permissions must be granted based on your use case:
1. **Adding Existing Labels**:
- Requires: `pull-requests: write`
- Use this if all labels already exist in the repository (i.e., pre-defined in `.github/labeler.yml`).
2. **Creating New Labels**:
- Requires: `issues: write`
- This is necessary if the action needs to create labels that do not already exist in the repository.
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
[`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.
There exists a potentially dangerous misuse of the `pull_request_target` workflow trigger that may lead to malicious PR authors (i.e. attackers) being able to obtain repository write permissions or stealing repository secrets. Hence, it is advisable that `pull_request_target` should only be used in workflows that are carefully designed to avoid executing untrusted code and to also ensure that workflows using `pull_request_target` limit access to sensitive resources. 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.
### Example Workflow Permissions
To ensure the action works correctly, include the following permissions in your workflow file:
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
[`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.
```yml
permissions:
contents: read
pull-requests: write
issues: write
```
### Manual Label Creation as an Alternative to Granting issues write Permission
If you prefer not to grant the `issues: write` permission in your workflow, you can manually create all required labels in the repository before the action runs.
## Notes regarding `pull_request_target` event
Using the `pull_request_target` event trigger involves several peculiarities related to initial set up of the labeler or updating version of the labeler.
@@ -409,4 +291,4 @@ Once you confirm that the updated configuration files function as intended, you
## Contributions
Contributions are welcome! See the [Contributor's Guide](CONTRIBUTING.md).
Contributions are welcome! See the [Contributor's Guide](CONTRIBUTING.md).
-18
View File
@@ -1,18 +0,0 @@
# Limit to 0 changed-files labels (none allowed)
changed-files-labels-limit: 0
# Labels based on changed files
component-a:
- changed-files:
- any-glob-to-any-file: ['components/a/**']
component-b:
- changed-files:
- any-glob-to-any-file: ['components/b/**']
# Labels based on branch patterns only
test-branch:
- head-branch: '^test/'
feature-branch:
- head-branch: '/feature/'
-26
View File
@@ -1,26 +0,0 @@
# Limit to 1 changed-files label
changed-files-labels-limit: 1
# Labels based on changed files
component-a:
- changed-files:
- any-glob-to-any-file: ['components/a/**']
component-b:
- changed-files:
- any-glob-to-any-file: ['components/b/**']
component-c:
- changed-files:
- any-glob-to-any-file: ['components/c/**']
component-d:
- changed-files:
- any-glob-to-any-file: ['components/d/**']
# Labels based on branch patterns only
test-branch:
- head-branch: '^test/'
feature-branch:
- head-branch: '/feature/'
-26
View File
@@ -1,26 +0,0 @@
# Limit to 2 changed-files labels
changed-files-labels-limit: 2
# Labels based on changed files
component-a:
- changed-files:
- any-glob-to-any-file: ['components/a/**']
component-b:
- changed-files:
- any-glob-to-any-file: ['components/b/**']
component-c:
- changed-files:
- any-glob-to-any-file: ['components/c/**']
component-d:
- changed-files:
- any-glob-to-any-file: ['components/d/**']
# Labels based on branch patterns only
test-branch:
- head-branch: '^test/'
feature-branch:
- head-branch: '/feature/'
-26
View File
@@ -1,26 +0,0 @@
# Limit to 3 changed-files labels
changed-files-labels-limit: 3
# Labels based on changed files
component-a:
- changed-files:
- any-glob-to-any-file: ['components/a/**']
component-b:
- changed-files:
- any-glob-to-any-file: ['components/b/**']
component-c:
- changed-files:
- any-glob-to-any-file: ['components/c/**']
component-d:
- changed-files:
- any-glob-to-any-file: ['components/d/**']
# Labels based on branch patterns only
test-branch:
- head-branch: '^test/'
feature-branch:
- head-branch: '/feature/'
-15
View File
@@ -1,15 +0,0 @@
# Skip file-based labeling if more than 5 files changed
max-files-changed: 5
# Labels based on changed files
component-a:
- changed-files:
- any-glob-to-any-file: ['components/a/**']
component-b:
- changed-files:
- any-glob-to-any-file: ['components/b/**']
component-c:
- changed-files:
- any-glob-to-any-file: ['components/c/**']
@@ -1,15 +0,0 @@
# Skip file-based labeling if more than 3 files changed
max-files-changed: 3
# Labels based on changed files
component-a:
- changed-files:
- any-glob-to-any-file: ['components/a/**']
component-b:
- changed-files:
- any-glob-to-any-file: ['components/b/**']
# Branch-based label (should not be affected)
test-branch:
- head-branch: ['^test/']
-23
View File
@@ -1,23 +0,0 @@
# Labels based on changed files
component-a:
- changed-files:
- any-glob-to-any-file: ['components/a/**']
component-b:
- changed-files:
- any-glob-to-any-file: ['components/b/**']
component-c:
- changed-files:
- any-glob-to-any-file: ['components/c/**']
component-d:
- changed-files:
- any-glob-to-any-file: ['components/d/**']
# Labels based on branch patterns only
test-branch:
- head-branch: '^test/'
feature-branch:
- head-branch: '/feature/'
-16
View File
@@ -1,16 +0,0 @@
# Test fixture for mixed rules behavior
# A label with both branch and changed-files rules is considered a "changed-files label"
# and is subject to the limit, even if it matches via the branch rule
changed-files-labels-limit: 0
# This label has both branch and changed-files rules
# It should be subject to the limit even if matched via branch
mixed-label:
- any:
- head-branch: '^test/'
- changed-files:
- any-glob-to-any-file: ['components/a/**']
# Pure branch-based label - not subject to limit
pure-branch-label:
- head-branch: '^test/'
+1 -322
View File
@@ -1,21 +1,15 @@
import * as yaml from 'js-yaml';
import * as core from '@actions/core';
import * as api from '../src/api';
import {labeler} from '../src/labeler';
import * as github from '@actions/github';
import * as fs from 'fs';
import {checkMatchConfigs} from '../src/labeler';
import {
MatchConfig,
toMatchConfig,
getLabelConfigMapFromObject,
getLabelConfigResultFromObject,
BaseMatchConfig,
configUsesChangedFiles
BaseMatchConfig
} from '../src/api/get-label-configs';
jest.mock('@actions/core');
jest.mock('../src/api');
beforeAll(() => {
jest.spyOn(core, 'getInput').mockImplementation((name, options) => {
@@ -62,204 +56,6 @@ describe('getLabelConfigMapFromObject', () => {
const result = getLabelConfigMapFromObject(yamlObject);
expect(result).toEqual(expected);
});
it('ignores top-level options like changed-files-labels-limit and max-files-changed', () => {
const configWithLimit = {
'changed-files-labels-limit': 5,
'max-files-changed': 100,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigMapFromObject(configWithLimit);
expect(result.has('changed-files-labels-limit')).toBe(false);
expect(result.has('max-files-changed')).toBe(false);
expect(result.has('label1')).toBe(true);
});
});
describe('getLabelConfigResultFromObject', () => {
it('extracts changed-files-labels-limit as a number', () => {
const config = {
'changed-files-labels-limit': 5,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.changedFilesLimit).toBe(5);
expect(result.labelConfigs.has('label1')).toBe(true);
});
it('parses changed-files-labels-limit from string', () => {
const config = {
'changed-files-labels-limit': '10',
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.changedFilesLimit).toBe(10);
});
it('trims whitespace when parsing string values', () => {
const config = {
'changed-files-labels-limit': ' 5 ',
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.changedFilesLimit).toBe(5);
});
it('returns undefined changedFilesLimit when not set', () => {
const config = {
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.changedFilesLimit).toBeUndefined();
});
it('throws error for invalid changed-files-labels-limit value', () => {
const config = {
'changed-files-labels-limit': 'invalid',
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/Invalid value for 'changed-files-labels-limit'/
);
});
it('throws error for negative changed-files-labels-limit value', () => {
const config = {
'changed-files-labels-limit': -1,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/must be a non-negative integer/
);
});
it('throws error for string with trailing characters', () => {
const config = {
'changed-files-labels-limit': '10abc',
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/must be a non-negative integer/
);
});
it('throws error for decimal string', () => {
const config = {
'changed-files-labels-limit': '3.2',
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/must be a non-negative integer/
);
});
it('throws error for float number', () => {
const config = {
'changed-files-labels-limit': 3.2,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/must be a non-negative integer/
);
});
it('accepts zero as a valid changed-files-labels-limit', () => {
const config = {
'changed-files-labels-limit': 0,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.changedFilesLimit).toBe(0);
});
it('extracts max-files-changed as a number', () => {
const config = {
'max-files-changed': 100,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.maxFilesChanged).toBe(100);
expect(result.labelConfigs.has('label1')).toBe(true);
});
it('parses max-files-changed from string', () => {
const config = {
'max-files-changed': '50',
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.maxFilesChanged).toBe(50);
});
it('returns undefined maxFilesChanged when not set', () => {
const config = {
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.maxFilesChanged).toBeUndefined();
});
it('throws error for invalid max-files-changed value', () => {
const config = {
'max-files-changed': 'invalid',
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/Invalid value for 'max-files-changed'/
);
});
it('throws error for negative max-files-changed value', () => {
const config = {
'max-files-changed': -1,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/must be a non-negative integer/
);
});
it('accepts zero as a valid max-files-changed', () => {
const config = {
'max-files-changed': 0,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.maxFilesChanged).toBe(0);
});
it('supports both options together', () => {
const config = {
'changed-files-labels-limit': 5,
'max-files-changed': 100,
label1: [{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}]
};
const result = getLabelConfigResultFromObject(config);
expect(result.changedFilesLimit).toBe(5);
expect(result.maxFilesChanged).toBe(100);
});
it('throws a clear error when max-files-changed is used as a label', () => {
const config = {
'max-files-changed': [
{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}
]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/reserved top-level option and cannot be used as a label name/
);
});
it('throws a clear error when changed-files-labels-limit is used as a label', () => {
const config = {
'changed-files-labels-limit': [
{'changed-files': [{'any-glob-to-any-file': ['*.txt']}]}
]
};
expect(() => getLabelConfigResultFromObject(config)).toThrow(
/reserved top-level option and cannot be used as a label name/
);
});
});
describe('toMatchConfig', () => {
@@ -363,120 +159,3 @@ describe('checkMatchConfigs', () => {
});
});
});
describe('configUsesChangedFiles', () => {
it('returns true when config has changed-files in any block', () => {
const matchConfig: MatchConfig[] = [
{any: [{changedFiles: [{anyGlobToAnyFile: ['*.txt']}]}]}
];
expect(configUsesChangedFiles(matchConfig)).toBe(true);
});
it('returns true when config has changed-files in all block', () => {
const matchConfig: MatchConfig[] = [
{all: [{changedFiles: [{allGlobsToAllFiles: ['*.txt']}]}]}
];
expect(configUsesChangedFiles(matchConfig)).toBe(true);
});
it('returns false when config only has branch patterns', () => {
const matchConfig: MatchConfig[] = [
{any: [{headBranch: ['^test/']}]},
{any: [{baseBranch: ['main']}]}
];
expect(configUsesChangedFiles(matchConfig)).toBe(false);
});
it('returns false when config has empty changed-files array', () => {
const matchConfig: MatchConfig[] = [{any: [{changedFiles: []}]}];
expect(configUsesChangedFiles(matchConfig)).toBe(false);
});
it('returns false when config has changed-files with empty objects', () => {
const matchConfig: MatchConfig[] = [{any: [{changedFiles: [{}]}]}];
expect(configUsesChangedFiles(matchConfig)).toBe(false);
});
it('returns true when config has mixed branch and changed-files patterns', () => {
const matchConfig: MatchConfig[] = [
{
any: [
{changedFiles: [{anyGlobToAnyFile: ['*.txt']}]},
{headBranch: ['^feature/']}
]
}
];
expect(configUsesChangedFiles(matchConfig)).toBe(true);
});
});
describe('labeler error handling', () => {
const mockClient = {} as any;
const mockPullRequest = {
number: 123,
data: {labels: []},
changedFiles: []
};
beforeEach(() => {
jest.resetAllMocks();
(github.getOctokit as jest.Mock).mockReturnValue(mockClient);
(api.getPullRequests as jest.Mock).mockReturnValue([
{
...mockPullRequest,
data: {labels: [{name: 'old-label'}]}
}
]);
(api.getLabelConfigs as jest.Mock).mockResolvedValue({
labelConfigs: new Map([['new-label', ['dummy-config']]]),
changedFilesLimit: undefined
});
// Force match so "new-label" is always added
jest.spyOn({checkMatchConfigs}, 'checkMatchConfigs').mockReturnValue(true);
});
it('throws a custom error for HttpError 403 with "unauthorized" message', async () => {
(api.setLabels as jest.Mock).mockRejectedValue({
name: 'HttpError',
status: 403,
message: 'Request failed with status code 403: Unauthorized'
});
await expect(labeler()).rejects.toThrow(
/does not have permission to create labels/
);
});
it('rethrows unexpected HttpError', async () => {
const unexpectedError = {
name: 'HttpError',
status: 404,
message: 'Not Found'
};
(api.setLabels as jest.Mock).mockRejectedValue(unexpectedError);
// NOTE: In the current implementation, labeler rethrows the raw error object (not an Error instance).
// `rejects.toThrow` only works with real Error objects, so here we must use `rejects.toEqual`.
// If labeler is updated to always wrap errors in `Error`, this test can be changed to use `rejects.toThrow`.
await expect(labeler()).rejects.toEqual(unexpectedError);
});
it('handles "Resource not accessible by integration" gracefully', async () => {
const error = {
name: 'HttpError',
message: 'Resource not accessible by integration'
};
(api.setLabels as jest.Mock).mockRejectedValue(error);
await labeler();
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining("requires 'issues: write'"),
expect.any(Object)
);
expect(core.setFailed).toHaveBeenCalledWith(error.message);
});
});
+1 -346
View File
@@ -37,17 +37,7 @@ const yamlFixtures = {
'branches.yml': fs.readFileSync('__tests__/fixtures/branches.yml'),
'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml'),
'not_supported.yml': fs.readFileSync('__tests__/fixtures/not_supported.yml'),
'any_and_all.yml': fs.readFileSync('__tests__/fixtures/any_and_all.yml'),
'mixed_labels.yml': fs.readFileSync('__tests__/fixtures/mixed_labels.yml'),
'limit_0.yml': fs.readFileSync('__tests__/fixtures/limit_0.yml'),
'limit_1.yml': fs.readFileSync('__tests__/fixtures/limit_1.yml'),
'limit_2.yml': fs.readFileSync('__tests__/fixtures/limit_2.yml'),
'limit_3.yml': fs.readFileSync('__tests__/fixtures/limit_3.yml'),
'mixed_rules.yml': fs.readFileSync('__tests__/fixtures/mixed_rules.yml'),
'max_files_5.yml': fs.readFileSync('__tests__/fixtures/max_files_5.yml'),
'max_files_with_branch.yml': fs.readFileSync(
'__tests__/fixtures/max_files_with_branch.yml'
)
'any_and_all.yml': fs.readFileSync('__tests__/fixtures/any_and_all.yml')
};
const configureInput = (
@@ -450,341 +440,6 @@ describe('run', () => {
expect(setLabelsMock).toHaveBeenCalledTimes(0);
});
describe('changed-files-labels-limit', () => {
it('applies all labels when count is within limit', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('limit_3.yml');
mockGitHubResponseChangedFiles(
'components/a/file.ts',
'components/b/file.ts'
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['component-a', 'component-b']
});
});
it('skips changed-files labels when count exceeds limit', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('limit_2.yml');
mockGitHubResponseChangedFiles(
'components/a/file.ts',
'components/b/file.ts',
'components/c/file.ts'
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
// No labels should be applied since changed-files labels exceed limit
expect(setLabelsMock).toHaveBeenCalledTimes(0);
});
it('still applies branch-based labels when changed-files limit is exceeded', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'test/some-feature'};
usingLabelerConfigYaml('limit_1.yml');
mockGitHubResponseChangedFiles(
'components/a/file.ts',
'components/b/file.ts',
'components/c/file.ts'
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
// Only the branch-based label should be applied
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['test-branch']
});
});
it('applies all labels when no limit is set', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('mixed_labels.yml');
mockGitHubResponseChangedFiles(
'components/a/file.ts',
'components/b/file.ts',
'components/c/file.ts',
'components/d/file.ts'
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['component-a', 'component-b', 'component-c', 'component-d']
});
});
it('does not count preexisting labels toward the limit', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('limit_2.yml');
mockGitHubResponseChangedFiles(
'components/a/file.ts',
'components/b/file.ts',
'components/c/file.ts',
'components/d/file.ts'
);
getPullMock.mockResolvedValue(<any>{
data: {labels: [{name: 'component-a'}, {name: 'component-b'}]}
});
await run();
// component-a and component-b are preexisting, so only 2 new labels (c, d) would be added
// which equals the limit of 2, so labels should be applied
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['component-a', 'component-b', 'component-c', 'component-d']
});
});
it('skips new labels when new count exceeds limit even with preexisting', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('limit_2.yml');
mockGitHubResponseChangedFiles(
'components/a/file.ts',
'components/b/file.ts',
'components/c/file.ts',
'components/d/file.ts'
);
getPullMock.mockResolvedValue(<any>{
data: {labels: [{name: 'component-a'}]}
});
await run();
// component-a is preexisting, so 3 new labels (b, c, d) would be added
// which exceeds the limit of 2, so no new changed-files labels are applied
expect(setLabelsMock).toHaveBeenCalledTimes(0);
});
it('applies labels when new count equals the limit', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('limit_2.yml');
mockGitHubResponseChangedFiles(
'components/a/file.ts',
'components/b/file.ts'
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['component-a', 'component-b']
});
});
it('skips all changed-files labels when limit is 0', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'test/some-feature'};
usingLabelerConfigYaml('limit_0.yml');
mockGitHubResponseChangedFiles('components/a/file.ts');
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
// With limit 0, only branch-based labels should be applied
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['test-branch']
});
});
it('treats labels with mixed rules as changed-files labels', async () => {
// A label that has both branch and changed-files rules is considered
// a "changed-files label" and subject to the limit, even if it matches
// via the branch rule
configureInput({});
github.context.payload.pull_request!.head = {ref: 'test/some-feature'};
usingLabelerConfigYaml('mixed_rules.yml');
mockGitHubResponseChangedFiles('unrelated/file.ts');
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
// The mixed-label matches via branch rule but is still subject to limit
// because it contains a changed-files rule in its definition.
// Only pure-branch-label should be applied.
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['pure-branch-label']
});
});
});
describe('max-files-changed', () => {
it('applies labels when changed files count is within limit', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('max_files_5.yml');
mockGitHubResponseChangedFiles(
'components/a/file.ts',
'components/b/file.ts',
'components/c/file.ts'
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['component-a', 'component-b', 'component-c']
});
});
it('skips file-based labels when changed files exceed limit', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('max_files_5.yml');
mockGitHubResponseChangedFiles(
'components/a/file1.ts',
'components/a/file2.ts',
'components/b/file1.ts',
'components/b/file2.ts',
'components/c/file1.ts',
'components/c/file2.ts' // 6 files > limit of 5
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
// No labels should be applied since changed files exceed limit
expect(setLabelsMock).toHaveBeenCalledTimes(0);
});
it('applies labels when changed files count equals limit', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('max_files_5.yml');
mockGitHubResponseChangedFiles(
'components/a/file1.ts',
'components/a/file2.ts',
'components/b/file1.ts',
'components/b/file2.ts',
'components/c/file.ts' // exactly 5 files = limit
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['component-a', 'component-b', 'component-c']
});
});
it('still applies branch-based labels when max-files-changed is exceeded', async () => {
configureInput({});
github.context.payload.pull_request!.head = {ref: 'test/some-feature'};
usingLabelerConfigYaml('max_files_with_branch.yml');
mockGitHubResponseChangedFiles(
'components/a/file1.ts',
'components/a/file2.ts',
'components/b/file1.ts',
'components/b/file2.ts' // 4 files > limit of 3
);
getPullMock.mockResolvedValue(<any>{
data: {labels: []}
});
await run();
// Only the branch-based label should be applied
expect(setLabelsMock).toHaveBeenCalledTimes(1);
expect(setLabelsMock).toHaveBeenCalledWith({
owner: 'monalisa',
repo: 'helloworld',
issue_number: 123,
labels: ['test-branch']
});
});
it('preserves preexisting changed-files labels with sync-labels when max-files-changed is exceeded', async () => {
configureInput({'sync-labels': true});
github.context.payload.pull_request!.head = {ref: 'main'};
usingLabelerConfigYaml('max_files_5.yml');
mockGitHubResponseChangedFiles(
'unrelated/file1.ts',
'unrelated/file2.ts',
'unrelated/file3.ts',
'unrelated/file4.ts',
'unrelated/file5.ts',
'unrelated/file6.ts' // 6 files > limit of 5
);
getPullMock.mockResolvedValue(<any>{
data: {labels: [{name: 'component-a'}]} // preexisting label
});
await run();
// No setLabels call because labels should remain unchanged
// (component-a is preserved, not removed by sync-labels)
expect(setLabelsMock).toHaveBeenCalledTimes(0);
});
});
it('should use local configuration file if it exists', async () => {
const configFile = 'only_pdfs.yml';
const configFilePath = path.join(__dirname, 'fixtures', configFile);
+1 -1
View File
@@ -28,5 +28,5 @@ outputs:
all-labels:
description: 'A comma-separated list of all labels that the PR contains'
runs:
using: 'node24'
using: 'node20'
main: 'dist/index.js'
+1423 -1998
View File
File diff suppressed because one or more lines are too long
+502 -375
View File
File diff suppressed because it is too large Load Diff
+6 -9
View File
@@ -1,11 +1,8 @@
{
"name": "labeler",
"version": "6.0.0",
"version": "5.0.0",
"description": "Labels pull requests by files altered",
"main": "lib/main.js",
"engines": {
"node": ">=24"
},
"scripts": {
"build": "tsc && ncc build lib/main.js",
"format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"",
@@ -32,25 +29,25 @@
"@octokit/plugin-retry": "^6.0.0",
"js-yaml": "^4.1.0",
"lodash.isequal": "^4.5.0",
"minimatch": "^10.2.3"
"minimatch": "^10.0.1"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/js-yaml": "^4.0.9",
"@types/lodash.isequal": "^4.5.8",
"@types/minimatch": "^5.1.2",
"@types/node": "^24.1.0",
"@types/node": "^20.11.30",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.18.0",
"@vercel/ncc": "^0.38.3",
"@vercel/ncc": "^0.38.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-jest": "^28.9.0",
"eslint-plugin-node": "^11.1.0",
"jest": "^29.7.0",
"prettier": "^3.4.2",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3"
"typescript": "^5.7.2"
}
}
+3 -108
View File
@@ -18,53 +18,12 @@ export interface MatchConfig {
export type BaseMatchConfig = BranchMatchConfig & ChangedFilesMatchConfig;
export interface LabelConfigResult {
labelConfigs: Map<string, MatchConfig[]>;
changedFilesLimit?: number;
maxFilesChanged?: number;
}
const ALLOWED_CONFIG_KEYS = ['changed-files', 'head-branch', 'base-branch'];
const TOP_LEVEL_OPTIONS = ['changed-files-labels-limit', 'max-files-changed'];
/**
* Parses and validates a non-negative integer value from the configuration.
*/
function parseNonNegativeInteger(value: unknown, optionName: string): number {
if (typeof value === 'number') {
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
throw new Error(
`Invalid value for '${optionName}': must be a non-negative integer (got ${value})`
);
}
return value;
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (!/^\d+$/.test(trimmed)) {
throw new Error(
`Invalid value for '${optionName}': must be a non-negative integer (got '${value}')`
);
}
return Number(trimmed);
}
if (Array.isArray(value)) {
throw new Error(
`'${optionName}' is a reserved top-level option and cannot be used as a label name. Please rename it.`
);
}
throw new Error(
`Invalid value for '${optionName}': expected a non-negative integer`
);
}
export const getLabelConfigs = (
client: ClientType,
configurationPath: string
): Promise<LabelConfigResult> =>
): Promise<Map<string, MatchConfig[]>> =>
Promise.resolve()
.then(() => {
if (!fs.existsSync(configurationPath)) {
@@ -95,49 +54,15 @@ export const getLabelConfigs = (
// loads (hopefully) a `{[label:string]: MatchConfig[]}`, but is `any`:
const configObject: any = yaml.load(configuration);
// transform `any` => `LabelConfigResult` or throw if yaml is malformed:
return getLabelConfigResultFromObject(configObject);
// transform `any` => `Map<string,MatchConfig[]>` or throw if yaml is malformed:
return getLabelConfigMapFromObject(configObject);
});
export function getLabelConfigResultFromObject(
configObject: any
): LabelConfigResult {
// Extract top-level options
let changedFilesLimit: number | undefined;
let maxFilesChanged: number | undefined;
const limitValue = configObject?.['changed-files-labels-limit'];
if (limitValue !== undefined) {
changedFilesLimit = parseNonNegativeInteger(
limitValue,
'changed-files-labels-limit'
);
}
const maxFilesValue = configObject?.['max-files-changed'];
if (maxFilesValue !== undefined) {
maxFilesChanged = parseNonNegativeInteger(
maxFilesValue,
'max-files-changed'
);
}
return {
labelConfigs: getLabelConfigMapFromObject(configObject),
changedFilesLimit,
maxFilesChanged
};
}
export function getLabelConfigMapFromObject(
configObject: any
): Map<string, MatchConfig[]> {
const labelMap: Map<string, MatchConfig[]> = new Map();
for (const label in configObject) {
// Skip top-level options
if (TOP_LEVEL_OPTIONS.includes(label)) {
continue;
}
const configOptions = configObject[label];
if (
!Array.isArray(configOptions) ||
@@ -199,33 +124,3 @@ export function toMatchConfig(config: any): BaseMatchConfig {
...branchConfig
};
}
/**
* Checks if any of the match configs for a label use changed-files patterns.
* This is used to determine if a label should be counted toward the changed-files limit.
*/
export function configUsesChangedFiles(matchConfigs: MatchConfig[]): boolean {
for (const config of matchConfigs) {
if (config.all) {
for (const baseConfig of config.all) {
if (
baseConfig.changedFiles &&
baseConfig.changedFiles.some(cf => Object.keys(cf).length > 0)
) {
return true;
}
}
}
if (config.any) {
for (const baseConfig of config.any) {
if (
baseConfig.changedFiles &&
baseConfig.changedFiles.some(cf => Object.keys(cf).length > 0)
) {
return true;
}
}
}
}
return false;
}
+13 -94
View File
@@ -5,11 +5,7 @@ import * as api from './api';
import isEqual from 'lodash.isequal';
import {getInputs} from './get-inputs';
import {
BaseMatchConfig,
MatchConfig,
configUsesChangedFiles
} from './api/get-label-configs';
import {BaseMatchConfig, MatchConfig} from './api/get-label-configs';
import {checkAllChangedFiles, checkAnyChangedFiles} from './changedFiles';
@@ -26,7 +22,7 @@ export const run = () =>
core.setFailed(error.message);
});
export async function labeler() {
async function labeler() {
const {token, configPath, syncLabels, dot, prNumbers} = getInputs();
if (!prNumbers.length) {
@@ -39,112 +35,36 @@ export async function labeler() {
const pullRequests = api.getPullRequests(client, prNumbers);
for await (const pullRequest of pullRequests) {
const {labelConfigs, changedFilesLimit, maxFilesChanged} =
await api.getLabelConfigs(client, configPath);
// Check if total changed files exceeds the max-files-changed threshold
const skipChangedFilesLabeling =
maxFilesChanged !== undefined &&
pullRequest.changedFiles.length > maxFilesChanged;
if (skipChangedFilesLabeling) {
core.info(
`Total changed files (${pullRequest.changedFiles.length}) exceeds max-files-changed (${maxFilesChanged}), skipping file-based labeling`
);
}
const labelConfigs: Map<string, MatchConfig[]> = await api.getLabelConfigs(
client,
configPath
);
const preexistingLabels = pullRequest.data.labels.map(l => l.name);
const allLabels: Set<string> = new Set<string>(preexistingLabels);
// Track labels that would be added based on changed-files patterns
const changedFilesLabels: Set<string> = new Set<string>();
for (const [label, configs] of labelConfigs.entries()) {
core.debug(`processing ${label}`);
// If this config uses changed-files and we're skipping file-based labeling,
// don't evaluate it at all (skip add/remove) to preserve preexisting labels
const usesChangedFiles = configUsesChangedFiles(configs);
if (skipChangedFilesLabeling && usesChangedFiles) {
core.debug(
`skipping ${label} (uses changed-files and max-files-changed exceeded)`
);
continue;
}
if (checkMatchConfigs(pullRequest.changedFiles, configs, dot)) {
allLabels.add(label);
// Track if this label uses changed-files patterns
if (usesChangedFiles) {
changedFilesLabels.add(label);
}
} else if (syncLabels) {
allLabels.delete(label);
}
}
// Check if changed-files labels should be skipped due to labels limit
const newChangedFilesLabels = [...changedFilesLabels].filter(
l => !preexistingLabels.includes(l)
);
if (
changedFilesLimit !== undefined &&
newChangedFilesLabels.length > changedFilesLimit
) {
core.info(
`Changed-files labels (${newChangedFilesLabels.length}) exceed limit (${changedFilesLimit}), skipping: ${newChangedFilesLabels.join(', ')}`
);
// Remove all new changed-files labels
for (const label of newChangedFilesLabels) {
allLabels.delete(label);
}
}
const labelsToApply = [...allLabels].slice(0, GITHUB_MAX_LABELS);
const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS);
const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS);
let finalLabels = labelsToApply;
let newLabels: string[] = [];
try {
if (!isEqual(labelsToApply, preexistingLabels)) {
// Fetch the latest labels for the PR
const latestLabels: string[] = [];
// Skip fetching real labels when running tests (uses mock data instead)
if (process.env.NODE_ENV !== 'test') {
const pr = await client.rest.pulls.get({
...github.context.repo,
pull_number: pullRequest.number
});
latestLabels.push(...pr.data.labels.map(l => l.name).filter(Boolean));
}
// Labels added manually during the run (not in first snapshot)
const manualAddedDuringRun = latestLabels.filter(
l => !preexistingLabels.includes(l)
if (!isEqual(labelsToAdd, preexistingLabels)) {
await api.setLabels(client, pullRequest.number, labelsToAdd);
newLabels = labelsToAdd.filter(
label => !preexistingLabels.includes(label)
);
// Preserve manual labels first, then apply config-based labels, respecting GitHub's 100-label limit
finalLabels = [
...new Set([...manualAddedDuringRun, ...labelsToApply])
].slice(0, GITHUB_MAX_LABELS);
await api.setLabels(client, pullRequest.number, finalLabels);
newLabels = finalLabels.filter(l => !preexistingLabels.includes(l));
}
} catch (error: any) {
if (
error.name === 'HttpError' &&
error.status === 403 &&
error.message.toLowerCase().includes('unauthorized')
) {
throw new Error(
`Failed to set labels for PR #${pullRequest.number}. The workflow does not have permission to create labels. ` +
`Ensure the 'issues: write' permission is granted in the workflow file or manually create the missing labels in the repository before running the action.`
);
} else if (
error.name !== 'HttpError' ||
error.message !== 'Resource not accessible by integration'
) {
@@ -152,8 +72,7 @@ export async function labeler() {
}
core.warning(
`The action requires 'issues: write' permission to create new labels or 'pull-requests: write' permission to add existing labels to pull requests. ` +
`For more information, refer to the action documentation: https://github.com/actions/labeler#recommended-permissions`,
`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`,
{
title: `${process.env['GITHUB_ACTION_REPOSITORY']} running under '${github.context.eventName}' is misconfigured`
}
@@ -165,7 +84,7 @@ export async function labeler() {
}
core.setOutput('new-labels', newLabels.join(','));
core.setOutput('all-labels', finalLabels.join(','));
core.setOutput('all-labels', labelsToAdd.join(','));
if (excessLabels.length) {
core.warning(