#!/bin/sh # script/lint: run the linters, golangci-lint over the Go code and then # ESLint over static/js/. Neither is ever installed locally. # # golangci-lint runs via docker only, one way, everywhere — script/lint builds # Dockerfile.lint, which COPYs the repo into the pinned golangci-lint image # and lints as a build step. This works even when the docker daemon is remote # and bind mounts are impossible, and it removes the host linter's shared # cache, which has attributed other checkouts' findings to this one. # # --no-cache-filter=lint forces the lint stage to re-execute on every run; a # cached lint stage exits 0 in under a second having linted nothing. The deps # stage keeps its cache, so module downloads are not repeated. # --progress=plain keeps the linter's own output visible on success, so a # passing run shows the issue count rather than nothing. # --output=type=cacheonly leaves no image behind to clean up. # # docker silently ignores --no-cache-filter for a stage name that does not # match, so a rename or a typo would restore the cached false green with no # warning and a fast exit 0. The flag is therefore not trusted: the build # output is teed to a log and a run is only a pass if golangci-lint's own # summary line ("N issues." / "N issues:") is in it. No summary, no lint, # whatever the exit code says. set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" main() { cd "$ROOT" log="$(mktemp -t webhooker-lint.XXXXXXXX)" rcfile="$(mktemp -t webhooker-lint-rc.XXXXXXXX)" trap 'rm -f "$log" "$rcfile"' EXIT INT TERM # The pipeline's status is tee's, and POSIX sh has no pipefail, so the # build's status travels via a file. Output still streams live. { docker build \ -f Dockerfile.lint \ --no-cache-filter=lint \ --progress=plain \ --output=type=cacheonly \ . 2>&1 && echo 0 >"$rcfile" || echo $? >"$rcfile" } | tee "$log" >&2 rc="$(cat "$rcfile")" [ "$rc" -eq 0 ] || exit "$rc" if ! grep -qE '[0-9]+ issues[.:]' "$log"; then echo "script/lint: golangci-lint printed no summary line; the linter" >&2 echo " did not run. Check that the stage named in --no-cache-filter" >&2 echo " still matches a stage in Dockerfile.lint." >&2 exit 1 fi # ESLint runs in the Dockerfile's js-lint stage, which the image build # runs too. It prints nothing on a pass, so there is no summary to look # for. Instead the stage is named once, for both flags: --target fails # on a name that matches no stage, so a rename cannot leave # --no-cache-filter silently ignored. That flag re-runs the whole stage, # yarn install included. js_stage=js-lint docker build \ --target "$js_stage" \ --no-cache-filter="$js_stage" \ --progress=plain \ --output=type=cacheonly \ . } main "$@"