chore(ci): bootstrap CI + hooks
Squash-merge bootstrap-cicd into integration
This commit is contained in:
115
scripts/ci
Executable file
115
scripts/ci
Executable file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PHASE="${1:-}"
|
||||
|
||||
usage() {
|
||||
echo "Usage: scripts/ci <format|lint|build|test|security|all>" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
if [[ -z "${PHASE}" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
repo_root() {
|
||||
git rev-parse --show-toplevel 2>/dev/null || pwd
|
||||
}
|
||||
|
||||
run_outside_container() {
|
||||
local phase="$1"
|
||||
local root
|
||||
root="$(repo_root)"
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "Docker is required to run CI tasks locally." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v docker-compose >/dev/null 2>&1 && ! docker compose version >/dev/null 2>&1; then
|
||||
echo "Docker Compose v2+ is required (docker compose)." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Build ci image if needed and run the requested phase inside the container
|
||||
(cd "$root" && docker compose -f docker/ci.compose.yml run --rm \
|
||||
-e IN_CI_CONTAINER=1 \
|
||||
ci bash -lc "cd /workspace && scripts/ci --inside ${phase}")
|
||||
}
|
||||
|
||||
run_format() {
|
||||
echo ">> Formatting"
|
||||
# shell: format in-place
|
||||
shfmt -bn -ci -i 2 -w .
|
||||
# prettier for markdown/yaml/json/etc
|
||||
prettier --log-level warn --write \
|
||||
"**/*.md" "**/*.yaml" "**/*.yml" "**/*.json" \
|
||||
"**/*.css" "**/*.html" 2>/dev/null || true
|
||||
}
|
||||
|
||||
run_lint() {
|
||||
echo ">> Linting"
|
||||
# shellcheck
|
||||
mapfile -t sh_files < <(git ls-files -z | xargs -0 file --mime-type | awk -F: '/(x-shellscript|text\/x-shellscript)/{print $1}'; git ls-files "*.sh")
|
||||
if [[ ${#sh_files[@]} -gt 0 ]]; then
|
||||
shellcheck -x "${sh_files[@]}" || (echo "Shellcheck failed" && exit 1)
|
||||
shfmt -d .
|
||||
fi
|
||||
# hadolint on Dockerfiles
|
||||
if ls Dockerfile* docker/*Dockerfile* 1>/dev/null 2>&1; then
|
||||
hadolint Dockerfile* docker/*Dockerfile* 2>/dev/null || true
|
||||
fi
|
||||
# yamllint
|
||||
if git ls-files "*.yml" "*.yaml" | grep -q .; then
|
||||
yamllint -s $(git ls-files "*.yml" "*.yaml")
|
||||
fi
|
||||
# markdownlint
|
||||
if git ls-files "*.md" | grep -q .; then
|
||||
markdownlint $(git ls-files "*.md")
|
||||
fi
|
||||
# actionlint for workflow files if present
|
||||
if [ -d .gitea/workflows ]; then
|
||||
actionlint -color
|
||||
fi
|
||||
}
|
||||
|
||||
run_build() {
|
||||
echo ">> Build checks"
|
||||
# Validate docker compose configs if present
|
||||
if [ -f docker-compose.yml ] || [ -f docker/compose.yml ]; then
|
||||
docker compose config -q
|
||||
fi
|
||||
}
|
||||
|
||||
run_test() {
|
||||
echo ">> Tests (none defined)"
|
||||
}
|
||||
|
||||
run_security() {
|
||||
echo ">> Security checks (skipped for this repo)"
|
||||
}
|
||||
|
||||
run_inside_container() {
|
||||
local phase="$1"
|
||||
case "$phase" in
|
||||
format) run_format ;;
|
||||
lint) run_lint ;;
|
||||
build) run_build ;;
|
||||
test) run_test ;;
|
||||
security) run_security ;;
|
||||
all) run_format; run_lint; run_build; run_test; run_security ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "--inside" ]]; then
|
||||
shift
|
||||
PHASE="${1:-}"
|
||||
[[ -z "$PHASE" ]] && usage
|
||||
run_inside_container "$PHASE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${IN_CI_CONTAINER:-}" != "1" ]]; then
|
||||
run_outside_container "$PHASE"
|
||||
else
|
||||
run_inside_container "$PHASE"
|
||||
fi
|
||||
|
Reference in New Issue
Block a user