diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7add053 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +max_line_length = 100 +indent_size = 2 + +[*.{sh,py}] +max_line_length = 120 + +[*.py] +indent_size = 4 + +[{CLAUDE.md,.editorconfig,super-linter.env,lychee.toml,renovate.json5,default.json,mise.toml}] +max_line_length = 300 diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 5496477..90918ca 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,10 +1,6 @@ { $schema: "https://docs.renovatebot.com/renovate-schema.json", - extends: [ - "config:recommended", - "customManagers:dockerfileVersions", - "customManagers:githubActionsVersions", - ], + extends: ["config:recommended", "customManagers:dockerfileVersions", "customManagers:githubActionsVersions"], branchPrefix: "grafanarenovatebot/", dependencyDashboard: true, platformCommit: "enabled", @@ -24,9 +20,7 @@ customType: "regex", description: "Update _VERSION variables in mise.toml", managerFilePatterns: ["/^mise\\.toml$/"], - matchStrings: [ - '# renovate: datasource=(?[a-z-]+?)(?: depName=(?.+?))?(?: packageName=(?.+?))?(?: versioning=(?[a-z-]+?))?\\s.+?_VERSION="?(?[^@"]+?)(?:@(?sha256:[a-f0-9]+))?"?\\s', - ], + matchStrings: ['# renovate: datasource=(?[a-z-]+?)(?: depName=(?.+?))?(?: packageName=(?.+?))?(?: versioning=(?[a-z-]+?))?\\s.+?_VERSION="?(?[^@"]+?)(?:@(?sha256:[a-f0-9]+))?"?\\s'], }, ], } diff --git a/CLAUDE.md b/CLAUDE.md index 7970789..f792d22 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,11 @@ All task scripts follow these conventions: - **Metadata**: Shell scripts use `#MISE` comments for metadata; Python scripts use `# [MISE]` comments - **Usage args**: Shell scripts use `#USAGE` comments to define CLI arguments that mise parses - **Exit behavior**: Scripts exit with non-zero on errors for CI integration -- **AUTOFIX mode**: All lint scripts check the `AUTOFIX` environment variable. When `AUTOFIX=true`, linters that support fixing issues will automatically apply fixes; linters without fix capabilities silently ignore it. This allows consuming repos to run all lints with `AUTOFIX=true` via a single task (e.g., `mise run fix`) without needing per-linter configuration +- **AUTOFIX mode**: All lint scripts check the `AUTOFIX` environment variable. + When `AUTOFIX=true`, linters that support fixing issues will automatically + apply fixes; linters without fix capabilities silently ignore it. This allows + consuming repos to run all lints with `AUTOFIX=true` via a single task + (e.g., `mise run fix`) without needing per-linter configuration ### Script Categories diff --git a/README.md b/README.md index 6cc5ca9..72afb98 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +

flint logo

@@ -8,8 +9,11 @@ Lint GitHub Release

