master
sh 145 lines 5.08 KB
Raw
1 #!/usr/bin/env bash
2 # analyze-local.sh -- run codacy-analysis-cli locally on the working tree.
3 #
4 # Mirrors what Codacy CI would run on the same source. Useful BEFORE
5 # `git push` to catch findings in seconds, not minutes.
6 #
7 # Output: a JSON dump under <repo>/.local/audits/codacy/.
8 # stdout (last line): the dump path.
9
10 set -euo pipefail
11
12 usage() {
13 cat <<'EOF'
14 analyze-local.sh [options]
15
16 Runs the official codacy-analysis-cli (https://github.com/codacy/codacy-analysis-cli)
17 on the current working tree and writes a JSON dump under
18 <repo>/.local/audits/codacy/. The cli respects the repo's .codacy.yml
19 exclude_paths.
20
21 Options:
22 --tool <name> run a single tool (e.g. shellcheck, markdownlint).
23 Omit to run all tools applicable to changed files.
24 --directory <path> analyze a subpath (default: <repo-root>)
25 --format json|sarif output format (default: json)
26 --output PATH explicit dump path (default: auto under .local/audits/codacy/)
27 --runner docker|local installer to use (default: auto -- prefer local
28 binary, fall back to docker, fall back to npm)
29 -h, --help
30
31 Required tools: docker (default) OR a local codacy-analysis-cli binary.
32 EOF
33 }
34
35 TOOL=
36 SUBDIR=
37 FORMAT=json
38 OUTPUT=
39 RUNNER=auto
40
41 while [ $# -gt 0 ]; do
42 case "$1" in
43 --tool) TOOL="$2"; shift 2 ;;
44 --directory) SUBDIR="$2"; shift 2 ;;
45 --format) FORMAT="$2"; shift 2 ;;
46 --output) OUTPUT="$2"; shift 2 ;;
47 --runner) RUNNER="$2"; shift 2 ;;
48 -h|--help) usage; exit 0 ;;
49 *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
50 esac
51 done
52
53 # shellcheck source=SCRIPTDIR/_lib.sh disable=SC1091
54 source "$(cd "$(dirname "$0")" && pwd)/_lib.sh"
55
56 # We do not require CODACY_TOKEN here -- the CLI runs without it
57 # for read-only local analysis. Skip env load to avoid forcing
58 # users without a token to set one just to run pre-push checks.
59
60 repo_root="$(codacyaudit_repo_root)"
61 audit_dir="$(codacyaudit_audit_dir)"
62
63 # Resolve target directory.
64 if [ -z "$SUBDIR" ]; then
65 SUBDIR="$repo_root"
66 else
67 case "$SUBDIR" in
68 /*) : ;; # absolute
69 *) SUBDIR="$(cd "$SUBDIR" && pwd)" ;;
70 esac
71 fi
72
73 # Resolve output path.
74 if [ -z "$OUTPUT" ]; then
75 suffix=""
76 [ -n "$TOOL" ] && suffix="-${TOOL}"
77 OUTPUT="${audit_dir}/local${suffix}-$(date -u +%Y%m%dT%H%M%SZ).${FORMAT}"
78 fi
79
80 # Pick a runner.
81 if [ "$RUNNER" = "auto" ]; then
82 if command -v codacy-analysis-cli >/dev/null 2>&1; then
83 RUNNER=local
84 elif command -v docker >/dev/null 2>&1; then
85 RUNNER=docker
86 else
87 echo -e "${CA_RED}[ERROR]${CA_NC} neither 'codacy-analysis-cli' nor 'docker' found in PATH." >&2
88 echo "Install options:" >&2
89 echo " - docker: https://docs.docker.com/get-docker/" >&2
90 echo " - cli: https://github.com/codacy/codacy-analysis-cli#install" >&2
91 exit 2
92 fi
93 fi
94
95 echo -e "${CA_GRAY}[analyze-local] runner=${RUNNER} format=${FORMAT} dir=${SUBDIR}${CA_NC}" >&2
96
97 case "$RUNNER" in
98 local)
99 # Local binary expects host paths.
100 local_args=(analyze --directory "$SUBDIR" --format "$FORMAT")
101 [ -n "$TOOL" ] && local_args+=(--tool "$TOOL")
102 if ! codacy-analysis-cli "${local_args[@]}" > "$OUTPUT" 2>/dev/null; then
103 echo -e "${CA_YELLOW}[analyze-local] cli returned non-zero (this is normal when findings are present)${CA_NC}" >&2
104 fi
105 ;;
106 docker)
107 # Per https://github.com/codacy/codacy-analysis-cli the CLI
108 # spawns one child container per tool and needs:
109 # - the host docker socket (docker-in-docker)
110 # - CODACY_CODE pointing at the host path of the source
111 # - the source bind-mounted at the SAME path inside the
112 # CLI container so child containers can resolve it
113 cli_args=(analyze --directory "$SUBDIR" --format "$FORMAT")
114 [ -n "$TOOL" ] && cli_args+=(--tool "$TOOL")
115 if ! docker run --rm \
116 --env CODACY_CODE="$SUBDIR" \
117 --volume /var/run/docker.sock:/var/run/docker.sock \
118 --volume "$SUBDIR":"$SUBDIR" \
119 codacy/codacy-analysis-cli:latest \
120 "${cli_args[@]}" > "$OUTPUT" 2>/dev/null; then
121 echo -e "${CA_YELLOW}[analyze-local] cli returned non-zero (this is normal when findings are present)${CA_NC}" >&2
122 fi
123 ;;
124 *)
125 echo -e "${CA_RED}[ERROR]${CA_NC} unknown --runner '${RUNNER}'" >&2
126 exit 2
127 ;;
128 esac
129
130 # Sanity check the output.
131 if [ ! -s "$OUTPUT" ]; then
132 echo -e "${CA_RED}[ERROR]${CA_NC} empty output at ${OUTPUT}; check the runner above" >&2
133 exit 1
134 fi
135
136 # Quick summary if format=json.
137 if [ "$FORMAT" = "json" ] && jq -e . "$OUTPUT" >/dev/null 2>&1; then
138 n="$(jq 'if type=="array" then length elif type=="object" and has("issues") then (.issues|length) else 0 end' "$OUTPUT")"
139 echo -e "${CA_GREEN}[analyze-local]${CA_NC} wrote ${n} finding(s) to ${OUTPUT}" >&2
140 else
141 echo -e "${CA_GREEN}[analyze-local]${CA_NC} wrote ${OUTPUT} (${FORMAT} format)" >&2
142 fi
143
144 # Last line on stdout: the path. Pipe-friendly.
145 echo "$OUTPUT"