mirror of
https://gitea.com/actions/dorny-paths-filter.git
synced 2026-08-21 16:13:13 +01:00
Compare commits
1 Commits
releases/v
...
65a156e663
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65a156e663 |
49
.github/workflows/pull-request-verification.yml
vendored
49
.github/workflows/pull-request-verification.yml
vendored
@@ -167,43 +167,6 @@ jobs:
|
||||
if: steps.filter.outputs.local_count != 1
|
||||
run: exit 1
|
||||
|
||||
test-predicate-quantifier-some-with-excludes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: modify working tree
|
||||
run: |
|
||||
mkdir -p mobile/.config
|
||||
echo "TEST" > mobile/main.kt
|
||||
echo "TEST" > mobile/README.md
|
||||
echo "TEST" > mobile/.config/lint.json
|
||||
echo "TEST" > backend.go
|
||||
git add -A
|
||||
- uses: ./
|
||||
id: filter
|
||||
with:
|
||||
base: HEAD
|
||||
list-files: shell
|
||||
predicate-quantifier: 'some-with-excludes'
|
||||
filters: |
|
||||
mobile:
|
||||
- 'mobile/**'
|
||||
- '!mobile/**/*.md'
|
||||
- '!mobile/.config/**'
|
||||
excludesOnly:
|
||||
- '!**/*.md'
|
||||
- name: Print 'mobile_files'
|
||||
env:
|
||||
MOBILE_FILES: ${{ steps.filter.outputs.mobile_files }}
|
||||
run: echo "$MOBILE_FILES"
|
||||
- name: filter-test
|
||||
if: |
|
||||
steps.filter.outputs.mobile != 'true'
|
||||
|| steps.filter.outputs.mobile_count != 1
|
||||
|| steps.filter.outputs.mobile_files != 'mobile/main.kt'
|
||||
|| steps.filter.outputs.excludesOnly != 'false'
|
||||
run: exit 1
|
||||
|
||||
test-change-type:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -229,17 +192,11 @@ jobs:
|
||||
any:
|
||||
- added|deleted|modified: "*"
|
||||
- name: Print 'added_files'
|
||||
env:
|
||||
ADDED_FILES: ${{ steps.filter.outputs.added_files }}
|
||||
run: echo "$ADDED_FILES"
|
||||
run: echo ${{steps.filter.outputs.added_files}}
|
||||
- name: Print 'modified_files'
|
||||
env:
|
||||
MODIFIED_FILES: ${{ steps.filter.outputs.modified_files }}
|
||||
run: echo "$MODIFIED_FILES"
|
||||
run: echo ${{steps.filter.outputs.modified_files}}
|
||||
- name: Print 'deleted_files'
|
||||
env:
|
||||
DELETED_FILES: ${{ steps.filter.outputs.deleted_files }}
|
||||
run: echo "$DELETED_FILES"
|
||||
run: echo ${{steps.filter.outputs.deleted_files}}
|
||||
- name: filter-test
|
||||
if: |
|
||||
steps.filter.outputs.added != 'true'
|
||||
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,13 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## v4.0.3
|
||||
- [Document safe handling of file list outputs in workflows](https://github.com/dorny/paths-filter/pull/326)
|
||||
- [Escape multi-line filenames in list-files shell and csv output](https://github.com/advisories/GHSA-7hc6-8hq5-9q2m)
|
||||
- [Add 'some-with-excludes' predicate quantifier](https://github.com/dorny/paths-filter/pull/322)
|
||||
- [Add contents permission to PR example](https://github.com/dorny/paths-filter/pull/248)
|
||||
- [Scope base-ignored warning to API path](https://github.com/dorny/paths-filter/pull/319)
|
||||
- [Update outputs in readme to account for the 'every' predicate-quantifier](https://github.com/dorny/paths-filter/pull/247)
|
||||
|
||||
## v4.0.2
|
||||
- [Work around git dubious ownership errors in container jobs](https://github.com/dorny/paths-filter/pull/317)
|
||||
- [Use rev-parse instead of branch --show-current for older git compat](https://github.com/dorny/paths-filter/pull/303)
|
||||
@@ -19,9 +11,6 @@
|
||||
## v4.0.0
|
||||
- [Update action runtime to node24](https://github.com/dorny/paths-filter/pull/294)
|
||||
|
||||
## v3.0.4
|
||||
- [Escape multi-line filenames in list-files shell and csv output](https://github.com/advisories/GHSA-7hc6-8hq5-9q2m)
|
||||
|
||||
## v3.0.3
|
||||
- [Add missing predicate-quantifier](https://github.com/dorny/paths-filter/pull/279)
|
||||
|
||||
|
||||
57
README.md
57
README.md
@@ -67,10 +67,6 @@ For more scenarios see [examples](#examples) section.
|
||||
|
||||
## Notes
|
||||
|
||||
- **Security:** `${FILTER_NAME}_files` outputs contain filenames that may be attacker-influenced on pull requests.
|
||||
Do not interpolate them directly into a `run:` script with `${{ ... }}`.
|
||||
Pass the value through `env:` and reference the variable from the shell instead.
|
||||
See [Custom processing of changed files](#custom-processing-of-changed-files).
|
||||
- Paths expressions are evaluated using [picomatch](https://github.com/micromatch/picomatch) library.
|
||||
Documentation for path expression format can be found on the project GitHub page.
|
||||
- Picomatch [dot](https://github.com/micromatch/picomatch#options) option is set to true.
|
||||
@@ -85,7 +81,6 @@ For more scenarios see [examples](#examples) section.
|
||||
|
||||
## What's New
|
||||
|
||||
- Add `some-with-excludes` value of the `predicate-quantifier` input parameter
|
||||
- Automatic workaround for git `dubious ownership` errors in container jobs
|
||||
- New major release `v4` after update to Node 24 [Breaking change]
|
||||
- Add `ref` input parameter
|
||||
@@ -177,17 +172,14 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob
|
||||
token: ''
|
||||
|
||||
# Optional parameter to override the default behavior of file matching algorithm.
|
||||
# Supported values:
|
||||
# 'some' - File is included if it matches at least one pattern (default).
|
||||
# 'every' - File is included only if it matches all of the patterns.
|
||||
# 'some-with-excludes' - File is included if it matches at least one pattern
|
||||
# and no negated pattern (the ones prefixed with '!').
|
||||
#
|
||||
# By default files that match at least one pattern defined by the filters will be included.
|
||||
# This parameter allows to override the "at least one pattern" behavior to make it so that
|
||||
# all of the patterns have to match or otherwise the file is excluded.
|
||||
# An example scenario where this is useful if you would like to match all
|
||||
# .ts files in a sub-directory but not .md files.
|
||||
# The filters below will match markdown files despite the exclusion syntax UNLESS
|
||||
# you specify 'every' or 'some-with-excludes' as the predicate-quantifier parameter.
|
||||
# When you do that, it will only match the .ts files in the subdirectory as expected.
|
||||
# you specify 'every' as the predicate-quantifier parameter. When you do that,
|
||||
# it will only match the .ts files in the subdirectory as expected.
|
||||
#
|
||||
# backend:
|
||||
# - 'pkg/a/b/c/**'
|
||||
@@ -205,11 +197,8 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob
|
||||
- With `predicate-quantifier: 'every'`:
|
||||
- `'true'` - if **any** changed file matches **all** of the filter's rules
|
||||
- `'false'` - if **no** changed file matches **all** of the filter's rules
|
||||
- With `predicate-quantifier: 'some-with-excludes'`:
|
||||
- `'true'` - if **any** changed file matches **at least one** of the filter's rules and **none** of its negated rules
|
||||
- `'false'` - if **no** changed file matches **at least one** of the filter's rules and **none** of its negated rules
|
||||
- Each filter sets an output variable with the name `${FILTER_NAME}_count` to the count of matching files.
|
||||
- If enabled, for each filter it sets an output variable with the name `${FILTER_NAME}_files`. It will contain a list of all files matching the filter. Treat these values as untrusted when filenames can come from pull requests.
|
||||
- If enabled, for each filter it sets an output variable with the name `${FILTER_NAME}_files`. It will contain a list of all files matching the filter.
|
||||
- `changes` - JSON array with names of all filters matching any of the changed files.
|
||||
|
||||
## Examples
|
||||
@@ -544,32 +533,6 @@ jobs:
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Detect changes in multiple unrelated paths and exclude some file extensions</summary>
|
||||
|
||||
```yaml
|
||||
- uses: dorny/paths-filter@v4
|
||||
id: filter
|
||||
with:
|
||||
# With 'some-with-excludes' a file is matched when it matches at least one pattern
|
||||
# and none of the negated ones. The filter below therefore matches all the files
|
||||
# in the 'mobile' folder and the workflow file, but never a markdown file or
|
||||
# anything in 'mobile/.config'.
|
||||
#
|
||||
# An exclusion is final - a file excluded by one pattern can't be included back
|
||||
# by another one. Consequently, a filter consisting of negated patterns only
|
||||
# never matches anything.
|
||||
predicate-quantifier: 'some-with-excludes'
|
||||
filters: |
|
||||
mobile:
|
||||
- 'mobile/**'
|
||||
- '!mobile/**/*.md'
|
||||
- '!mobile/.config/**'
|
||||
- '.github/workflows/test_mobile.yml'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Custom processing of changed files
|
||||
|
||||
<details>
|
||||
@@ -593,13 +556,9 @@ jobs:
|
||||
- added|modified: '*.md'
|
||||
- name: Lint Markdown
|
||||
if: ${{ steps.filter.outputs.markdown == 'true' }}
|
||||
env:
|
||||
MARKDOWN_FILES: ${{ steps.filter.outputs.markdown_files }}
|
||||
run: npx textlint $MARKDOWN_FILES
|
||||
run: npx textlint ${{ steps.filter.outputs.markdown_files }}
|
||||
```
|
||||
|
||||
When passing file lists to shell commands, use `env:` as shown above. Do not write `${{ steps.filter.outputs.markdown_files }}` directly inside the `run:` script.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -625,8 +584,6 @@ When passing file lists to shell commands, use `env:` as shown above. Do not wri
|
||||
files: ${{ steps.filter.outputs.changed_files }}
|
||||
```
|
||||
|
||||
The `json` and `csv` formats are intended as structured data for scripts, programs, or other actions. Passing them to an action input as above is fine. Do not interpolate `json` or `csv` outputs directly into a `run:` script.
|
||||
|
||||
</details>
|
||||
|
||||
## See also
|
||||
|
||||
@@ -20,12 +20,4 @@ describe('csvEscape() backslash escapes every character except subset of definit
|
||||
test('Double quote should be escaped by another double quote', () => {
|
||||
expect(csvEscape('file " with double quote')).toBe('"file "" with double quote"')
|
||||
})
|
||||
|
||||
test('filename with LF should be quoted per RFC 4180', () => {
|
||||
expect(csvEscape('a\nb')).toBe('"a\nb"')
|
||||
})
|
||||
|
||||
test('filename with CRLF should be quoted per RFC 4180', () => {
|
||||
expect(csvEscape('a\r\nb')).toBe('"a\r\nb"')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -148,99 +148,6 @@ describe('matching tests', () => {
|
||||
expect(otherPkgJpegMatch.backend).toEqual([])
|
||||
})
|
||||
|
||||
test('ignores exclusions when using the default predicate quantifier', () => {
|
||||
const yaml = `
|
||||
src:
|
||||
- 'src/**'
|
||||
- '!**/*.md'
|
||||
`
|
||||
const filter = new Filter(yaml)
|
||||
|
||||
// A negated pattern is just another pattern for the 'some' quantifier - a markdown file
|
||||
// inside 'src' still matches 'src/**' and any other file matches the negated pattern.
|
||||
const files = modified(['src/README.md', 'other/file.txt'])
|
||||
expect(filter.match(files).src).toEqual(files)
|
||||
})
|
||||
|
||||
test('matches files of every pattern when set to PredicateQuantifier.SOME_WITH_EXCLUDES', () => {
|
||||
const yaml = `
|
||||
mobile:
|
||||
- 'mobile/**'
|
||||
- '!mobile/**/*.md'
|
||||
- '!mobile/.config/**'
|
||||
- '.github/workflows/test_mobile.yml'
|
||||
`
|
||||
const filterConfig: FilterConfig = {predicateQuantifier: PredicateQuantifier.SOME_WITH_EXCLUDES}
|
||||
const filter = new Filter(yaml, filterConfig)
|
||||
|
||||
const sourceFiles = modified(['mobile/main.kt', 'mobile/src/some/Activity.kt'])
|
||||
const workflowFiles = modified(['.github/workflows/test_mobile.yml'])
|
||||
const docsFiles = modified(['mobile/README.md', 'mobile/docs/some/page.md'])
|
||||
const configFiles = modified(['mobile/.config/lint.json', 'mobile/.config/nested/lint.json'])
|
||||
const otherFiles = modified(['backend/main.go', '.github/workflows/test_backend.yml'])
|
||||
|
||||
expect(filter.match(sourceFiles).mobile).toEqual(sourceFiles)
|
||||
expect(filter.match(workflowFiles).mobile).toEqual(workflowFiles)
|
||||
expect(filter.match(docsFiles).mobile).toEqual([])
|
||||
expect(filter.match(configFiles).mobile).toEqual([])
|
||||
expect(filter.match(otherFiles).mobile).toEqual([])
|
||||
})
|
||||
|
||||
test('excludes file with PredicateQuantifier.SOME_WITH_EXCLUDES regardless of the pattern order', () => {
|
||||
const yaml = `
|
||||
excludeFirst:
|
||||
- '!**/*.md'
|
||||
- 'src/**'
|
||||
excludeLast:
|
||||
- 'src/**'
|
||||
- '!**/*.md'
|
||||
`
|
||||
const filterConfig: FilterConfig = {predicateQuantifier: PredicateQuantifier.SOME_WITH_EXCLUDES}
|
||||
const filter = new Filter(yaml, filterConfig)
|
||||
|
||||
const match = filter.match(modified(['src/index.ts', 'src/README.md']))
|
||||
expect(match.excludeFirst).toEqual(modified(['src/index.ts']))
|
||||
expect(match.excludeLast).toEqual(modified(['src/index.ts']))
|
||||
})
|
||||
|
||||
test('keeps file excluded with PredicateQuantifier.SOME_WITH_EXCLUDES even if a later pattern includes it', () => {
|
||||
const yaml = `
|
||||
src:
|
||||
- 'src/**'
|
||||
- '!**/*.md'
|
||||
- 'src/docs/**'
|
||||
`
|
||||
const filterConfig: FilterConfig = {predicateQuantifier: PredicateQuantifier.SOME_WITH_EXCLUDES}
|
||||
const filter = new Filter(yaml, filterConfig)
|
||||
|
||||
const match = filter.match(modified(['src/docs/guide.md', 'src/docs/logo.png']))
|
||||
expect(match.src).toEqual(modified(['src/docs/logo.png']))
|
||||
})
|
||||
|
||||
test('matches nothing with PredicateQuantifier.SOME_WITH_EXCLUDES when there is no include pattern', () => {
|
||||
const yaml = `
|
||||
src:
|
||||
- '!**/*.md'
|
||||
`
|
||||
const filterConfig: FilterConfig = {predicateQuantifier: PredicateQuantifier.SOME_WITH_EXCLUDES}
|
||||
const filter = new Filter(yaml, filterConfig)
|
||||
|
||||
const match = filter.match(modified(['src/index.ts', 'src/README.md']))
|
||||
expect(match.src).toEqual([])
|
||||
})
|
||||
|
||||
test('treats negated extglob as an include pattern with PredicateQuantifier.SOME_WITH_EXCLUDES', () => {
|
||||
const yaml = `
|
||||
backend:
|
||||
- '!(**/*.tsx|**/*.less)'
|
||||
`
|
||||
const filterConfig: FilterConfig = {predicateQuantifier: PredicateQuantifier.SOME_WITH_EXCLUDES}
|
||||
const filter = new Filter(yaml, filterConfig)
|
||||
|
||||
expect(filter.match(modified(['src/server.py'])).backend).toEqual(modified(['src/server.py']))
|
||||
expect(filter.match(modified(['src/ui.tsx'])).backend).toEqual([])
|
||||
})
|
||||
|
||||
test('matches path based on rules included using YAML anchor', () => {
|
||||
const yaml = `
|
||||
shared: &shared
|
||||
@@ -290,61 +197,6 @@ describe('matching specific change status', () => {
|
||||
expect(match.addOrModify).toEqual(files)
|
||||
})
|
||||
|
||||
test('respects change status of exclude patterns when set to PredicateQuantifier.SOME_WITH_EXCLUDES', () => {
|
||||
const yaml = `
|
||||
src:
|
||||
- 'src/**'
|
||||
- deleted: '!src/generated/**'
|
||||
`
|
||||
const filterConfig: FilterConfig = {predicateQuantifier: PredicateQuantifier.SOME_WITH_EXCLUDES}
|
||||
const filter = new Filter(yaml, filterConfig)
|
||||
|
||||
const files = [
|
||||
{status: ChangeStatus.Deleted, filename: 'src/generated/api.ts'},
|
||||
{status: ChangeStatus.Modified, filename: 'src/generated/api.ts'}
|
||||
]
|
||||
const match = filter.match(files)
|
||||
expect(match.src).toEqual([files[1]])
|
||||
})
|
||||
|
||||
test('matches multiple patterns of single change status when set to PredicateQuantifier.SOME_WITH_EXCLUDES', () => {
|
||||
const yaml = `
|
||||
docs: &docs
|
||||
- '!**/*.md'
|
||||
src:
|
||||
- added|modified: 'src/**'
|
||||
- added|modified: *docs
|
||||
`
|
||||
const filterConfig: FilterConfig = {predicateQuantifier: PredicateQuantifier.SOME_WITH_EXCLUDES}
|
||||
const filter = new Filter(yaml, filterConfig)
|
||||
|
||||
const files = [
|
||||
{status: ChangeStatus.Added, filename: 'src/index.ts'},
|
||||
{status: ChangeStatus.Added, filename: 'src/README.md'},
|
||||
{status: ChangeStatus.Deleted, filename: 'src/legacy.ts'}
|
||||
]
|
||||
const match = filter.match(files)
|
||||
expect(match.src).toEqual([files[0]])
|
||||
})
|
||||
|
||||
test('or-es patterns of single change status when using the default predicate quantifier', () => {
|
||||
const yaml = `
|
||||
src:
|
||||
- added|modified: ['src/**', '!**/*.md']
|
||||
`
|
||||
const filter = new Filter(yaml)
|
||||
|
||||
// Both patterns are OR-ed into a single rule, therefore a markdown file inside 'src'
|
||||
// matches through 'src/**' and any other file matches through the negated pattern.
|
||||
const files = [
|
||||
{status: ChangeStatus.Added, filename: 'src/README.md'},
|
||||
{status: ChangeStatus.Added, filename: 'other/file.txt'},
|
||||
{status: ChangeStatus.Deleted, filename: 'src/index.ts'}
|
||||
]
|
||||
const match = filter.match(files)
|
||||
expect(match.src).toEqual([files[0], files[1]])
|
||||
})
|
||||
|
||||
test('matches when using an anchor', () => {
|
||||
const yaml = `
|
||||
shared: &shared
|
||||
|
||||
@@ -54,16 +54,4 @@ describe('shellEscape() returns human readable filenames with as few escaping ap
|
||||
test('filename with single quote and special characters is split and quoted/escaped as needed', () => {
|
||||
expect(shellEscape("file'with $quote")).toBe("file\\''with $quote'")
|
||||
})
|
||||
|
||||
test('filename with LF should be single-quoted', () => {
|
||||
expect(shellEscape('x\ntouch pwned.md')).toBe("'x\ntouch pwned.md'")
|
||||
})
|
||||
|
||||
test('filename with CRLF should be single-quoted', () => {
|
||||
expect(shellEscape('x\r\ntouch pwned.md')).toBe("'x\r\ntouch pwned.md'")
|
||||
})
|
||||
|
||||
test('filename with CR should be single-quoted', () => {
|
||||
expect(shellEscape('a\rb')).toBe("'a\rb'")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,11 +46,7 @@ inputs:
|
||||
default: '100'
|
||||
predicate-quantifier:
|
||||
description: |
|
||||
allows to override the "at least one pattern" behavior:
|
||||
'some' - file is included if it matches at least one pattern (default).
|
||||
'every' - file is included only if it matches all of the patterns.
|
||||
'some-with-excludes' - file is included if it matches at least one pattern
|
||||
and no negated pattern (the ones prefixed with '!').
|
||||
allows to override the "at least one pattern" behavior to make it so that all of the patterns have to match or otherwise the file is excluded.
|
||||
required: false
|
||||
default: 'some'
|
||||
outputs:
|
||||
|
||||
1739
dist/index.js
vendored
1739
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
48
package-lock.json
generated
48
package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/github": "6.0.0",
|
||||
"@octokit/plugin-retry": "^6.1.0",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -199,7 +200,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz",
|
||||
"integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.23.5",
|
||||
@@ -1391,7 +1391,6 @@
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz",
|
||||
"integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.0.0",
|
||||
@@ -1463,6 +1462,38 @@
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-retry": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz",
|
||||
"integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^13.0.0",
|
||||
"bottleneck": "^2.15.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": "5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-retry/node_modules/@octokit/openapi-types": {
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/plugin-retry/node_modules/@octokit/types": {
|
||||
"version": "13.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^24.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "8.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.6.tgz",
|
||||
@@ -1895,7 +1926,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2296,6 +2326,12 @@
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
||||
},
|
||||
"node_modules/bottleneck": {
|
||||
"version": "2.19.5",
|
||||
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
|
||||
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
@@ -2336,7 +2372,6 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001565",
|
||||
"electron-to-chromium": "^1.4.601",
|
||||
@@ -2883,7 +2918,6 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
|
||||
"integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -2939,7 +2973,6 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
|
||||
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -3114,7 +3147,6 @@
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
|
||||
"integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -4622,7 +4654,6 @@
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "^29.7.0",
|
||||
"@jest/types": "^29.6.3",
|
||||
@@ -6798,7 +6829,6 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/github": "6.0.0",
|
||||
"@octokit/plugin-retry": "^6.1.0",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -21,12 +21,6 @@ const MatchOptions = {
|
||||
interface FilterRuleItem {
|
||||
status?: ChangeStatus[] // Required change status of the matched files
|
||||
isMatch: (str: string) => boolean // Matches the filename
|
||||
// Matchers for the individual polarities of the patterns this item was created from.
|
||||
// They are used only by the PredicateQuantifier.SOME_WITH_EXCLUDES quantifier,
|
||||
// which has to tell inclusions and exclusions apart. The other quantifiers keep
|
||||
// evaluating the item as a whole via 'isMatch'.
|
||||
isInclude?: (str: string) => boolean // Matches any of the patterns which are not negated
|
||||
isExclude?: (str: string) => boolean // Matches any of the patterns which are negated (e.g. '!**/*.md')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,17 +47,7 @@ export enum PredicateQuantifier {
|
||||
* at least one pattern that matches them. This is the default behavior if you don't
|
||||
* specify anything as a predicate quantifier.
|
||||
*/
|
||||
SOME = 'some',
|
||||
/**
|
||||
* When choosing 'some-with-excludes' in the config it means that files will get matched if
|
||||
* at least one of the patterns matches them and none of the negated patterns (the ones
|
||||
* prefixed with '!') matches them. An exclusion is final - a file excluded by one pattern
|
||||
* can't be included back by another one.
|
||||
*
|
||||
* A filter which consists of negated patterns only never matches anything,
|
||||
* because there is no pattern which could include a file in the first place.
|
||||
*/
|
||||
SOME_WITH_EXCLUDES = 'some-with-excludes'
|
||||
SOME = 'some'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,35 +104,13 @@ export class Filter {
|
||||
}
|
||||
|
||||
private isMatch(file: File, patterns: FilterRuleItem[]): boolean {
|
||||
const isStatusMatch = (rule: Readonly<FilterRuleItem>): boolean => {
|
||||
return rule.status === undefined || rule.status.includes(file.status)
|
||||
}
|
||||
const aPredicate = (rule: Readonly<FilterRuleItem>): boolean => {
|
||||
return isStatusMatch(rule) && rule.isMatch(file.filename)
|
||||
return (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename)
|
||||
}
|
||||
|
||||
switch (this.filterConfig?.predicateQuantifier) {
|
||||
case PredicateQuantifier.EVERY:
|
||||
return patterns.every(aPredicate)
|
||||
case PredicateQuantifier.SOME_WITH_EXCLUDES: {
|
||||
let isIncluded = false
|
||||
for (const rule of patterns) {
|
||||
if (!isStatusMatch(rule)) {
|
||||
continue
|
||||
}
|
||||
// Once a file is excluded it stays excluded - no other pattern can include it back.
|
||||
// Therefore all the patterns have to be evaluated even if the file is already included.
|
||||
if (rule.isExclude?.(file.filename)) {
|
||||
return false
|
||||
}
|
||||
if (!isIncluded && rule.isInclude?.(file.filename)) {
|
||||
isIncluded = true
|
||||
}
|
||||
}
|
||||
return isIncluded
|
||||
}
|
||||
default:
|
||||
return patterns.some(aPredicate)
|
||||
if (this.filterConfig?.predicateQuantifier === 'every') {
|
||||
return patterns.every(aPredicate)
|
||||
} else {
|
||||
return patterns.some(aPredicate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +120,7 @@ export class Filter {
|
||||
}
|
||||
|
||||
if (typeof item === 'string') {
|
||||
return [createRuleItem(item)]
|
||||
return [{status: undefined, isMatch: picomatch(item, MatchOptions)}]
|
||||
}
|
||||
|
||||
if (typeof item === 'object') {
|
||||
@@ -168,12 +130,14 @@ export class Filter {
|
||||
`Expected [key:string]= pattern:string | string[], but [${key}:${typeof key}]= ${pattern}:${typeof pattern} found`
|
||||
)
|
||||
}
|
||||
const status = key
|
||||
.split('|')
|
||||
.map(x => x.trim())
|
||||
.filter(x => x.length > 0)
|
||||
.map(x => x.toLowerCase()) as ChangeStatus[]
|
||||
return createRuleItem(pattern, status)
|
||||
return {
|
||||
status: key
|
||||
.split('|')
|
||||
.map(x => x.trim())
|
||||
.filter(x => x.length > 0)
|
||||
.map(x => x.toLowerCase()) as ChangeStatus[],
|
||||
isMatch: picomatch(pattern, MatchOptions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -190,25 +154,3 @@ export class Filter {
|
||||
function flat<T>(arr: T[][]): T[] {
|
||||
return arr.reduce((acc, val) => acc.concat(val), [])
|
||||
}
|
||||
|
||||
// Compiles filename pattern(s) of a single filter rule item into matchers.
|
||||
// Multiple patterns are OR-ed together, which is how picomatch treats an array of globs.
|
||||
// Patterns are also split by their polarity, so PredicateQuantifier.SOME_WITH_EXCLUDES
|
||||
// can tell inclusions from exclusions. Note that only a leading '!' negates the whole
|
||||
// pattern - the '!(...)' extglob is a regular pattern matching everything it doesn't enumerate.
|
||||
function createRuleItem(patterns: string | string[], status?: ChangeStatus[]): FilterRuleItem {
|
||||
const matchers = (Array.isArray(patterns) ? patterns : [patterns]).map(pattern =>
|
||||
picomatch(pattern, MatchOptions, true)
|
||||
)
|
||||
// picomatch inverts the result of a matcher created from a negated pattern.
|
||||
// Inverting it back gives a matcher of the filenames such pattern excludes.
|
||||
const includes = matchers.filter(matcher => !matcher.state.negated)
|
||||
const excludes = matchers.filter(matcher => matcher.state.negated)
|
||||
|
||||
return {
|
||||
status,
|
||||
isMatch: str => matchers.some(matcher => matcher(str)),
|
||||
isInclude: includes.length > 0 ? str => includes.some(matcher => matcher(str)) : undefined,
|
||||
isExclude: excludes.length > 0 ? str => excludes.some(matcher => !matcher(str)) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ export function csvEscape(value: string): string {
|
||||
if (value === '') return value
|
||||
|
||||
// Only safe characters
|
||||
if (/^[a-zA-Z0-9._+:@%/-]+$/.test(value)) {
|
||||
if (/^[a-zA-Z0-9._+:@%/-]+$/m.test(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ export function shellEscape(value: string): string {
|
||||
if (value === '') return value
|
||||
|
||||
// Only safe characters
|
||||
if (/^[a-zA-Z0-9,._+:@%/-]+$/.test(value)) {
|
||||
if (/^[a-zA-Z0-9,._+:@%/-]+$/m.test(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (value.includes("'")) {
|
||||
// Only safe characters, single quotes and white-spaces
|
||||
if (/^[a-zA-Z0-9,._+:@%/'\s-]+$/.test(value)) {
|
||||
if (/^[a-zA-Z0-9,._+:@%/'\s-]+$/m.test(value)) {
|
||||
return `"${value}"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as fs from 'fs'
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import {retry} from '@octokit/plugin-retry'
|
||||
import {GetResponseDataTypeFromEndpointMethod} from '@octokit/types'
|
||||
import {MergeGroupEvent, PullRequest, PushEvent} from '@octokit/webhooks-types'
|
||||
|
||||
@@ -204,7 +205,7 @@ async function getChangedFilesFromGit(base: string, head: string, initialFetchDe
|
||||
async function getChangedFilesFromApi(token: string, pullRequest: PullRequest): Promise<File[]> {
|
||||
core.startGroup(`Fetching list of changed files for PR#${pullRequest.number} from GitHub API`)
|
||||
try {
|
||||
const client = github.getOctokit(token)
|
||||
const client = github.getOctokit(token, undefined, retry)
|
||||
const per_page = 100
|
||||
const files: File[] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user