+ -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. +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:** @@ -19,11 +23,53 @@ A toolbox of reusable [mise](https://mise.jdx.dev/) lint task scripts. Pick the | `lint:links` | [lychee](https://lychee.cli.rs/) | | `lint:renovate-deps` | [Renovate](https://docs.renovatebot.com/) dependency tracking | +## How it works + +Flint relies on two tools that each play a distinct role: + +### mise — the task runner + +[mise](https://mise.jdx.dev/) is a polyglot dev tool manager and task +runner. In the context of flint, mise serves two purposes: + +1. **Installing tools.** mise's `[tools]` section pins exact versions + of the linters each task needs (e.g., `lychee`, `node`, + `"npm:renovate"`). Running `mise install` gives every developer and + CI runner the same versions, so local runs are consistent with CI. + +2. **Running tasks.** mise downloads task scripts from this repository + via HTTP, wires them into your project as local commands + (`mise run lint`, `mise run fix`), and passes flags and environment + variables through to each script. You don't need to clone flint — + mise fetches the scripts directly from GitHub URLs pinned in your + `mise.toml`. + +### Renovate — the dependency updater + +[Renovate](https://docs.renovatebot.com/) is an automated dependency update bot. +Extending the flint [Renovate preset](#automatic-version-updates-with-renovate) +(`default.json`) is essential for any repository that uses flint — without it, +SHA-pinned flint URLs and `_VERSION` variables in `mise.toml` would never get +updated. The preset ships custom managers that detect these patterns and open +PRs to bump both flint itself and the tools it runs +(e.g., Super-Linter, lychee). + +Optionally, the [`lint:renovate-deps`](#lintrenovate-deps) task adds a second +layer: it runs Renovate locally to detect which dependencies Renovate is +tracking, compares this against a committed snapshot, and fails if they +diverge — catching cases where a dependency silently falls off Renovate's +radar. + ## Usage -⚠️ **Important**: Always pin to a specific version, never use `main`. The main branch may contain breaking changes. See [CHANGELOG.md](CHANGELOG.md) for version history. +⚠️ **Important**: Always pin to a specific version, never use `main`. +The main branch may contain breaking changes. +See [CHANGELOG.md](CHANGELOG.md) for version history. -Add whichever tasks you need as HTTP remote tasks in your `mise.toml`, pinned to the commit SHA of a release tag with a version comment: +Add whichever tasks you need as HTTP remote tasks in your `mise.toml`, +pinned to the commit SHA of a release tag with a version comment: + + ```toml # Pick the tasks you need from flint (https://github.com/grafana/flint) @@ -38,7 +84,11 @@ description = "Verify renovate-tracked-deps.json is up to date" file = "https://raw.githubusercontent.com/grafana/flint/30090d5540807f330a94420ad11b57ba93eaaa84/tasks/lint/renovate-deps.py" # v0.3.0 ``` -The SHA pin ensures the URL is immutable (tag-based URLs can change if a tag is force-pushed), and the `# v0.3.0` comment tells Renovate which version is currently pinned. + + +The SHA pin ensures the URL is immutable (tag-based URLs can change +if a tag is force-pushed), and the `# v0.3.0` comment tells Renovate +which version is currently pinned. Then wire up top-level `lint` and `fix` tasks that reference whichever tasks you adopted (add any project-specific subtasks to the `depends` list): @@ -55,13 +105,30 @@ run = "AUTOFIX=true mise run lint" ## Example -See [grafana/docker-otel-lgtm](https://github.com/grafana/docker-otel-lgtm) for a real-world example of a repository using flint. Its [CONTRIBUTING.md](https://github.com/grafana/docker-otel-lgtm/blob/main/CONTRIBUTING.md) describes the developer workflow, and its [mise.toml](https://github.com/grafana/docker-otel-lgtm/blob/main/mise.toml) shows how the tasks are wired up. +See [grafana/docker-otel-lgtm][example-repo] for a real-world example +of a repository using flint. Its [CONTRIBUTING.md][example-contributing] +describes the developer workflow, and its [mise.toml][example-mise] +shows how the tasks are wired up. + +[example-repo]: https://github.com/grafana/docker-otel-lgtm +[example-contributing]: https://github.com/grafana/docker-otel-lgtm/blob/main/CONTRIBUTING.md +[example-mise]: https://github.com/grafana/docker-otel-lgtm/blob/main/mise.toml ## Tasks ### `lint:super-linter` -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. +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. + +**mise** fetches this script from the SHA-pinned URL in `mise.toml` +and runs it as `mise run lint:super-linter`. The +`SUPER_LINTER_VERSION` environment variable (set in `mise.toml`) +controls which Super-Linter image is pulled. **Renovate**, via the +flint preset, opens PRs to bump both the flint script URL and the +`SUPER_LINTER_VERSION` value when new versions are available. **Flags:** @@ -69,21 +136,38 @@ Runs [Super-Linter](https://github.com/super-linter/super-linter) via Docker or | ----------- | ------------------------------------------------------------ | | `--autofix` | Enable autofix mode (enables `FIX_*` vars from the env file) | -When autofix is not enabled, all `FIX_*` lines are filtered out of the env file before running Super-Linter. +When autofix is not enabled, all `FIX_*` lines are filtered out of +the env file before running Super-Linter. **Environment variables:** + + | 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 | + + ### `lint:links` -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. +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. + +**mise** fetches this script and runs it as `mise run lint:links`. +Lychee is installed via mise's `[tools]` section — add +`lychee = ""` to your `mise.toml`. **Renovate**, via the +flint preset, opens PRs to bump the flint script URL when a new +version is available. **Flags:** + + | Flag | Description | | ---------------------- | ------------------------------------------------------------------------------------ | | `--full` | Check all links (local + remote) in all files (single run) | @@ -92,25 +176,42 @@ Checks links with [lychee](https://lychee.cli.rs/). By default it runs two check | `--lychee-args ` | Extra arguments to pass to lychee | | `...` | 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. + + +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 | + + **Examples:** ```bash -mise run lint:links # All links in modified files + local links in all files (default) +mise run lint:links # All links in modified + local links in all files (default) mise run lint:links --full # All links in all files ``` ### `lint:renovate-deps` -Verifies `.github/renovate-tracked-deps.json` is up to date by running Renovate locally and parsing its debug logs. +Verifies `.github/renovate-tracked-deps.json` is up to date by +running Renovate locally and parsing its debug logs. + +**mise** fetches this script and runs it as `mise run lint:renovate-deps`. +The Renovate CLI is installed via mise's `[tools]` section — add +`node = ""` and `"npm:renovate" = ""` to your +`mise.toml`. **Renovate** plays a dual role here: the flint preset +keeps the script URL up to date, while the script itself runs Renovate +locally in `--platform=local` mode to discover which dependencies +Renovate is tracking and compares them against a committed snapshot. **Flags:** @@ -120,35 +221,65 @@ Verifies `.github/renovate-tracked-deps.json` is up to date by running Renovate **Environment variables:** + + | 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. +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. +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 the linter 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`: +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` flag (or `AUTOFIX=true` env var) → automatically regenerates and updates the committed file +- If they differ → linter fails with a unified diff showing which + dependencies were added or removed +- With `--autofix` flag (or `AUTOFIX=true` env var) → automatically + regenerates and updates the committed file #### 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 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. +- **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. +- **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. +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): @@ -167,14 +298,17 @@ 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. +Linters that don't support autofix (like lychee link checker) +silently ignore the `AUTOFIX` environment variable. ## Automatic version updates with Renovate Flint provides a [Renovate shareable preset](https://docs.renovatebot.com/config-presets/) with custom managers that automatically update: -- **SHA-pinned flint versions** in `mise.toml` (`raw.githubusercontent.com` URLs with commit SHA and version comment) +- **SHA-pinned flint versions** in `mise.toml` + (`raw.githubusercontent.com` URLs with commit SHA and version + comment) - **`_VERSION` variables** in `mise.toml` (e.g., `SUPER_LINTER_VERSION`) Add this to your `renovate.json5`: @@ -187,20 +321,40 @@ Add this to your `renovate.json5`: ## Per-repo configuration -Each task expects certain config files that your repository must provide. You only need the files for the tasks you adopt: - -- **`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`) +Each task expects certain config files that your repository must +provide. You only need the files for the tasks you adopt: + +- **`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 -This project uses [Semantic Versioning](https://semver.org/). Breaking changes will be documented in [CHANGELOG.md](CHANGELOG.md) and will result in a major version bump. +This project uses [Semantic Versioning](https://semver.org/). +Breaking changes will be documented in [CHANGELOG.md](CHANGELOG.md) +and will result in a major version bump. -**Always pin to a specific commit SHA** in your `mise.toml` file URLs with a version comment (e.g., `# v0.3.0`). Never reference `main` directly as it may contain unreleased breaking changes. To find the commit SHA for a release tag, run `git rev-parse v0.3.0`. +**Always pin to a specific commit SHA** in your `mise.toml` file +URLs with a version comment (e.g., `# v0.3.0`). Never reference +`main` directly as it may contain unreleased breaking changes. To +find the commit SHA for a release tag, run +`git rev-parse v0.3.0`. ## Releasing -Releases are automated via [Release Please](https://github.com/googleapis/release-please). When conventional commits land on `main`, Release Please opens (or updates) a release PR with a changelog. +Releases are automated via +[Release Please](https://github.com/googleapis/release-please). +When conventional commits land on `main`, Release Please opens +(or updates) a release PR with a changelog. -> **Note:** CI checks don't trigger automatically on release-please PRs because they are created with `GITHUB_TOKEN`. To run CI, either click **Update branch** or **close and reopen** the PR. +> **Note:** CI checks don't trigger automatically on release-please +> PRs because they are created with `GITHUB_TOKEN`. To run CI, +> either click **Update branch** or **close and reopen** the PR. diff --git a/tasks/lint/links.sh b/tasks/lint/links.sh index df46c57..a7790d5 100755 --- a/tasks/lint/links.sh +++ b/tasks/lint/links.sh @@ -96,7 +96,8 @@ is_config_modified() { # Pattern for detecting config changes that should trigger a full lint. # Consuming repos can override this via LYCHEE_CONFIG_CHANGE_PATTERN. - local config_change_pattern="${LYCHEE_CONFIG_CHANGE_PATTERN:-^(\.github/config/lychee\.toml|\.mise/tasks/lint/.*|mise\.toml)$}" + local default_pattern='^(\.github/config/lychee\.toml|\.mise/tasks/lint/.*|mise\.toml)$' + local config_change_pattern="${LYCHEE_CONFIG_CHANGE_PATTERN:-$default_pattern}" local config_modified # shellcheck disable=SC2086 # intentional: head may expand to empty diff --git a/tasks/lint/renovate-deps.py b/tasks/lint/renovate-deps.py index 0dcc65f..eacc028 100755 --- a/tasks/lint/renovate-deps.py +++ b/tasks/lint/renovate-deps.py @@ -23,7 +23,8 @@ REPO_ROOT = Path(_repo_root_env) COMMITTED = REPO_ROOT / ".github" / "renovate-tracked-deps.json" -EXCLUDED_MANAGERS = {m.strip() for m in os.environ.get("RENOVATE_TRACKED_DEPS_EXCLUDE", "").split(",") if m.strip()} # pylint: disable=line-too-long # noqa: E501 +_exclude_env = os.environ.get("RENOVATE_TRACKED_DEPS_EXCLUDE", "") +EXCLUDED_MANAGERS = {m.strip() for m in _exclude_env.split(",") if m.strip()} def run_renovate(tmpdir):