Skip to content

🎨 Linters, Validators, and Formatters #43

🎨 Linters, Validators, and Formatters

🎨 Linters, Validators, and Formatters #43

Workflow file for this run

# Drives the project's own `mise run check` (the same task local dev + the pre-commit
# hook use) and reports lint/format failures. On a PR it leaves one sticky comment
# pointing the author at `mise run check --fix`. See mise-fy ci/github.md.
name: 🎨 Linters, Validators, and Formatters
on:
pull_request: {}
schedule:
# Daily sweep so the default branch can't quietly rot.
- cron: "0 0 * * *"
# Run manually — pick the scope: --all (whole tree) or --pr (this branch's diff vs the default branch).
workflow_dispatch:
inputs:
scope:
description: "Files to check"
type: choice
options: [all, pr]
default: all
# Read the repo; comment on PRs to point the author at the fix command.
permissions:
contents: read
pull-requests: write
# One run per PR (or ref) — newer pushes cancel stale runs.
concurrency:
group: lint-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check:
name: check
runs-on: ubuntu-latest
env:
# Pretty colors in the CI!
CLICOLOR_FORCE: "1"
FORCE_COLOR: "1"
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0 # full history so `check --pr` can diff against the default branch
persist-credentials: false
- name: Setup mise
uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0
with:
install: true
install_args: "--locked" # honor mise.lock
cache: true
experimental: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # so tool installs don't hit API rate limits
# Report only — same `check` task local + pre-commit use; we do NOT auto-fix in CI.
# On failure, emit a `::error::` pointing the author at `--fix` (see the comment step).
- name: Run check
id: check
env:
EVENT: ${{ github.event_name }} # via env, never interpolated (zizmor: template-injection)
SCOPE_INPUT: ${{ inputs.scope }} # 'all'|'pr' on manual dispatch; empty on PR/schedule
BASE_REF: ${{ github.event.pull_request.base.ref || github.event.repository.default_branch }}
REF_NAME: ${{ github.ref_name }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # pinact
run: |
# PRs check changed files (--pr); schedule/dispatch default to --all (dispatch can pick --pr).
if [ "$EVENT" = pull_request ]; then
scope="pr"
else
scope="${SCOPE_INPUT:-all}"
fi
# hk's --pr resolves the base via origin/HEAD, which the Actions checkout never sets; point it
# at the PR base so hk diffs origin/<base>...HEAD (else it finds 0 files and passes green).
if [ "$scope" = pr ]; then
git remote set-head origin "$BASE_REF" >/dev/null 2>&1 || true
fi
mise run check "--$scope" || {
echo "::error::Lint & format failed on $REF_NAME — fix locally with 'mise run check --fix --$scope', then push. ('mise run setup' installs the pre-commit hook so this happens on every commit.)"
exit 1
}
# One sticky PR comment pointing at `--fix`: post/update it when the check fails,
# delete it once the check passes. No inline suggestions — the author runs the fixer.
- name: Comment lint status
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script:
| # zizmor: ignore[template-injection] -- steps.check.outcome is a trusted success/failure value
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const MARKER = '<!-- mise-lint -->';
const BOT = 'github-actions[bot]';
const failed = '${{ steps.check.outcome }}' === 'failure';
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number });
const existing = comments.find((c) => c.user.login === BOT && (c.body || '').includes(MARKER));
if (failed) {
const body = [
MARKER,
'🚨 Lint & format failed. Fix it locally, then push:',
'',
'```bash',
'mise run check --fix --pr',
'```',
'',
'Run `mise run setup` once so the pre-commit hook does this on every commit.',
].join('\n');
if (existing) await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
else await github.rest.issues.createComment({ owner, repo, issue_number, body });
} else if (existing) {
await github.rest.issues.deleteComment({ owner, repo, comment_id: existing.id });
}