🎨 Linters, Validators, and Formatters #43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 }); | |
| } |