264 lines
6.5 KiB
Bash
Executable file
264 lines
6.5 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
REPORT_DIR="$ROOT_DIR/reports"
|
|
ERROR_REPORT="$REPORT_DIR/errors.md"
|
|
|
|
FILES_CHECKED=0
|
|
WARNINGS=0
|
|
ERRORS=0
|
|
|
|
DETAILS=()
|
|
|
|
EXCLUDES=(
|
|
".git"
|
|
".forgejo"
|
|
"node_modules"
|
|
"vendor"
|
|
"Library"
|
|
"Temp"
|
|
"Logs"
|
|
"obj"
|
|
"bin"
|
|
)
|
|
|
|
mkdir -p "$REPORT_DIR"
|
|
|
|
escape_md() {
|
|
printf '%s' "$1" | sed 's/|/\\|/g'
|
|
}
|
|
|
|
have_tool() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
add_warning() {
|
|
local message="$1"
|
|
WARNINGS=$((WARNINGS + 1))
|
|
DETAILS+=("- Warning: $(escape_md "$message")")
|
|
}
|
|
|
|
add_error() {
|
|
local message="$1"
|
|
ERRORS=$((ERRORS + 1))
|
|
DETAILS+=("- Error: $(escape_md "$message")")
|
|
}
|
|
|
|
add_ok() {
|
|
local message="$1"
|
|
DETAILS+=("- OK: $(escape_md "$message")")
|
|
}
|
|
|
|
relative_path() {
|
|
local path="$1"
|
|
path="${path#"$ROOT_DIR"/}"
|
|
printf '%s' "$path"
|
|
}
|
|
|
|
join_by() {
|
|
local delimiter="$1"
|
|
shift || true
|
|
local first=1
|
|
local item
|
|
for item in "$@"; do
|
|
if [[ $first -eq 1 ]]; then
|
|
printf '%s' "$item"
|
|
first=0
|
|
else
|
|
printf '%s%s' "$delimiter" "$item"
|
|
fi
|
|
done
|
|
}
|
|
|
|
collect_files() {
|
|
local find_args=("$ROOT_DIR")
|
|
local exclude
|
|
|
|
if ((${#EXCLUDES[@]} > 0)); then
|
|
find_args+=("(")
|
|
for exclude in "${EXCLUDES[@]}"; do
|
|
find_args+=(-name "$exclude" -o)
|
|
done
|
|
unset 'find_args[${#find_args[@]}-1]'
|
|
find_args+=(")" -prune -o)
|
|
fi
|
|
|
|
find_args+=(-type f -print0)
|
|
find "${find_args[@]}"
|
|
}
|
|
|
|
run_check() {
|
|
local label="$1"
|
|
local file="$2"
|
|
shift 2
|
|
|
|
FILES_CHECKED=$((FILES_CHECKED + 1))
|
|
|
|
if "$@" >/tmp/runlevel-validate.out 2>/tmp/runlevel-validate.err; then
|
|
add_ok "$label passed: $(relative_path "$file")"
|
|
else
|
|
local stdout stderr combined
|
|
stdout="$(tr '\n' ' ' </tmp/runlevel-validate.out | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
|
stderr="$(tr '\n' ' ' </tmp/runlevel-validate.err | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
|
combined="$(join_by ' | ' "${stdout:-}" "${stderr:-}")"
|
|
add_error "$label failed: $(relative_path "$file")${combined:+ | $combined}"
|
|
fi
|
|
}
|
|
|
|
run_repo_build_checks() {
|
|
local sln_files=()
|
|
local csproj_files=()
|
|
local cmake_file="$ROOT_DIR/CMakeLists.txt"
|
|
local tmp_dir
|
|
|
|
while IFS= read -r -d '' file; do
|
|
sln_files+=("$file")
|
|
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.sln' -type f -print0)
|
|
|
|
while IFS= read -r -d '' file; do
|
|
csproj_files+=("$file")
|
|
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.csproj' -type f -print0)
|
|
|
|
if ((${#sln_files[@]} > 0 || ${#csproj_files[@]} > 0)); then
|
|
if have_tool dotnet; then
|
|
if ((${#sln_files[@]} > 0)); then
|
|
for file in "${sln_files[@]}"; do
|
|
run_check "dotnet build" "$file" dotnet build "$file" --nologo --verbosity minimal
|
|
done
|
|
else
|
|
for file in "${csproj_files[@]}"; do
|
|
run_check "dotnet build" "$file" dotnet build "$file" --nologo --verbosity minimal
|
|
done
|
|
fi
|
|
else
|
|
add_warning "dotnet not installed; skipping C# validation."
|
|
fi
|
|
fi
|
|
|
|
if [[ -f "$cmake_file" ]]; then
|
|
if have_tool cmake; then
|
|
FILES_CHECKED=$((FILES_CHECKED + 1))
|
|
tmp_dir="$(mktemp -d)"
|
|
if cmake -S "$ROOT_DIR" -B "$tmp_dir" >/tmp/runlevel-validate.out 2>/tmp/runlevel-validate.err; then
|
|
add_ok "cmake configure passed: CMakeLists.txt"
|
|
else
|
|
local stdout stderr combined
|
|
stdout="$(tr '\n' ' ' </tmp/runlevel-validate.out | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
|
stderr="$(tr '\n' ' ' </tmp/runlevel-validate.err | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
|
combined="$(join_by ' | ' "${stdout:-}" "${stderr:-}")"
|
|
add_error "cmake configure failed: CMakeLists.txt${combined:+ | $combined}"
|
|
fi
|
|
rm -rf "$tmp_dir"
|
|
else
|
|
add_warning "cmake not installed; skipping C/C++ validation."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
validate_file() {
|
|
local file="$1"
|
|
local base
|
|
|
|
base="$(basename "$file")"
|
|
|
|
case "$file" in
|
|
*.php)
|
|
if have_tool php; then
|
|
run_check "php -l" "$file" php -l "$file"
|
|
else
|
|
add_warning "php not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*.py)
|
|
if have_tool python3; then
|
|
run_check "python3 -m py_compile" "$file" python3 -m py_compile "$file"
|
|
else
|
|
add_warning "python3 not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*.rb)
|
|
if have_tool ruby; then
|
|
run_check "ruby -c" "$file" ruby -c "$file"
|
|
else
|
|
add_warning "ruby not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*.pl|*.pm)
|
|
if have_tool perl; then
|
|
run_check "perl -c" "$file" perl -c "$file"
|
|
else
|
|
add_warning "perl not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*.sh|*.bash)
|
|
if have_tool bash; then
|
|
run_check "bash -n" "$file" bash -n "$file"
|
|
else
|
|
add_warning "bash not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*.js|*.mjs|*.cjs)
|
|
if have_tool node; then
|
|
run_check "node --check" "$file" node --check "$file"
|
|
else
|
|
add_warning "node not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*.json)
|
|
if have_tool jq; then
|
|
run_check "jq validation" "$file" jq empty "$file"
|
|
else
|
|
add_warning "jq not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*.yml|*.yaml)
|
|
if have_tool yamllint; then
|
|
run_check "yamllint" "$file" yamllint -f parsable "$file"
|
|
else
|
|
add_warning "yamllint not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*.html|*.htm)
|
|
if have_tool tidy; then
|
|
run_check "tidy validation" "$file" tidy -qe "$file"
|
|
else
|
|
add_warning "tidy not installed; skipping $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
*)
|
|
if [[ "$base" == "Dockerfile" ]]; then
|
|
FILES_CHECKED=$((FILES_CHECKED + 1))
|
|
add_ok "Detected Dockerfile: $(relative_path "$file")"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
while IFS= read -r -d '' file; do
|
|
validate_file "$file"
|
|
done < <(collect_files)
|
|
|
|
run_repo_build_checks
|
|
|
|
{
|
|
printf '# Validation Summary\n\n'
|
|
printf -- '- Files Checked: %s\n' "$FILES_CHECKED"
|
|
printf -- '- Warnings: %s\n' "$WARNINGS"
|
|
printf -- '- Errors: %s\n\n' "$ERRORS"
|
|
printf '## Detailed Results\n\n'
|
|
if ((${#DETAILS[@]} == 0)); then
|
|
printf 'No validation steps were run.\n'
|
|
else
|
|
printf '%s\n' "${DETAILS[@]}"
|
|
fi
|
|
} >"$ERROR_REPORT"
|
|
|
|
rm -f /tmp/runlevel-validate.out /tmp/runlevel-validate.err
|
|
|
|
if ((ERRORS > 0)); then
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|