Files
IronFox/scripts/lint.sh
Akash Yadav aeb8d3a5f0 feat: lint shell scripts with shellcheck + shfmt in CI and pre-commit
ironfox-oss/IronFox!162
Add a blocking `lint` CI stage (runs first, on a lightweight Alpine
image) plus a core.hooksPath pre-commit hook, with scripts/lint.sh as
the shared runner for CI, the hook, and manual use. Checks are
configured in .shellcheckrc and formatting in .editorconfig.

Reformat all scripts with shfmt (2-space indent, switch-case indent,
space redirects) via .editorconfig, so a bare `shfmt` is consistent
everywhere. bootstrap.sh installs shellcheck + shfmt per-OS and
enables the pre-commit hook.

The hook is a convenience (bypassable with --no-verify); the CI
`lint-scripts` job is the enforcement gate. SC2086/SC2046 are
disabled for now as tracked follow-up.

MR-author: Akash Yadav <itsaky01@gmail.com>
Co-authored-by: Akash Yadav <contact@itsaky.com>
Approved-by: celenity <celenity@celenity.dev>
Merged-by: celenity <celenity@celenity.dev>
2026-07-31 02:33:10 +00:00

81 lines
2.3 KiB
Bash
Executable File

#!/bin/bash
# Lints IronFox's shell scripts with shellcheck (static analysis) and shfmt
# (formatting). This is the single source of truth used by CI, the pre-commit
# hook, and manual runs.
#
# It is intentionally SELF-CONTAINED: it does NOT source env.sh (which performs
# full OS/path bootstrap), so it runs unchanged in a minimal CI container.
#
# Usage:
# scripts/lint.sh Lint all tracked shell scripts (CI + manual)
# scripts/lint.sh --staged Lint only staged shell scripts (pre-commit hook)
#
# Config lives in .shellcheckrc (checks) and .editorconfig (shfmt formatting),
# both read automatically from the repo root.
set -euo pipefail
# Resolve and move to the repo root so relative paths and config discovery
# (.shellcheckrc, .editorconfig) work regardless of the caller's cwd.
REPO_ROOT="$(git rev-parse --show-toplevel)"
cd "${REPO_ROOT}"
mode='all'
if [[ "${1:-}" == '--staged' ]]; then
mode='staged'
fi
# Collect target scripts. `git ls-files` naturally excludes generated,
# gitignored files (e.g. scripts/env_local.sh, scripts/env_build.sh).
declare -a targets=()
if [[ "${mode}" == 'staged' ]]; then
while IFS= read -r file; do
[[ -n "${file}" ]] && targets+=("${file}")
done < <(git diff --cached --name-only --diff-filter=ACM -- 'scripts/*.sh')
else
while IFS= read -r file; do
targets+=("${file}")
done < <(git ls-files 'scripts/*.sh')
fi
if [[ ${#targets[@]} -eq 0 ]]; then
echo 'lint: no shell scripts to check.'
exit 0
fi
# Ensure the tools are available before running.
missing=0
for tool in shellcheck shfmt; do
if ! command -v "${tool}" > /dev/null 2>&1; then
echo "lint: required tool '${tool}' is not installed or not in PATH." >&2
missing=1
fi
done
if [[ "${missing}" -ne 0 ]]; then
echo 'lint: install the missing tool(s) (e.g. run scripts/bootstrap.sh) and retry.' >&2
exit 127
fi
status=0
echo "lint: shellcheck (${#targets[@]} file(s))..."
if ! shellcheck -x "${targets[@]}"; then
status=1
fi
echo 'lint: shfmt formatting check...'
if ! shfmt -d "${targets[@]}"; then
echo >&2
echo "lint: formatting issues found above. Fix with:" >&2
echo " git ls-files 'scripts/*.sh' | xargs shfmt -w" >&2
status=1
fi
if [[ "${status}" -eq 0 ]]; then
echo 'lint: OK'
else
echo 'lint: FAILED' >&2
fi
exit "${status}"