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
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0 # needed for merge-base used in lint:links-in-modified-files
fetch-depth: 0 # needed for merge-base used in lint:links

- name: Setup mise
uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
Expand Down
1 change: 1 addition & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"MD041": false,
"MD060": false
}
15 changes: 7 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,18 @@ All task scripts follow these conventions:
**`tasks/lint/`** - Linting validators:

- `super-linter.sh`: Runs Super-Linter via Docker/Podman, auto-detects runtime, handles SELinux on Fedora
- `links.sh`, `local-links.sh`: Run lychee link checker with different scopes
- `links-in-modified-files.sh`: Smart link linting that checks config changes and only lints modified files
- `links.sh`: Runs lychee link checker with two default checks (all links in modified files + local links in all files) and a `--full` flag for comprehensive checking
- `renovate-deps.py`: Verifies `.github/renovate-tracked-deps.json` is up to date by running Renovate locally and parsing its debug logs. With `AUTOFIX=true`, automatically regenerates and updates the file

### Key Design Decisions

1. **Container runtime detection**: `super-linter.sh` tries podman first (with SELinux "z" mount flag), falls back to Docker
2. **AUTOFIX mode**: All lint scripts support the `AUTOFIX` environment variable for unified fix workflows:
- `super-linter.sh`: Filters out `FIX_*` env vars unless `AUTOFIX=true`, enabling Super-Linter's built-in fixers
- `renovate-deps.py`: Automatically regenerates and updates `.github/renovate-tracked-deps.json` when `AUTOFIX=true`
- Link linters (`links.sh`, `local-links.sh`, `links-in-modified-files.sh`): Silently ignore `AUTOFIX` (lychee has no autofix capability)
- Typical usage in consuming repos: `[tasks.fix]` with `run = "AUTOFIX=true mise run lint"` to fix all linters in one command
3. **Diff-based link checking**: `links-in-modified-files.sh` optimizes CI by only checking modified files, unless config changed
2. **AUTOFIX mode**: Lint scripts that support fixing accept `--autofix` flag and `AUTOFIX` env var for unified fix workflows:
- `super-linter.sh`: Filters out `FIX_*` env vars unless autofix is enabled
- `renovate-deps.py`: Automatically regenerates and updates `.github/renovate-tracked-deps.json` when autofix is enabled
- `links.sh`: Silently ignores the `AUTOFIX` env var (lychee has no autofix capability; no `--autofix` flag is exposed)
- The `AUTOFIX` env var is how the `fix` meta-task propagates autofix through the dependency chain
3. **Diff-based link checking**: `links.sh` runs two checks by default (all links in modified files + local links in all files), use `--full` to check all links in all files; falls back to `--full` when config changes
4. **Renovate exclusions**: `RENOVATE_TRACKED_DEPS_EXCLUDE` allows skipping managers like `github-actions,github-runners`
5. **Consuming repos provide config**: Scripts reference config files (`.github/config/super-linter.env`, `.github/config/lychee.toml`) that consuming repos must provide

Expand Down
172 changes: 105 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,125 +1,168 @@
# flint
<p align="center">
<img src=".idea/icon.svg" width="128" height="128" alt="flint logo">
</p>

Reusable mise lint task scripts for Super-Linter, lychee link checking, and Renovate dependency tracking.
<h1 align="center">flint</h1>

