diff --git a/.github/config/super-linter.env b/.github/config/super-linter.env index e9558bc..3f7678d 100644 --- a/.github/config/super-linter.env +++ b/.github/config/super-linter.env @@ -2,16 +2,22 @@ FILTER_REGEX_EXCLUDE=(.*renovate-tracked-deps\.json|CHANGELOG\.md) IGNORE_GITIGNORED_FILES=true LOG_LEVEL=ERROR -# Disable linters not relevant for this repository -VALIDATE_CHECKOV=false -VALIDATE_GIT_COMMITLINT=false -VALIDATE_JSCPD=false -# Use Biome instead of Prettier for JSON formatting (Biome uses tabs, Prettier uses spaces) -VALIDATE_JSON_PRETTIER=false -# Use Ruff instead of Black/Pylint/isort for Python -VALIDATE_PYTHON_BLACK=false -VALIDATE_PYTHON_ISORT=false -VALIDATE_PYTHON_PYLINT=false +# Allow-list: only enable linters relevant for this repository +VALIDATE_BASH=true +VALIDATE_BIOME_FORMAT=true +VALIDATE_EDITORCONFIG=true +VALIDATE_ENV=true +VALIDATE_GITHUB_ACTIONS=true +VALIDATE_JSONC=true +VALIDATE_MARKDOWN=true +VALIDATE_MARKDOWN_PRETTIER=true +VALIDATE_NATURAL_LANGUAGE=true +VALIDATE_PYTHON_ISORT=true +VALIDATE_PYTHON_RUFF=true +VALIDATE_PYTHON_RUFF_FORMAT=true +VALIDATE_SHELL_SHFMT=true +VALIDATE_SPELL_CODESPELL=true +VALIDATE_YAML_PRETTIER=true # Enable auto-fix for relevant linters FIX_BIOME_FORMAT=true diff --git a/.gitignore b/.gitignore index 485dee6..1933a30 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea +.mise.super-linter-*.toml diff --git a/AGENTS.md b/AGENTS.md index d7966c0..a5da465 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,12 @@ 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 + auto-detects runtime, handles SELinux on Fedora. + `--native` flag runs a **subset** of linters directly + on the host for fast local feedback (not a full + replacement for the container — CI uses the full set). + `--full` flag lints all files instead of only changed + files (applies to both native and container modes) - `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 @@ -51,7 +56,9 @@ All task scripts follow these conventions: 1. **Container runtime detection**: `super-linter.sh` tries podman first (with SELinux "z" mount flag), - falls back to Docker + falls back to Docker. With `--native`, the container + runtime is bypassed entirely and linters run directly + on the host 2. **AUTOFIX mode**: Lint scripts that support fixing accept `--autofix` flag and `AUTOFIX` env var for unified fix workflows: @@ -101,14 +108,55 @@ This ensures all files pass CI linting (Biome formatting, shellcheck, etc.). Review the auto-fixed files before committing — auto-fixes may produce unexpected results. +A pre-commit hook can automate this — run +`mise run setup:pre-commit-hook` once per clone to install +it. The hook runs native linters with autofix on every +commit. + ```bash # Auto-fix and verify (recommended dev workflow) mise run fix # Verify only (same command used in CI) mise run lint + +# Install pre-commit hook (one-time setup) +mise run setup:pre-commit-hook ``` +## Native Mode Tips + +For faster native linting, consider switching +`super-linter.env` from a deny-list +(`VALIDATE_X=false` for each unwanted linter) to an +allow-list (only `VALIDATE_X=true` for linters you +want). Super-linter's logic — and native mode — treats +any explicit `VALIDATE_*=true` as "only run these". +This avoids noise from linters like `golangci-lint` +running on non-Go repos. + +After updating the super-linter version in `mise.toml`, +run `mise run setup:native-lint-tools` on the host to +install matching tool versions. Tools not installed are +skipped with a warning in native mode. + +**Config files:** Native mode requires linter configs at +standard locations (project root), not in +`.github/linters/` (super-linter's convention). The +script errors if `.github/linters/` exists. All +supported linters auto-discover their config: +`shellcheck`→`.shellcheckrc`, +`markdownlint`→`.markdownlint.json`, +`editorconfig-checker`→`.ecrc`, +`actionlint`→`.github/actionlint.yml`, +`hadolint`→`.hadolint.yaml`, +`golangci-lint`→`.golangci.yml`, +`ruff`→`ruff.toml`/`pyproject.toml`, +`codespell`→`.codespellrc`/`pyproject.toml`, +`biome`→`biome.json`, +`prettier`→`.prettierrc`, +`shfmt`→`.editorconfig`. + ## Adding New Linters When adding new lint scripts, follow these patterns: diff --git a/README.md b/README.md index b8e4c27..e1a28df 100644 --- a/README.md +++ b/README.md @@ -157,10 +157,45 @@ Docker versioning). | Flag | Description | | ----------- | ------------------------------------------------------------ | | `--autofix` | Enable autofix mode (enables `FIX_*` vars from the env file) | +| `--native` | Run linters natively instead of via container | +| `--full` | Lint all files instead of only changed files | When autofix is not enabled, all `FIX_*` lines are filtered out of the env file before running Super-Linter. +**Native mode:** + +The `--native` flag runs a **subset** of linters directly on +the host for fast local feedback. It is not a full replacement +for the Super-Linter container — CI should always use the +container for comprehensive checks. + +Native mode reads the same `super-linter.env` file and follows +Super-Linter's default logic for determining which linters are +enabled: if any `VALIDATE_*=true` is set, only those linters run; +otherwise all linters run unless explicitly `VALIDATE_*=false`. +`FILTER_REGEX_EXCLUDE` is respected. `FIX_*` variables are honored +when `--autofix` is also set. + +Supported native linters (subset of super-linter): + +- `actionlint` +- `biome` +- `codespell` +- `editorconfig-checker` +- `golangci-lint` +- `hadolint` +- `markdownlint` +- `prettier` +- `ruff` +- `shellcheck` +- `shfmt` + +Tools must be installed separately (e.g., via +`mise run setup:native-lint-tools`). Missing tools and unsupported +`VALIDATE_*` flags are skipped with a warning. Linter configs must +be at standard project-root locations (not `.github/linters/`). + **Environment variables:** @@ -396,6 +431,35 @@ 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. +## Pre-commit hook + +Flint provides a `pre-commit` task that runs native linters with +autofix on every commit — fast feedback without the container +overhead. To set it up: + +```bash +mise run setup:pre-commit-hook +``` + +This generates a `.git/hooks/pre-commit` that runs +`mise run pre-commit`, which uses `--native --autofix` to fix +formatting issues before the commit completes. + +**For consuming repos**, add these tasks to your `mise.toml`: + +```toml +[tasks.pre-commit] +description = "Pre-commit hook: native lint with autofix" +depends = ["setup:native-lint-tools"] +run = "mise run lint:super-linter -- --native --autofix" + +[tasks."setup:pre-commit-hook"] +description = "Install git pre-commit hook" +run = "mise generate git-pre-commit --write --task=pre-commit" +``` + +Then run `mise run setup:pre-commit-hook` once per clone. + ## Automatic version updates with Renovate Flint provides a [Renovate shareable preset](https://docs.renovatebot.com/config-presets/) @@ -448,13 +512,6 @@ find the commit SHA for a release tag, run ## 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. - -> **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. +See [RELEASING.md](RELEASING.md). [stf]: https://developer.mozilla.org/en-US/docs/Web/URI/Fragment/Text_fragments diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..7258123 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,25 @@ +# 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. + +> **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. + +## Post-release: regenerate version mapping + +After merging a release that bumps `SUPER_LINTER_VERSION`, +regenerate the native lint tool version mapping: + +```bash +mise run setup:update-super-linter-versions +git add super-linter-versions/ +git commit -m "chore: regenerate super-linter version mapping" +``` + + diff --git a/mise.toml b/mise.toml index 255e80b..91207c3 100644 --- a/mise.toml +++ b/mise.toml @@ -13,6 +13,19 @@ SUPER_LINTER_VERSION="slim-v8.4.0@sha256:8421cd4687937ac32a829539d03de51a147c164 description = "Run Super-Linter on the repository" file = "tasks/lint/super-linter.sh" +[tasks."lint:super-linter-native"] +description = "Run linters natively (fast, for local dev)" +depends = ["setup:native-lint-tools"] +run = "mise run lint:super-linter -- --native" + +[tasks."setup:native-lint-tools"] +description = "Install native lint tools matching the pinned super-linter version" +file = "tasks/setup/native-lint-tools.sh" + +[tasks."setup:update-super-linter-versions"] +description = "Generate super-linter version mapping from the super-linter repo" +file = "tasks/setup/update-super-linter-versions.sh" + [tasks."lint:links"] description = "Check for broken links in changed files + all local links" file = "tasks/lint/links.sh" @@ -28,3 +41,12 @@ 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" + +[tasks.pre-commit] +description = "Pre-commit hook: native lint with autofix" +depends = ["setup:native-lint-tools"] +run = "mise run lint:super-linter -- --native --autofix" + +[tasks."setup:pre-commit-hook"] +description = "Install git pre-commit hook that runs native linting" +run = "mise generate git-pre-commit --write --task=pre-commit" diff --git a/super-linter-versions/v8.4.0.toml b/super-linter-versions/v8.4.0.toml new file mode 100644 index 0000000..da811e9 --- /dev/null +++ b/super-linter-versions/v8.4.0.toml @@ -0,0 +1,19 @@ +# Tool versions matching super-linter v8.4.0 +# Source: https://github.com/super-linter/super-linter/tree/v8.4.0 +# +# Auto-generated by tasks/setup/update-super-linter-versions.sh +# Dockerfile multi-stage builds pin exact versions for compiled tools. +# npm and python versions come from lock files in dependencies/. + +[tools] +shellcheck = "v0.11.0" +shfmt = "v3.12.0" +actionlint = "1.7.10" +hadolint = "v2.14.0" +"npm:markdownlint-cli" = "0.47.0" +"npm:prettier" = "3.8.1" +"npm:@biomejs/biome" = "2.3.13" +"pipx:ruff" = "0.14.14" +"pipx:codespell" = "2.4.1" +editorconfig-checker = "v3.6.0" +golangci-lint = "v2.8.0" diff --git a/tasks/lint/super-linter.sh b/tasks/lint/super-linter.sh index d93dbcc..0b58ef2 100755 --- a/tasks/lint/super-linter.sh +++ b/tasks/lint/super-linter.sh @@ -4,12 +4,20 @@ set -euo pipefail #USAGE flag "--autofix" help="Enable autofix mode (enables FIX_* vars from the env file)" +#USAGE flag "--native" help="Run linters natively instead of via container" +#USAGE flag "--full" help="Lint all files instead of only changed files" # shellcheck disable=SC2154 # usage_autofix is set by mise if [ "${usage_autofix:-}" = "true" ]; then AUTOFIX=true fi +# shellcheck disable=SC2154 # usage_native is set by mise +NATIVE="${usage_native:-false}" + +# shellcheck disable=SC2154 # usage_full is set by mise +LINT_ALL="${usage_full:-false}" + _LINTER_RAN=false _on_exit() { @@ -25,10 +33,12 @@ _on_exit() { } trap _on_exit EXIT -# check for required env vars, otherwise exit with error -if [ -z "${SUPER_LINTER_VERSION:-}" ]; then - echo "SUPER_LINTER_VERSION environment variable is not set. Exiting." - exit 1 +if [ "$NATIVE" != "true" ]; then + # check for required env vars, otherwise exit with error + if [ -z "${SUPER_LINTER_VERSION:-}" ]; then + echo "SUPER_LINTER_VERSION environment variable is not set. Exiting." + exit 1 + fi fi if [ -z "${MISE_PROJECT_ROOT:-}" ]; then @@ -38,6 +48,241 @@ fi cd "${MISE_PROJECT_ROOT}" +ENV_FILE="${SUPER_LINTER_ENV_FILE:-.github/config/super-linter.env}" + +# --- Native mode --- +if [ "$NATIVE" = "true" ]; then + # Native mode expects linter configs at the project root (standard tool locations). + # Super-linter's .github/linters/ convention is not supported. + LINTER_RULES_PATH="${LINTER_RULES_PATH:-.github/linters}" + if [ -d "$LINTER_RULES_PATH" ]; then + echo "Error: native mode does not support linter configs in ${LINTER_RULES_PATH}/." + echo "Move config files to the project root (standard tool locations) instead." + exit 1 + fi + + # Source env file to get VALIDATE_*, FIX_*, and FILTER_REGEX_EXCLUDE + declare -A _env_vars + while IFS='=' read -r key value; do + [[ -z "$key" || "$key" =~ ^[[:space:]]*# ]] && continue + # Trim whitespace + key="${key%"${key##*[![:space:]]}"}" + value="${value%"${value##*[![:space:]]}"}" + _env_vars["$key"]="$value" + done <"$ENV_FILE" + + FILTER_REGEX_EXCLUDE="${_env_vars[FILTER_REGEX_EXCLUDE]:-}" + + # Determine which linters are enabled using Super Linter's default logic: + # If ANY VALIDATE_*=true is explicitly set, only those run. + # Otherwise, all linters are enabled (unless explicitly VALIDATE_*=false). + _has_explicit_true=false + for key in "${!_env_vars[@]}"; do + if [[ "$key" == VALIDATE_* && "${_env_vars[$key]}" == "true" ]]; then + _has_explicit_true=true + break + fi + done + + _is_enabled() { + local flag="$1" + local val="${_env_vars[$flag]:-}" + if [ "$_has_explicit_true" = "true" ]; then + # Explicit mode: only VALIDATE_*=true are enabled + [[ "$val" == "true" ]] + else + # Default mode: all enabled unless explicitly false + [[ "$val" != "false" ]] + fi + } + + _filter_files() { + if [ -n "$FILTER_REGEX_EXCLUDE" ]; then + grep -vE "$FILTER_REGEX_EXCLUDE" || true + else + cat + fi + } + + # Compute merge base once for changed-file detection and golangci-lint diff mode + _BASE_BRANCH="${DEFAULT_BRANCH:-main}" + _MERGE_BASE=$(git merge-base "origin/${_BASE_BRANCH}" HEAD 2>/dev/null || echo "") + + _list_files() { + if [ "$LINT_ALL" = "true" ]; then + git ls-files + else + if [ -n "$_MERGE_BASE" ]; then + # Files changed in the PR + uncommitted changes + { + git diff --name-only --diff-filter=d "$_MERGE_BASE"...HEAD + git diff --name-only --diff-filter=d + } | sort -u + else + # No merge base found (e.g. shallow clone), fall back to all files + git ls-files + fi + fi + } + + # Cache the file list once (avoids re-running git commands per linter) + mapfile -t _CACHED_FILES < <(_list_files | _filter_files) + + _find_files() { + local -a patterns=("$@") + [ ${#_CACHED_FILES[@]} -eq 0 ] && return + for file in "${_CACHED_FILES[@]}"; do + for pattern in "${patterns[@]}"; do + # shellcheck disable=SC2254 # glob pattern matching is intentional + case "$file" in + $pattern) echo "$file" ;; + */$pattern) echo "$file" ;; + esac + done + done | sort -u + } + + # Linter definitions: validate_flag|tool_binary|check_cmd_template|fix_cmd_template|file_patterns + # {FILE} = per-file invocation (one run per file) + # {FILES} = all matching files passed as arguments in one invocation + # "SELF" = tool handles its own file discovery (no file args) + # Config files: linters use their standard config discovery from the project root. + declare -a LINTER_DEFS=( + "VALIDATE_BASH|shellcheck|shellcheck {FILE}||*.sh *.bash" + "VALIDATE_SHELL_SHFMT|shfmt|shfmt -d {FILE}|shfmt -w {FILE}|*.sh *.bash" + "VALIDATE_MARKDOWN|markdownlint|markdownlint {FILE}|markdownlint --fix {FILE}|*.md" + "VALIDATE_MARKDOWN_PRETTIER|prettier|prettier --check {FILE}|prettier --write {FILE}|*.md" + "VALIDATE_YAML_PRETTIER|prettier|prettier --check {FILE}|prettier --write {FILE}|*.yaml *.yml" + "VALIDATE_JSON_PRETTIER|prettier|prettier --check {FILE}|prettier --write {FILE}|*.json" + "VALIDATE_EDITORCONFIG|editorconfig-checker|editorconfig-checker {FILES}||*" + "VALIDATE_GITHUB_ACTIONS|actionlint|actionlint {FILE}||.github/workflows/*.yml .github/workflows/*.yaml" + "VALIDATE_DOCKERFILE_HADOLINT|hadolint|hadolint {FILE}||Dockerfile Dockerfile.* *.dockerfile" + "VALIDATE_GO_GOLANGCI_LINT|golangci-lint|golangci-lint run||SELF" + "VALIDATE_PYTHON_RUFF|ruff|ruff check {FILE}|ruff check --fix {FILE}|*.py" + "VALIDATE_PYTHON_RUFF_FORMAT|ruff|ruff format --check {FILE}|ruff format {FILE}|*.py" + "VALIDATE_SPELL_CODESPELL|codespell|codespell {FILES}|codespell --write-changes {FILES}|*" + "VALIDATE_JSONC|biome|biome check {FILE}|biome check --fix {FILE}|*.json *.jsonc *.js *.ts *.jsx *.tsx" + "VALIDATE_BIOME_FORMAT|biome|biome format {FILE}|biome format --write {FILE}|*.json *.jsonc *.js *.ts *.jsx *.tsx" + ) + + # Track which VALIDATE_* flags from env are not supported + declare -A _supported_flags + for def in "${LINTER_DEFS[@]}"; do + IFS='|' read -r flag _ _ _ _ <<<"$def" + _supported_flags["$flag"]=1 + done + + _unsupported=() + for key in "${!_env_vars[@]}"; do + if [[ "$key" == VALIDATE_* && "${_env_vars[$key]}" == "true" && -z "${_supported_flags[$key]:-}" ]]; then + _unsupported+=("$key") + fi + done + + if [ ${#_unsupported[@]} -gt 0 ]; then + printf '⚠️ Not supported in native mode: %s\n' "${_unsupported[*]}" + fi + + _LINTER_RAN=true + _failed=() + _skipped=() + + for def in "${LINTER_DEFS[@]}"; do + IFS='|' read -r flag tool check_cmd fix_cmd patterns <<<"$def" + + if ! _is_enabled "$flag"; then + continue + fi + + if ! command -v "$tool" >/dev/null 2>&1; then + _skipped+=("$flag") + continue + fi + + # Determine which command to run + if [ "${AUTOFIX:-}" = "true" ] && [ -n "$fix_cmd" ]; then + # Check if FIX_ counterpart is enabled + fix_flag="FIX_${flag#VALIDATE_}" + if [ "${_env_vars[$fix_flag]:-}" = "true" ]; then + cmd_template="$fix_cmd" + else + cmd_template="$check_cmd" + fi + else + cmd_template="$check_cmd" + fi + + linter_failed=false + + if [ "$patterns" = "SELF" ]; then + # Tool handles its own file discovery; add diff flags when not linting all files + if [ "$LINT_ALL" != "true" ] && [ -n "$_MERGE_BASE" ]; then + if [[ "$cmd_template" == golangci-lint* ]]; then + cmd_template+=" --new-from-rev=$_MERGE_BASE" + fi + fi + if ! eval "$cmd_template"; then + linter_failed=true + fi + else + # Find matching files + read -ra pattern_arr <<<"$patterns" + mapfile -t files < <(_find_files "${pattern_arr[@]}") + + # mapfile produces a single empty element when input is empty + if [ ${#files[@]} -eq 0 ] || [[ ${#files[@]} -eq 1 && -z "${files[0]}" ]]; then + continue + fi + + if [[ "$cmd_template" == *"{FILES}"* ]]; then + # Build quoted file list for single invocation + quoted_files="" + for file in "${files[@]}"; do + # shellcheck disable=SC2016 # single quotes are intentional to prevent expansion + quoted_files+=" '${file//\'/\'\\\'\'}'" + done + cmd="${cmd_template//\{FILES\}/$quoted_files}" + if ! eval "$cmd"; then + linter_failed=true + fi + else + # Per-file invocation + for file in "${files[@]}"; do + # shellcheck disable=SC2016 # single quotes are intentional to prevent expansion + quoted_file="'${file//\'/\'\\\'\'}'" + cmd="${cmd_template//\{FILE\}/$quoted_file}" + if ! eval "$cmd"; then + linter_failed=true + fi + done + fi + fi + + if [ "$linter_failed" = "true" ]; then + _failed+=("$flag") + fi + done + + if [ ${#_skipped[@]} -gt 0 ]; then + printf '⚠️ Skipped (tool not found): %s\n' "${_skipped[*]}" + fi + + if [ ${#_failed[@]} -gt 0 ]; then + printf '\n❌ Native lint failed: %s\n' "${_failed[*]}" + exit 1 + fi + + exit 0 +fi + +# --- Container mode --- +if [ "${AUTOFIX:-}" != "true" ]; then + # Filter out FIX_* and comment lines when not auto-fixing + _FILTERED_ENV_FILE=$(mktemp) + grep -v '^#' "$ENV_FILE" | grep -v '^FIX_' >"$_FILTERED_ENV_FILE" + ENV_FILE="$_FILTERED_ENV_FILE" +fi + if command -v podman >/dev/null 2>&1; then RUNTIME=podman # Fedora, by default, runs with SELinux on. We require the "z" option for bind mounts. @@ -52,20 +297,18 @@ else exit 1 fi -ENV_FILE="${SUPER_LINTER_ENV_FILE:-.github/config/super-linter.env}" -if [ "${AUTOFIX:-}" != "true" ]; then - # Filter out FIX_* and comment lines when not auto-fixing - _FILTERED_ENV_FILE=$(mktemp) - grep -v '^#' "$ENV_FILE" | grep -v '^FIX_' >"$_FILTERED_ENV_FILE" - ENV_FILE="$_FILTERED_ENV_FILE" -fi - $RUNTIME image pull -q --platform linux/amd64 "ghcr.io/super-linter/super-linter:${SUPER_LINTER_VERSION}" >/dev/null +VALIDATE_ALL_CODEBASE="false" +if [ "$LINT_ALL" = "true" ]; then + VALIDATE_ALL_CODEBASE="true" +fi + _LINTER_RAN=true $RUNTIME container run --rm --platform linux/amd64 \ -e RUN_LOCAL=true \ -e DEFAULT_BRANCH=main \ + -e VALIDATE_ALL_CODEBASE="$VALIDATE_ALL_CODEBASE" \ --env-file "$ENV_FILE" \ -v "$(pwd)":/tmp/lint:"${MOUNT_OPTS}" \ "ghcr.io/super-linter/super-linter:${SUPER_LINTER_VERSION}" diff --git a/tasks/setup/native-lint-tools.sh b/tasks/setup/native-lint-tools.sh new file mode 100755 index 0000000..576cd95 --- /dev/null +++ b/tasks/setup/native-lint-tools.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +#MISE description="Install native lint tools matching the pinned super-linter version" + +set -euo pipefail + +if [ -z "${MISE_PROJECT_ROOT:-}" ]; then + echo "MISE_PROJECT_ROOT environment variable is not set. Exiting." + exit 1 +fi + +cd "${MISE_PROJECT_ROOT}" + +if [ -z "${SUPER_LINTER_VERSION:-}" ]; then + echo "SUPER_LINTER_VERSION environment variable is not set. Exiting." + exit 1 +fi + +# Extract clean version (strip slim- prefix and @sha256 digest) +VERSION="${SUPER_LINTER_VERSION#slim-}" +VERSION="${VERSION%%@*}" + +FLINT_REPO="${FLINT_REPO:-grafana/flint}" + +# Derive FLINT_REF from the flint SHA pinned in mise.toml task URLs. +# Consuming repos pin flint tasks to a specific commit SHA, e.g.: +# file = "https://raw.githubusercontent.com/grafana/flint//tasks/..." +# This ensures the version mapping matches the flint version in use. +# Falls back to "main" for flint itself (where tasks are local file paths). +if [ -z "${FLINT_REF:-}" ]; then + FLINT_REF=$(grep -oP \ + "raw\\.githubusercontent\\.com/${FLINT_REPO}/\\K[a-f0-9]{40}" \ + mise.toml 2>/dev/null | head -1 || true) + FLINT_REF="${FLINT_REF:-main}" +fi + +ENV_NAME="super-linter-${VERSION}" +LOCAL_TOML=".mise.${ENV_NAME}.toml" + +if [ -f "$LOCAL_TOML" ]; then + echo "Native lint tools already set up for super-linter ${VERSION}" + mise install -E "$ENV_NAME" + exit 0 +fi + +# Clean up old versions +rm -f .mise.super-linter-*.toml + +VERSION_FILE="super-linter-versions/${VERSION}.toml" + +# Use local file if available (flint itself), otherwise fetch from GitHub +if [ -f "$VERSION_FILE" ]; then + cp "$VERSION_FILE" "$LOCAL_TOML" +else + echo "Fetching tool versions for super-linter ${VERSION}..." + REMOTE_URL="https://raw.githubusercontent.com/${FLINT_REPO}/${FLINT_REF}/${VERSION_FILE}" + + if ! curl -fsSL "$REMOTE_URL" -o "$LOCAL_TOML"; then + echo "Failed to fetch version mapping from ${REMOTE_URL}" + echo "No version mapping available for super-linter ${VERSION}." + rm -f "$LOCAL_TOML" + exit 1 + fi +fi + +echo "Installing native lint tools..." +mise trust "$LOCAL_TOML" +mise install -E "$ENV_NAME" + +echo "Native lint tools installed for super-linter ${VERSION}" diff --git a/tasks/setup/update-super-linter-versions.sh b/tasks/setup/update-super-linter-versions.sh new file mode 100755 index 0000000..ecf95ab --- /dev/null +++ b/tasks/setup/update-super-linter-versions.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +#MISE description="Generate super-linter version mapping from the super-linter repo" + +set -euo pipefail + +if [ -z "${MISE_PROJECT_ROOT:-}" ]; then + echo "MISE_PROJECT_ROOT environment variable is not set. Exiting." + exit 1 +fi + +cd "${MISE_PROJECT_ROOT}" + +if [ -z "${SUPER_LINTER_VERSION:-}" ]; then + echo "SUPER_LINTER_VERSION environment variable is not set. Exiting." + exit 1 +fi + +# Extract clean version (strip slim- prefix and @sha256 digest) +VERSION="${SUPER_LINTER_VERSION#slim-}" +VERSION="${VERSION%%@*}" + +REPO="super-linter/super-linter" +TAG="${VERSION}" +OUTPUT="super-linter-versions/${VERSION}.toml" + +echo "Extracting tool versions for super-linter ${TAG}..." + +# Fetch Dockerfile to extract versions from FROM lines +DOCKERFILE=$(gh api "repos/${REPO}/contents/Dockerfile?ref=${TAG}" -q '.content' | base64 -d) + +# Extract version from a FROM line like "FROM koalaman/shellcheck:v0.11.0 AS shellcheck" +_from_version() { + local image="$1" + echo "$DOCKERFILE" | grep -E "^FROM ${image}:" | sed "s|.*${image}:\(v\{0,1\}\)|\1|; s| .*||; s|-.*||" +} + +# Fetch a pip requirements file and extract the pinned version +_pip_version() { + local pkg="$1" + gh api "repos/${REPO}/contents/dependencies/python/${pkg}.txt?ref=${TAG}" \ + -q '.content' | base64 -d | sed "s/${pkg}==//" +} + +# Fetch package-lock.json once and extract npm package versions +_PACKAGE_LOCK="" +_npm_version() { + local pkg="$1" + if [ -z "$_PACKAGE_LOCK" ]; then + _PACKAGE_LOCK=$(gh api "repos/${REPO}/contents/dependencies/package-lock.json?ref=${TAG}" -q '.content' | base64 -d) + fi + echo "$_PACKAGE_LOCK" | + python3 -c "import json,sys; print(json.load(sys.stdin)['packages']['node_modules/${pkg}']['version'])" +} + +# Collect versions +shellcheck=$(_from_version "koalaman/shellcheck") +shfmt=$(_from_version "mvdan/shfmt") +actionlint=$(_from_version "rhysd/actionlint") +hadolint=$(_from_version "hadolint/hadolint") +editorconfig_checker=$(_from_version "mstruebing/editorconfig-checker") +golangci_lint=$(_from_version "golangci/golangci-lint") +markdownlint=$(_npm_version "markdownlint-cli") +prettier=$(_npm_version "prettier") +biome=$(_npm_version "@biomejs/biome") +ruff=$(_pip_version "ruff") +codespell=$(_pip_version "codespell") + +mkdir -p super-linter-versions + +cat >"$OUTPUT" <