Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/template-files/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ conda/infrastructure:
- .github/workflows/cla.yml
- .github/workflows/update.yml

# [optional] to include repo in https://github.com/orgs/conda/projects/2
# [optional] project management workflows
- .github/workflows/issues.yml
- .github/workflows/labels.yml
- .github/workflows/project.yml
Expand Down Expand Up @@ -48,3 +48,8 @@ conda/infrastructure:
# dst: rever.xsh # codespell:ignore rever
# - src: templates/releases/TEMPLATE
# dst: news/TEMPLATE

conda/actions:
# [optional] lint workflow (requires .pre-commit-config.yaml)
- src: lint/workflow.yml.tmpl
dst: .github/workflows/lint.yml
91 changes: 90 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,98 @@ jobs:
${{ steps.templates-error.outputs.summary }}
GITHUB_TOKEN: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }}

lint:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Filter Changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: filter
with:
filters: |
lint:
- 'lint/**'

# Test 1: Success case - clean code should pass lint
- name: Run lint on success test data
id: lint-success
continue-on-error: true
uses: ./lint
env:
WORKING_DIRECTORY: lint/data/success
with:
token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }}
checkout: false
working-directory: ${{ env.WORKING_DIRECTORY }}
config: pre-commit-config.yaml
# Only comment on PR if lint code changed
pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }}
comment-anchor: lint-comment-test-success
comment-on-success: ${{ steps.filter.outputs.lint == 'true' }}
comment-header: |
> [!WARNING]
> **Working directory:** `${{ env.WORKING_DIRECTORY }}`
> This is what the lint comment looks like when there are no lint issues.

# Test 2: Error case - code with issues should fail lint
- name: Run lint on error test data
id: lint-error
continue-on-error: true
uses: ./lint
env:
WORKING_DIRECTORY: lint/data/error
with:
token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }}
checkout: false
working-directory: ${{ env.WORKING_DIRECTORY }}
config: pre-commit-config.yaml
# Only comment on PR if lint code changed
pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }}
comment-anchor: lint-comment-test-error
comment-header: |
> [!WARNING]
> **Working directory:** `${{ env.WORKING_DIRECTORY }}`
> This is what the lint comment looks like when there are lint issues.

- name: Reset test data changes
run: git checkout -- lint/data/

# Verify all test outcomes
- name: Verify test outcomes
run: |
failed=0

# Test 1: Success case should pass
if [[ "${{ steps.lint-success.outputs.outcome }}" != "success" ]]; then
echo "::error::Test 1 (success): Expected outcome=success, got ${{ steps.lint-success.outputs.outcome }}"
failed=1
fi
if [[ -z "${{ steps.lint-success.outputs.output }}" ]]; then
echo "::error::Test 1 (success): Expected output to be non-empty"
failed=1
fi

# Test 2: Error case should fail
if [[ "${{ steps.lint-error.outputs.outcome }}" != "failure" ]]; then
echo "::error::Test 2 (error): Expected outcome=failure, got ${{ steps.lint-error.outputs.outcome }}"
failed=1
fi
if ! echo "${{ steps.lint-error.outputs.output }}" | grep -q "F401"; then
echo "::error::Test 2 (error): Expected output to contain F401 (unused import)"
failed=1
fi
if [[ -z "${{ steps.lint-error.outputs.diff }}" ]]; then
echo "::error::Test 2 (error): Expected diff output to be non-empty"
failed=1
fi

exit $failed

# required check
analyze:
needs: [pytest, read-file, template-files]
needs: [pytest, read-file, template-files, lint]
if: '!cancelled()'
runs-on: ubuntu-latest
steps:
Expand Down
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# exclude test files with intentional lint issues
exclude: ^lint/data/error/

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
Expand Down
150 changes: 150 additions & 0 deletions lint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Lint Action

