#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" ARCHIVE_BASE_DEFAULT="/tmp/mosquito-archives" ARCHIVE_TAG_DEFAULT="$(date +%Y%m%d_%H%M%S)" MODE="archive" # archive | delete APPLY="false" # false => dry-run INCLUDE_TRACKED="false" # true => include git tracked paths FAIL_ON_FOUND="false" # true => exit non-zero when cleanup candidates exist INCLUDE_BUILD_OUTPUTS="false" # true => include dist/target build outputs ARCHIVE_BASE="${ARCHIVE_BASE_DEFAULT}" ARCHIVE_TAG="${ARCHIVE_TAG_DEFAULT}" ARTIFACT_PATHS=( "frontend/admin/test-results" "frontend/e2e/e2e-results" "frontend/e2e/e2e-report" "frontend/e2e/playwright-report" "frontend/e2e/test-results" "frontend/e2e-admin/test-results" "frontend/e2e-results" ) BUILD_OUTPUT_PATHS=( "target" "frontend/dist" "frontend/admin/dist" "frontend/h5/dist" ) ROOT_SPILLOVER_GLOBS=( "e2e-test-report-*.md" "E2E_TEST*.md" "TEST_E2E_*.md" "COVERAGE_*.md" ) usage() { cat <<'EOF' Usage: ./scripts/ci/clean-artifacts.sh [--apply] [--mode archive|delete] [--archive-base DIR] [--archive-tag TAG] [--include-tracked] [--include-build-outputs] [--fail-on-found] Options: --apply Execute cleanup. Without this flag, script runs in dry-run mode. --mode MODE archive (default) | delete --archive-base DIR Archive base dir for archive mode. Default: /tmp/mosquito-archives --archive-tag TAG Archive subdir tag. Default: current timestamp --include-tracked Include git tracked paths (default: skip tracked) --include-build-outputs Include build output paths (target, frontend/*/dist) --fail-on-found Exit non-zero when cleanup candidates are found (useful for CI dry-run) -h, --help Show help Examples: ./scripts/ci/clean-artifacts.sh ./scripts/ci/clean-artifacts.sh --apply ./scripts/ci/clean-artifacts.sh --apply --mode archive --archive-tag manual_cleanup ./scripts/ci/clean-artifacts.sh --include-build-outputs --apply --mode archive --archive-tag weekly_cleanup ./scripts/ci/clean-artifacts.sh --fail-on-found EOF } log() { echo "[clean-artifacts] $*"; } is_git_tracked() { local rel="$1" local out out="$(git -C "${ROOT_DIR}" ls-files -- "${rel}" 2>/dev/null || true)" [[ -n "${out}" ]] } run_cmd() { if [[ "${APPLY}" == "true" ]]; then "$@" else log "DRY-RUN: $*" fi } while [[ $# -gt 0 ]]; do case "$1" in --apply) APPLY="true" shift ;; --mode) MODE="${2:-}" shift 2 ;; --archive-base) ARCHIVE_BASE="${2:-}" shift 2 ;; --archive-tag) ARCHIVE_TAG="${2:-}" shift 2 ;; --include-tracked) INCLUDE_TRACKED="true" shift ;; --include-build-outputs) INCLUDE_BUILD_OUTPUTS="true" shift ;; --fail-on-found) FAIL_ON_FOUND="true" shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown option: $1" >&2 usage exit 1 ;; esac done if [[ "${MODE}" != "archive" && "${MODE}" != "delete" ]]; then echo "Invalid --mode: ${MODE}" >&2 exit 1 fi ARCHIVE_DIR="${ARCHIVE_BASE}/${ARCHIVE_TAG}" log "root=${ROOT_DIR}" log "mode=${MODE} apply=${APPLY} include_tracked=${INCLUDE_TRACKED} include_build_outputs=${INCLUDE_BUILD_OUTPUTS} fail_on_found=${FAIL_ON_FOUND}" if [[ "${INCLUDE_BUILD_OUTPUTS}" == "true" ]] && pgrep -af "spring-boot:run|e2e_continuous_runner.sh|vite" >/dev/null 2>&1; then log "WARN: detected active dev/e2e processes; build outputs may be recreated immediately." fi if [[ "${MODE}" == "archive" ]]; then log "archive_dir=${ARCHIVE_DIR}" if [[ "${APPLY}" == "true" ]]; then mkdir -p "${ARCHIVE_DIR}" fi fi cleaned_count=0 skipped_count=0 found_count=0 CANDIDATE_PATHS=("${ARTIFACT_PATHS[@]}") if [[ "${INCLUDE_BUILD_OUTPUTS}" == "true" ]]; then CANDIDATE_PATHS+=("${BUILD_OUTPUT_PATHS[@]}") fi for rel in "${CANDIDATE_PATHS[@]}"; do abs="${ROOT_DIR}/${rel}" if [[ ! -e "${abs}" ]]; then continue fi if [[ "${INCLUDE_TRACKED}" != "true" ]] && is_git_tracked "${rel}"; then log "SKIP tracked path: ${rel}" skipped_count=$((skipped_count + 1)) continue fi if [[ "${MODE}" == "archive" ]]; then dest="${ARCHIVE_DIR}/${rel}" run_cmd mkdir -p "$(dirname "${dest}")" run_cmd mv "${abs}" "${dest}" log "ARCHIVE ${rel} -> ${dest}" else run_cmd rm -rf "${abs}" log "DELETE ${rel}" fi found_count=$((found_count + 1)) cleaned_count=$((cleaned_count + 1)) done # Root report spillover (files in repository root) shopt -s nullglob for pattern in "${ROOT_SPILLOVER_GLOBS[@]}"; do for file in "${ROOT_DIR}"/${pattern}; do [[ -f "${file}" ]] || continue rel="$(basename "${file}")" if [[ "${INCLUDE_TRACKED}" != "true" ]] && is_git_tracked "${rel}"; then log "SKIP tracked root file: ${rel}" skipped_count=$((skipped_count + 1)) continue fi if [[ "${MODE}" == "archive" ]]; then dest="${ARCHIVE_DIR}/root-spillover/${rel}" run_cmd mkdir -p "$(dirname "${dest}")" run_cmd mv "${file}" "${dest}" log "ARCHIVE ${rel} -> ${dest}" else run_cmd rm -f "${file}" log "DELETE ${rel}" fi found_count=$((found_count + 1)) cleaned_count=$((cleaned_count + 1)) done done shopt -u nullglob # Move root attach_pid files into tmp/pids pids_dir="${ROOT_DIR}/tmp/pids" if compgen -G "${ROOT_DIR}/.attach_pid*" > /dev/null; then run_cmd mkdir -p "${pids_dir}" while IFS= read -r pid_file; do rel_pid="$(basename "${pid_file}")" if [[ "${MODE}" == "archive" ]]; then if [[ "${APPLY}" == "true" ]]; then mv "${pid_file}" "${pids_dir}/${rel_pid}" else log "DRY-RUN: mv ${pid_file} ${pids_dir}/${rel_pid}" fi log "MOVE ${rel_pid} -> tmp/pids/" else run_cmd rm -f "${pid_file}" log "DELETE ${rel_pid}" fi found_count=$((found_count + 1)) cleaned_count=$((cleaned_count + 1)) done < <(find "${ROOT_DIR}" -maxdepth 1 -type f -name ".attach_pid*" | sort) fi log "done: found=${found_count}, cleaned=${cleaned_count}, skipped=${skipped_count}" if [[ "${APPLY}" != "true" ]]; then log "dry-run completed. Use --apply to execute." fi if [[ "${FAIL_ON_FOUND}" == "true" && "${found_count}" -gt 0 ]]; then log "fail-on-found enabled: detected ${found_count} cleanup candidates." exit 2 fi