Shared [mise](https://mise.jdx.dev/) lint task scripts for
[Super-Linter](https://github.com/super-linter/super-linter),
[lychee](https://lychee.cli.rs/), and
[Renovate tracked-deps](https://docs.renovatebot.com/) verification.
<p align="center">
<a href="https://github.com/grafana/flint/actions/workflows/lint.yml"><img src="https://github.com/grafana/flint/actions/workflows/lint.yml/badge.svg" alt="Lint"></a>
<a href="https://github.com/grafana/flint/releases"><img src="https://img.shields.io/github/v/release/grafana/flint" alt="GitHub Release"></a>
</p>

A toolbox of reusable [mise](https://mise.jdx.dev/) lint task scripts. Pick the ones you need — each task is independent and can be adopted on its own.

**Available tasks:**

| Task | Tool |
| -------------------- | ------------------------------------------------------------- |
| `lint:super-linter` | [Super-Linter](https://github.com/super-linter/super-linter) |
| `lint:links` | [lychee](https://lychee.cli.rs/) |
| `lint:renovate-deps` | [Renovate](https://docs.renovatebot.com/) dependency tracking |

## Usage

⚠️ **Important**: Always pin to a specific version tag (e.g., `v0.1.0`), never use `main`. The main branch may contain breaking changes. See [CHANGELOG.md](CHANGELOG.md) for version history.

Reference individual task scripts via HTTP remote tasks in your `mise.toml`:
Add whichever tasks you need as HTTP remote tasks in your `mise.toml`:

```toml
# Shared lint tasks from flint
# Pick the tasks you need from flint
[tasks."lint:super-linter"]
description = "Run Super-Linter on the repository"
file = "https://raw.githubusercontent.com/grafana/flint/v0.1.0/tasks/lint/super-linter.sh"
[tasks."lint:links"]
description = "Lint links in all files"
description = "Check for broken links in changed files + all local links"
file = "https://raw.githubusercontent.com/grafana/flint/v0.1.0/tasks/lint/links.sh"
[tasks."lint:local-links"]
description = "Lint links in local files"
file = "https://raw.githubusercontent.com/grafana/flint/v0.1.0/tasks/lint/local-links.sh"
[tasks."lint:links-in-modified-files"]
description = "Lint links in modified files"
file = "https://raw.githubusercontent.com/grafana/flint/v0.1.0/tasks/lint/links-in-modified-files.sh"
[tasks."lint:renovate-deps"]
description = "Verify renovate-tracked-deps.json is up to date"
file = "https://raw.githubusercontent.com/grafana/flint/v0.1.0/tasks/lint/renovate-deps.py"
```

Then wire up the top-level `lint` and `fix` tasks (add any project-specific
subtasks to the `depends` list):
Then wire up top-level `lint` and `fix` tasks that reference whichever tasks
you adopted (add any project-specific subtasks to the `depends` list):

```toml
[tasks.lint]
description = "Run all lints"
depends = ["lint:super-linter", "lint:local-links", "lint:links-in-modified-files", "lint:renovate-deps"]
depends = ["lint:super-linter", "lint:links", "lint:renovate-deps"]

[tasks.fix]
description = "Auto-fix lint issues and regenerate tracked deps"
run = "AUTOFIX=true mise run lint"
```

## Required environment variables
## Tasks

Set these in your `mise.toml`:
### `lint:super-linter`

| Variable | Required | Description |
| ---------------------- | -------- | ------------------------------------------------- |
| `SUPER_LINTER_VERSION` | yes | Super-Linter image tag (e.g. `v8.4.0@sha256:...`) |
Runs [Super-Linter](https://github.com/super-linter/super-linter) via Docker or Podman. Auto-detects the container runtime (prefers Podman, falls back to Docker) and handles SELinux bind-mount flags on Fedora.

## Optional environment variables
**Flags:**

| Variable | Default | Description |
| ------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `SUPER_LINTER_ENV_FILE` | `.github/config/super-linter.env` | Path to the Super-Linter env file |
| `LYCHEE_CONFIG` | `.github/config/lychee.toml` | Path to the lychee config file |
| `LYCHEE_CONFIG_CHANGE_PATTERN` | `^(\.github/config/lychee\.toml\|\.mise/tasks/lint/.*\|mise\.toml)$` | Regular expression for files whose change triggers a full link check |
| `AUTOFIX` | unset | Set to `true` to enable autofix mode (Super-Linter fixes, renovate-deps regeneration) |
| `RENOVATE_TRACKED_DEPS_EXCLUDE` | unset | Comma-separated Renovate managers to exclude (e.g. `github-actions,github-runners`) |
| Flag | Description |
| ----------- | ------------------------------------------------------------ |
| `--autofix` | Enable autofix mode (enables `FIX_*` vars from the env file) |

## Provided tasks
When autofix is not enabled, all `FIX_*` lines are filtered out of the env file before running Super-Linter.

| Task | Description | AUTOFIX Support |
| ------------------------------ | ------------------------------------------------- | ----------------------- |
| `lint:super-linter` | Run Super-Linter via Docker/Podman | ✅ Enables `FIX_*` vars |
| `lint:links` | Check links in all files with lychee | ❌ Ignored |
| `lint:local-links` | Check local file links with lychee | ❌ Ignored |
| `lint:links-in-modified-files` | Check links only in files modified vs base branch | ❌ Ignored |
| `lint:renovate-deps` | Verify `renovate-tracked-deps.json` is up to date | ✅ Regenerates file |
**Environment variables:**

## How AUTOFIX Works
| Variable | Default | Required | Description |
| ----------------------- | --------------------------------- | -------- | ------------------------------------------------- |
| `SUPER_LINTER_VERSION` | — | yes | Super-Linter image tag (e.g. `v8.4.0@sha256:...`) |
| `SUPER_LINTER_ENV_FILE` | `.github/config/super-linter.env` | no | Path to the Super-Linter env file |

All lint scripts support the `AUTOFIX` environment variable for a unified fix workflow:
### `lint:links`

**Check mode** (default):
Checks links with [lychee](https://lychee.cli.rs/). By default it runs two checks: **all links (local + remote) in modified files** and **local file links in all files**. This keeps CI fast while catching both broken remote links in changed content and broken internal links across the whole repository.

```bash
mise run lint # Check all linters, fail on issues
mise run lint:super-linter # Check code style, fail on issues
mise run lint:renovate-deps # Verify tracked deps, fail if out of date
```
**Flags:**

| Flag | Description |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `--full` | Check all links (local + remote) in all files (single run) |
| `--base <ref>` | Base branch to compare against (default: `origin/$GITHUB_BASE_REF` or `origin/main`) |
| `--head <ref>` | Head commit to compare against (default: `$GITHUB_HEAD_SHA` or `HEAD`) |
| `--lychee-args <args>` | Extra arguments to pass to lychee |
| `<file>...` | Files to check (default: `.`; only used with `--full`) |

When running in default mode, if a config change is detected (matching `LYCHEE_CONFIG_CHANGE_PATTERN`), the script falls back to `--full` behavior.

**Environment variables:**

| Variable | Default | Description |
| ------------------------------ | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `LYCHEE_CONFIG` | `.github/config/lychee.toml` | Path to the lychee config file |
| `LYCHEE_CONFIG_CHANGE_PATTERN` | `^(\.github/config/lychee\.toml\|\.mise/tasks/lint/.*\|mise\.toml)$` | Regular expression for files whose change triggers a full link check |

**Fix mode** (`AUTOFIX=true`):
**Examples:**

```bash
mise run fix # Auto-fix all fixable issues
# Or run individual linters:
AUTOFIX=true mise run lint:super-linter # Apply code fixes
AUTOFIX=true mise run lint:renovate-deps # Regenerate tracked deps
mise run lint:links # All links in modified files + local links in all files (default)
mise run lint:links --full # All links in all files
```

Linters that don't support autofix (like lychee link checker) silently ignore the `AUTOFIX` variable and run normally. This allows you to run all lints with `AUTOFIX=true` without errors.
### `lint:renovate-deps`

Verifies `.github/renovate-tracked-deps.json` is up to date by running Renovate locally and parsing its debug logs.

**Flags:**

| Flag | Description |
| ----------- | ------------------------------------------------------ |
| `--autofix` | Automatically regenerate and update the committed file |

## Renovate Tracked Deps Linter
**Environment variables:**

### Why this exists
| Variable | Default | Description |
| ------------------------------- | ------- | ----------------------------------------------------------------------------------- |
| `RENOVATE_TRACKED_DEPS_EXCLUDE` | unset | Comma-separated Renovate managers to exclude (e.g. `github-actions,github-runners`) |

#### Why this exists

Renovate silently stops tracking a dependency when it can no longer parse the version reference (typo in a comment annotation, unsupported syntax, moved file, etc.). When that happens, the dependency freezes in place with no PR and no dashboard entry — it simply disappears from Renovate's radar.

The Dependency Dashboard catches _known_ dependencies that are pending or in error, but it cannot show you a dependency that Renovate no longer sees at all. This linter closes that gap by keeping a committed snapshot of every dependency Renovate tracks and failing CI when the two diverge.

### How it works
#### How it works

The `lint:renovate-deps` task runs Renovate locally in `--platform=local` mode, parses its debug log for the `packageFiles with updates` message, and generates a dependency list (grouped by file and manager). It then diffs this against the committed `.github/renovate-tracked-deps.json`:

- If they match → linter passes
- If they differ → linter fails with a unified diff showing which dependencies were added or removed
- With `AUTOFIX=true` → automatically regenerates and updates the committed file

### Typical workflow
#### Typical workflow

- **A dependency disappears** (e.g., someone removes a `# renovate:` comment or changes a file that Renovate was matching) → CI fails, showing the removed dependency in the diff. The author can then decide whether the removal was intentional or accidental.

- **A new dependency is added** → CI fails because the committed snapshot is stale. Run `mise run fix` (or `AUTOFIX=true mise run lint:renovate-deps`) to regenerate and update the file, then commit.

- **Routine regeneration** → After any change to `renovate.json5`, Dockerfiles, `go.mod`, `package.json`, or other files Renovate scans, the linter will detect the change and require regeneration.

## How AUTOFIX Works

Lint scripts that support fixing accept an `--autofix` flag. Autofix can also be enabled via the `AUTOFIX=true` environment variable, which is how the `fix` meta-task propagates it through the dependency chain.

**Check mode** (default):

```bash
mise run lint # Check all linters, fail on issues
mise run lint:super-linter # Check code style, fail on issues
mise run lint:renovate-deps # Verify tracked deps, fail if out of date
```

**Fix mode:**

```bash
mise run fix # Auto-fix all fixable issues
# Or run individual linters:
mise run lint:super-linter --autofix # Apply code fixes
mise run lint:renovate-deps --autofix # Regenerate tracked deps
```

Linters that don't support autofix (like lychee link checker) silently ignore the `AUTOFIX` environment variable.

## Automatic version updates with Renovate

To let Renovate automatically update the pinned flint version in your
Expand All @@ -144,18 +187,13 @@ To let Renovate automatically update the pinned flint version in your
This matches all `raw.githubusercontent.com` URLs in `mise.toml` and updates
the version tag (e.g., `v0.1.0`) when a new release is published.

## Per-repo configuration (not included)
## Per-repo configuration

Each consuming repository must provide its own:
Each task expects certain config files that your repository must provide. You only need the files for the tasks you adopt:

- **Super-Linter env file** (`.github/config/super-linter.env`) — which
validators to enable, which `FIX_*` vars to set
- **Linter config files** — `.golangci.yaml`, `.markdownlint.yaml`,
`.yaml-lint.yml`, `.editorconfig`, etc.
- **Lychee config** (`.github/config/lychee.toml`) — exclusions, timeouts,
remappings
- **Renovate config** (`.github/renovate.json5`) and committed snapshot
(`.github/renovate-tracked-deps.json`)
- **`lint:super-linter`** — Super-Linter env file (`.github/config/super-linter.env`) to select which validators to enable and which `FIX_*` vars to set, plus any linter config files (`.golangci.yaml`, `.markdownlint.yaml`, `.yaml-lint.yml`, `.editorconfig`, etc.)
- **`lint:links`** — Lychee config (`.github/config/lychee.toml`) for exclusions, timeouts, remappings
- **`lint:renovate-deps`** — Renovate config (`.github/renovate.json5`) and committed snapshot (`.github/renovate-tracked-deps.json`)

## Versioning

Expand Down
12 changes: 2 additions & 10 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,16 @@ description = "Run Super-Linter on the repository"
file = "tasks/lint/super-linter.sh"

[tasks."lint:links"]
description = "Lint links in all files"
description = "Check for broken links in changed files + all local links"
file = "tasks/lint/links.sh"

[tasks."lint:local-links"]
description = "Lint links in local files"
file = "tasks/lint/local-links.sh"

[tasks."lint:links-in-modified-files"]
description = "Lint links in modified files"
file = "tasks/lint/links-in-modified-files.sh"

[tasks."lint:renovate-deps"]
description = "Verify renovate-tracked-deps.json is up to date"
file = "tasks/lint/renovate-deps.py"

[tasks."lint"]
description = "Run all lints"
depends = ["lint:super-linter", "lint:local-links", "lint:links-in-modified-files", "lint:renovate-deps"]
depends = ["lint:super-linter", "lint:links", "lint:renovate-deps"]

[tasks.fix]
description = "Auto-fix lint issues and regenerate tracked deps"
Expand Down
Loading