├── .pre-commit-config.yaml ├── SECURITY.md ├── .github └── workflows │ └── ci.yaml ├── LICENSE ├── README.md └── action.yaml /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v6.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | To report a vulnerability in prek-action, [open a private vulnerability report](https://github.com/j178/prek-action/security/advisories/new) and you can create a patch on a private fork or, after reporting the problem, our maintainers will fix it as soon as possible. 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main, 'dev-*'] 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macos-latest] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - uses: actions/checkout@v5 16 | - name: Test | ${{ matrix.os }} | no extra-args 17 | uses: ./ 18 | 19 | - name: Test | ${{ matrix.os }} | extra-args 20 | uses: ./ 21 | with: 22 | extra-args: '--hook-stage pre-commit --verbose' 23 | 24 | - name: Test | ${{ matrix.os }} | extra-args with CJK characters 25 | uses: ./ 26 | with: 27 | extra-args: '--files 中文' 28 | 29 | - name: Test | ${{ matrix.os }} | extra-args with quoted string (with spaces) 30 | uses: ./ 31 | with: 32 | extra-args: '--files "file with spaces.txt"' 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Frost Ming 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prek Action 2 | 3 | A GitHub Action that runs [pre-commit](https://pre-commit.com) hooks using [prek](https://github.com/j178/prek) in your CI/CD pipeline. 4 | 5 | *Despite the name, `pre-commit` hooks can be run at any time, not just before commits.* 6 | 7 | ## What is prek? 8 | 9 | [prek](https://github.com/j178/prek) is a fast hooks runner that provides an alternative to the widely-used [pre-commit](https://pre-commit.com) framework. It offers better performance and caching capabilities for running code quality checks. 10 | 11 | ## Usage 12 | 13 | ### Basic Usage 14 | 15 | ```yaml 16 | name: Prek checks 17 | on: [push, pull_request] 18 | 19 | jobs: 20 | prek: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v6 24 | - uses: j178/prek-action@v1 25 | ``` 26 | 27 | ### Custom Arguments 28 | 29 | ```yaml 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: j178/prek-action@v1 33 | with: 34 | extra-args: '--all-files --directory packages/' 35 | ``` 36 | 37 | ### Running Specific Hooks 38 | 39 | ```yaml 40 | steps: 41 | - uses: actions/checkout@v6 42 | - uses: j178/prek-action@v1 43 | with: 44 | extra-args: '--all-files mypy flake8 ruff' 45 | ``` 46 | 47 | ### Specifying Prek Version 48 | 49 | ```yaml 50 | steps: 51 | - uses: actions/checkout@v6 52 | - uses: j178/prek-action@v1 53 | with: 54 | prek-version: '0.2.1' 55 | extra-args: '--all-files' 56 | ``` 57 | 58 | ### Install Only 59 | 60 | ```yaml 61 | steps: 62 | - uses: actions/checkout@v6 63 | - uses: j178/prek-action@v1 64 | with: 65 | install-only: true 66 | - run: | 67 | prek run \ 68 | --show-diff-on-failure \ 69 | --color always 70 | ``` 71 | 72 | When `install-only` is set to `true`, the action will only install prek and skip running it. The `extra-args` input has no effect in this mode. 73 | 74 | Running `prek` in a separate step can be useful for example when you need to customize the environment. 75 | 76 | ## Inputs 77 | 78 | | Input | Description | Required | Default | 79 | | ------------------ | ------------------------------------------ | -------- | ------------- | 80 | | `extra-args` | Additional arguments to pass to `prek run` | No | `--all-files` | 81 | | `install-only` | Only install prek, do not run it | No | `false` | 82 | | `prek-version` | Version of prek to install (e.g., '0.2.1', 'latest') | No | `latest` | 83 | | `working-directory` | The working directory to run prek in | No | `.` | 84 | | `token` | GitHub token to avoid API rate limiting | No | `${{ github.token }}` | 85 | 86 | ## Outputs 87 | 88 | | Output | Description | 89 | | -------------- | ----------------------------------------------------- | 90 | | `prek-version` | The resolved version of prek that was installed | 91 | 92 | ## Requirements 93 | 94 | Your repository must have a `.pre-commit-config.yaml` file configured for use with pre-commit hooks. 95 | 96 | ## License 97 | 98 | This action is licensed under the MIT License. See [LICENSE](LICENSE) for details. 99 | -------------------------------------------------------------------------------- /action.yaml: -------------------------------------------------------------------------------- 1 | name: prek-action 2 | description: Run pre-commit hooks using prek 3 | inputs: 4 | extra-args: 5 | description: Options to pass to `prek run` 6 | required: false 7 | default: '--all-files' 8 | extra_args: 9 | description: Options to pass to `prek run` (deprecated, use extra-args) 10 | required: false 11 | install-only: 12 | description: Only install prek, do not run it 13 | required: false 14 | default: 'false' 15 | prek-version: 16 | description: Version of prek to install (e.g., '0.2.1', 'latest') 17 | required: false 18 | default: 'latest' 19 | working-directory: 20 | description: The working directory to run prek in 21 | required: false 22 | default: '.' 23 | token: 24 | description: GitHub token to avoid API rate limiting 25 | required: false 26 | default: ${{ github.token }} 27 | 28 | outputs: 29 | prek-version: 30 | description: The version of prek that was installed 31 | value: ${{ steps.resolve.outputs.version }} 32 | runs: 33 | using: composite 34 | steps: 35 | - name: Resolve version 36 | id: resolve 37 | run: | 38 | # Resolve prek version 39 | 40 | import os 41 | import json 42 | from urllib import request 43 | 44 | prek_version = os.environ['PREK_VERSION'] 45 | gh_token = os.environ.get('GH_TOKEN', '') 46 | 47 | if prek_version == 'latest': 48 | url = 'https://api.github.com/repos/j178/prek/releases/latest' 49 | headers = {'Accept': 'application/vnd.github+json'} 50 | 51 | if gh_token: 52 | headers['Authorization'] = f'Bearer {gh_token}' 53 | 54 | try: 55 | req = request.Request(url, headers=headers) 56 | with request.urlopen(req) as response: 57 | data = json.loads(response.read()) 58 | prek_version = data['tag_name'] 59 | except Exception as e: 60 | if gh_token: 61 | print(f'::warning::Authenticated request failed: {e}, falling back to unauthenticated request') 62 | headers.pop('Authorization', None) 63 | req = request.Request(url, headers=headers) 64 | with request.urlopen(req) as response: 65 | data = json.loads(response.read()) 66 | prek_version = data['tag_name'] 67 | else: 68 | raise 69 | 70 | version = prek_version.lstrip('v') 71 | version = f'v{version}' 72 | 73 | with open(os.environ['GITHUB_OUTPUT'], 'a') as f: 74 | f.write(f'version={version}\n') 75 | shell: python 76 | env: 77 | PREK_VERSION: ${{ inputs.prek-version }} 78 | GH_TOKEN: ${{ inputs.token }} 79 | - name: Install prek 80 | run: | 81 | # Installing prek 82 | 83 | echo "::group::Installing prek ${PREK_VERSION}" 84 | export PREK_UNMANAGED_INSTALL=1 85 | export PREK_INSTALL_DIR="$RUNNER_TOOL_CACHE/prek/${PREK_VERSION}/$RUNNER_ARCH" 86 | mkdir -p "$PREK_INSTALL_DIR" 87 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/download/${PREK_VERSION}/prek-installer.sh | sh 88 | echo "$PREK_INSTALL_DIR" >> $GITHUB_PATH 89 | echo "::endgroup::" 90 | shell: bash 91 | if: runner.os != 'Windows' 92 | env: 93 | PREK_VERSION: ${{ steps.resolve.outputs.version }} 94 | - name: Install prek (Windows) 95 | run: | 96 | # Installing prek 97 | 98 | Write-Host "::group::Installing prek $env:PREK_VERSION" 99 | $env:PREK_UNMANAGED_INSTALL = "1" 100 | $env:PREK_INSTALL_DIR = "$env:RUNNER_TOOL_CACHE\prek\$env:PREK_VERSION\$env:RUNNER_ARCH" 101 | New-Item -ItemType Directory -Force -Path $env:PREK_INSTALL_DIR 102 | Invoke-RestMethod https://github.com/j178/prek/releases/download/$env:PREK_VERSION/prek-installer.ps1 | Invoke-Expression 103 | Add-Content -Path $env:GITHUB_PATH -Value $env:PREK_INSTALL_DIR 104 | Write-Host "::endgroup::" 105 | shell: pwsh 106 | if: runner.os == 'Windows' 107 | env: 108 | PREK_VERSION: ${{ steps.resolve.outputs.version }} 109 | - name: Setup cache 110 | uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 111 | with: 112 | path: | 113 | ~/.cache/prek 114 | ~\AppData\Local\prek 115 | # `env.pythonLocation` is set by `actions/setup-python`, pointing to the directory where Python is installed. 116 | # prek doesn't require Python, but the hooks it runs often do (e.g., pre-commit hooks), changing the Python version makes Python virtual environments invalid. 117 | # So we include it in the cache key to avoid using invalid caches. 118 | key: prek-v1|${{ runner.os }}|${{ runner.arch }}|${{ env.pythonLocation }}|${{ hashFiles(format('{0}/**/.pre-commit-config.yaml', inputs.working-directory)) }} 119 | - name: Run prek 120 | run: | 121 | # Running prek 122 | 123 | import shlex 124 | import sys 125 | import os 126 | 127 | cmd = ['prek', 'run', '--show-diff-on-failure', '--color=always'] 128 | 129 | extra_args = os.environ['EXTRA_ARGS'] 130 | if extra_args: 131 | try: 132 | parsed_args = shlex.split(extra_args) 133 | cmd.extend(parsed_args) 134 | except ValueError as e: 135 | print(f'Error parsing extra-args: {e}', file=sys.stderr) 136 | sys.exit(1) 137 | 138 | os.execvp(cmd[0], cmd) 139 | 140 | shell: python 141 | working-directory: ${{ inputs.working-directory }} 142 | if: inputs.install-only == 'false' 143 | env: 144 | EXTRA_ARGS: ${{ inputs.extra_args || inputs.extra-args }} 145 | - name: Show verbose logs 146 | run: | 147 | # Prek verbose logs 148 | 149 | echo "::group::Prek verbose logs" 150 | cache_dir="$(prek cache dir --no-log-file 2>/dev/null || echo ~/.cache/prek)" 151 | log="$cache_dir/prek.log" 152 | cat "$log" || echo "No prek log file found at $log" 153 | echo "::endgroup::" 154 | shell: bash 155 | if: inputs.install-only == 'false' && always() 156 | branding: 157 | icon: 'git-commit' 158 | color: 'orange' 159 | --------------------------------------------------------------------------------