A composite GitHub Action that runs [prek](https://github.com/j178/prek) (a fast pre-commit hook runner) with optional autofix and PR comments.

## Files

- `action.yml` - Composite action with all logic
- `workflow.yml.tmpl` - Workflow template for syncing to repos

## Syncing to Repositories

To adopt this workflow via template-files, add to your `.github/template-files/config.yml`:

```yaml
- source: lint/workflow.yml.tmpl
target: .github/workflows/lint.yml
# Optional: additional branch patterns (main is always included)
# branches:
# - '2[0-9].[0-9]+.x' # CalVer release branches
# Optional: Python version for prek hooks
# python_version: '3.12'
```

## Features

- Installs and runs prek with your existing `.pre-commit-config.yaml`
- Captures command output and git diff for PR comments
- Creates/updates sticky PR comments showing lint issues and suggested fixes
- Updates comment to show success when issues are resolved
- Optionally commits and pushes fixes (autofix mode)
- Reacts to trigger comments with 👀 → 🎉/😕

## Usage

### Basic Usage (lint check only)

```yaml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: conda/actions/lint@main
```

The action automatically fails if lint issues are found (unless `autofix: true`).

### With Autofix via Comment Trigger

```yaml
on:
pull_request:
issue_comment:
types: [created]

jobs:
lint:
if: >-
github.event_name == 'pull_request'
|| (
github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& github.event.comment.body == '@conda-bot prek autofix'
)
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: conda/actions/lint@main
with:
autofix: ${{ github.event_name == 'issue_comment' }}
comment-id: ${{ github.event.comment.id }}
pr-number: ${{ github.event.issue.number }}
```

## Inputs

| Input | Description | Required | Default |
|-------|-------------|----------|---------|
| `token` | GitHub token for PR comments and pushing | No | `${{ github.token }}` |
| `autofix` | Whether to commit and push fixes | No | `'false'` |
| `comment-id` | Comment ID to react to (for issue_comment triggers) | No | `''` |
| `pr-number` | PR number (defaults to current PR, override for issue_comment triggers) | No | `${{ github.event.pull_request.number }}` |
| `git-user-name` | Git user name for autofix commits | No | `conda-bot` |
| `git-user-email` | Git user email for autofix commits | No | `18747875+conda-bot@users.noreply.github.com` |
| `python-version` | Python version for running prek hooks | No | `'3.12'` |
| `checkout` | Whether to checkout the repository (set to false if already checked out) | No | `'true'` |
| `working-directory` | Directory to run prek in (defaults to repo root) | No | `'.'` |
| `config` | Path to pre-commit config file (defaults to auto-discovery) | No | `''` |
| `comment-anchor` | Unique anchor for sticky comment (customize to avoid conflicts with parallel workflows) | No | `'lint-comment'` |
| `comment-header` | Optional header text to prepend to comments (e.g., to mark test comments) | No | `''` |
| `comment-on-success` | Create success comment even without prior lint failure (useful for testing) | No | `'false'` |

## Outputs

| Output | Description |
|--------|-------------|
| `outcome` | `success` if no lint issues, `failure` if issues found |
| `output` | The prek command output |
| `diff` | The git diff of suggested fixes (only if outcome is failure) |

## Disabling PR Comments

To run lint without creating PR comments, omit the `pr-number` input:

```yaml
- uses: conda/actions/lint@main
with:
pr-number: '' # No PR comments
```

This is useful for:
- Running lint in contexts without a PR (e.g., scheduled runs)
- CI test scenarios where you don't want test comments cluttering PRs
- Conditional commenting based on file changes (see tests.yml for an example)

## PR Comments

The action creates a sticky comment (identified by `<!-- lint-comment -->`) that:

- Shows prek output and git diff on failure
- Shows a note for fork PRs (autofix cannot push to forks)
- Shows a warning if autofix was attempted but push failed
- Updates to "✅ Lint issues fixed" when resolved
- Includes link to workflow run for details

## Fork PRs

By default, `GITHUB_TOKEN` cannot push to fork PRs. The action detects this and shows a helpful message explaining how to fix locally.

To enable autofix for fork PRs, use a GitHub App token instead (**untested**):

```yaml
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- uses: conda/actions/lint@main
with:
token: ${{ steps.app-token.outputs.token }}
autofix: true
comment-id: ${{ github.event.comment.id }}
pr-number: ${{ github.event.issue.number }}
```

Requirements:
- GitHub App with `contents: write` and `pull-requests: write` permissions
- PR author must enable "Allow edits from maintainers"
Loading
Loading