Skip to content
Merged
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
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: CI

on:
push:
branches:
- main
pull_request:

jobs:
check:
name: Lint, typecheck, test, and build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: pnpm
- run: pnpm install
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test:run
- run: pnpm build
# The action runs the committed bundle, so it must match the source.
- name: Check that dist is current
run: git diff --exit-code -- dist

e2e:
name: Run the action on the fixture
# The fixture rule reports a finding only on changed lines, and only a pull request has changed lines.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
checks: write
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v6
- id: alint
uses: ./
with:
version: 0.7.3
working-directory: test/fixture
check-name: alint (fixture)
- name: Report the outputs
run: echo "errors=${{ steps.alint.outputs.errors }} warnings=${{ steps.alint.outputs.warnings }}"
86 changes: 85 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,87 @@
# alint-dev/setup

GitHub Action that runs `alint` on a pull request and publishes the result.
GitHub Action that runs [`alint`](https://github.com/alint-dev/alint) on a pull request and publishes the result.

The action does these steps:

1. Installs `alint`.
2. Writes the model provider configuration.
3. Restores the `alint` cache.
4. Lints the changed files of the pull request and keeps the findings on changed lines.
5. Publishes a check run with line annotations, one summary comment, and a job summary.

## Usage

```yaml
name: alint

on:
pull_request:

jobs:
alint:
runs-on: ubuntu-latest
permissions:
checks: write
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: alint-dev/setup@main
with:
provider-endpoint: https://api.openai.com/v1
provider-model: gpt-5.4-mini
api-key: ${{ secrets.OPENAI_API_KEY }}
```

If the `alint` configuration imports packages, install the project dependencies before this action.

## Inputs

| Input | Default | Description |
| --- | --- | --- |
| `version` | | Version of `@alint-js/cli` to install. When empty, the action uses the `alint` in `node_modules`, or installs the latest version. |
| `provider-endpoint` | | Endpoint of an OpenAI-compatible model provider. When empty, the action does not write provider configuration. |
| `provider-model` | | Model id for the provider. Required with `provider-endpoint`. |
| `api-key` | | API key for the provider. The action sends it as a Bearer token. |
| `files` | | Files or directories to lint, separated by spaces or lines. |
| `working-directory` | `.` | Directory that contains the `alint` configuration. |
| `check-name` | `alint` | Name of the check run. |
| `github-token` | `github.token` | Token that creates the check run and the summary comment. |

## Outputs

| Output | Description |
| --- | --- |
| `errors` | Number of error findings. |
| `warnings` | Number of warning findings. |

## Behavior

- The check run fails when a finding is an error. It is neutral when there are only warnings, and it passes when there are no findings.
- The step itself fails only when `alint` cannot complete the run or GitHub rejects a request.
- Each run rewrites the same summary comment. A pull request that never had findings gets no comment.
- On a pull request, the action reads the changed files from the GitHub API. The checkout does not need the full history.
- When `files` is set, the action lints those files and keeps each finding.
- On other events, the action lints the working directory and writes no comment.
- A pull request from a fork gets no secrets and a read-only token. The action skips it with a notice.

## When not to use it

Do not use this action for local runs or for a CI system other than GitHub Actions. Run the `alint` CLI there.

## Development

```bash
pnpm install
pnpm test:run
pnpm build
```

The action runs `dist/index.mjs`. Run `pnpm build` and commit `dist` with each source change. CI fails when `dist` does not match the source.

The `e2e` job runs the action on `test/fixture`. Its rule does not call a model, so the job needs no provider.

## License

MIT
116 changes: 116 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: alint
description: Run alint on a pull request and publish the result as a check run and a summary comment
author: alint-dev
branding:
icon: check-circle
color: purple

inputs:
version:
description: >-
Version of @alint-js/cli to install. When empty, the action uses the alint that the project
installed in node_modules, or installs the latest version.
default: ''
provider-endpoint:
description: >-
Endpoint of an OpenAI-compatible model provider. When empty, the action does not write
provider configuration.
default: ''
provider-model:
description: Model id for the provider. Required with provider-endpoint.
default: ''
api-key:
description: API key for the provider. The action sends it as a Bearer token.
default: ''
files:
description: >-
Files or directories to lint, separated by spaces or lines. When empty, a pull request gets
its changed lines and other events get the working directory.
default: ''
working-directory:
description: Directory that contains the alint configuration.
default: .
check-name:
description: Name of the check run.
default: alint
github-token:
description: Token that creates the check run and the summary comment.
default: ${{ github.token }}

outputs:
errors:
description: Number of error findings.
value: ${{ steps.run.outputs.errors }}
warnings:
description: Number of warning findings.
value: ${{ steps.run.outputs.warnings }}

runs:
using: composite
steps:
# A pull request from a fork gets no secrets and a read-only token, so the action cannot publish there.
- id: guard
shell: bash
run: |
if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && [ "$(jq -r '.pull_request.head.repo.full_name' "$GITHUB_EVENT_PATH")" != "$GITHUB_REPOSITORY" ]; then
echo "::notice title=alint::Skipped. A pull request from a fork cannot publish the result."
echo "skip=true" >> "$GITHUB_OUTPUT"
fi

- id: install
if: steps.guard.outputs.skip != 'true'
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
ALINT_VERSION: ${{ inputs.version }}
run: |
if [ -z "$ALINT_VERSION" ] && [ -x node_modules/.bin/alint ]; then
echo "command=$PWD/node_modules/.bin/alint" >> "$GITHUB_OUTPUT"
else
npm install --global "@alint-js/cli@${ALINT_VERSION:-latest}"
echo "command=alint" >> "$GITHUB_OUTPUT"
fi

- if: steps.guard.outputs.skip != 'true' && inputs.provider-endpoint != ''
shell: bash
env:
ALINT: ${{ steps.install.outputs.command }}
ALINT_API_KEY: ${{ inputs.api-key }}
ALINT_ENDPOINT: ${{ inputs.provider-endpoint }}
ALINT_MODEL: ${{ inputs.provider-model }}
run: |
"$ALINT" setup --no-interactive \
--provider-id ci \
--provider-endpoint "$ALINT_ENDPOINT" \
--provider-model "$ALINT_MODEL" \
${ALINT_API_KEY:+--provider-header "Authorization=Bearer $ALINT_API_KEY"}

- if: steps.guard.outputs.skip != 'true'
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
ALINT: ${{ steps.install.outputs.command }}
run: |
"$ALINT" plugin install

- if: steps.guard.outputs.skip != 'true'
uses: actions/cache@v4
with:
path: ${{ inputs.working-directory }}/.alintcache
# alint keys each cached result by the file content and the rule text.
# A new key for each commit saves the latest cache, and the prefix restores the most recent one.
key: alint-${{ runner.os }}-${{ github.sha }}
restore-keys: |
alint-${{ runner.os }}-

- id: run
if: steps.guard.outputs.skip != 'true'
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
ALINT_COMMAND: ${{ steps.install.outputs.command }}
GITHUB_TOKEN: ${{ inputs.github-token }}
INPUT_CHECK_NAME: ${{ inputs.check-name }}
INPUT_FILES: ${{ inputs.files }}
run: |
node "$GITHUB_ACTION_PATH/dist/index.mjs"
Loading
Loading