ia_dev/deploy/pousse.sh
2026-03-16 16:38:55 +01:00

204 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "[pousse][ERROR] Not in a git repository" >&2
exit 1
fi
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo "${BASH_SOURCE[0]:-$0}")"
DEPLOY_DIR="$(cd "$(dirname "$SCRIPT_REAL")" && pwd)"
IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)"
if [[ "$(pwd)" != "$PROJECT_ROOT" ]]; then
SCRIPT_ABS="${DEPLOY_DIR}/$(basename "${BASH_SOURCE[0]:-$0}")"
cd "$PROJECT_ROOT" && exec "$SCRIPT_ABS" "$@"
fi
# Resolve project id and config path: MAIL_TO or AI_AGENT_TOKEN only (no fallback) → projects/<id>/conf.json
# shellcheck source=../lib/project_config.sh
source "${IA_DEV_ROOT}/lib/project_config.sh"
remote="origin"
bump_version=false
usage() {
cat <<'EOF'
Usage:
./deploy/pousse.sh [--remote <remote>] [--bump-version]
--bump-version Increment patch (third component) in VERSION before staging.
Reads a full multi-line commit message from STDIN, then:
- if not in repo root: re-exec from repo root (standardized execution)
- build check (npm run build in each directory listed in projects/<id>/conf.json build_dirs, if any; exit on failure)
- git add -A
- git commit -F <message>
- git push -u <remote> HEAD
The current branch must already exist on the remote (e.g. origin/<branch>); otherwise the script refuses to push.
Example:
./deploy/pousse.sh <<'MSG'
Title
**Motivations:**
- ...
**Root causes:**
- ...
**Correctifs:**
- ...
**Evolutions:**
- ...
**Pages affectées:**
- ...
MSG
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--remote)
remote="$2"
shift 2
;;
--bump-version)
bump_version=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "[pousse][ERROR] Unknown arg: $1" >&2
usage >&2
exit 1
;;
esac
done
repo_root="$(git rev-parse --show-toplevel)"
# When run from ia_dev root, use configured project repo for git operations (MAIL_TO or AI_AGENT_TOKEN must be set)
git_work_root="$repo_root"
if [[ -n "${PROJECT_CONFIG_PATH:-}" && -f "$PROJECT_CONFIG_PATH" ]] && command -v jq >/dev/null 2>&1 && [[ "$repo_root" == "$IA_DEV_ROOT" ]]; then
_secrets_path="$(jq -r '.deploy.secrets_path // ""' "$PROJECT_CONFIG_PATH" 2>/dev/null)"
if [[ -n "$_secrets_path" ]]; then
_gwr="$(dirname "$_secrets_path")"
if [[ -d "$_gwr" ]]; then
git_work_root="$_gwr"
fi
fi
fi
if [[ "$(pwd)" != "$git_work_root" ]]; then
cd "$git_work_root" || { echo "[pousse][ERROR] Cannot cd to project root ${git_work_root}" >&2; exit 1; }
fi
branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ -z "$branch" || "$branch" == "HEAD" ]]; then
echo "[pousse][ERROR] Detached HEAD is not supported" >&2
exit 1
fi
author_name="$(git config user.name || true)"
if [[ "$author_name" != "4NK" && "$author_name" != "Nicolas Cantu" ]]; then
echo "[pousse][ERROR] Refusing to commit: git user.name must be '4NK' or 'Nicolas Cantu' (got: '${author_name}')" >&2
exit 1
fi
# Build dirs from project config (projects/<id>/conf.json); skip if no config or no build_dirs
build_dirs=()
if [[ -n "${PROJECT_CONFIG_PATH:-}" && -f "$PROJECT_CONFIG_PATH" ]] && command -v jq >/dev/null 2>&1; then
while IFS= read -r d; do
[[ -n "$d" ]] && build_dirs+=( "$d" )
done < <(jq -r '.build_dirs[]? // empty' "$PROJECT_CONFIG_PATH" 2>/dev/null)
fi
if [[ ${#build_dirs[@]} -gt 0 ]]; then
echo "[pousse] Build check (${#build_dirs[@]} dirs from project config)..."
for dir in "${build_dirs[@]}"; do
if [[ "$dir" = /* ]]; then
abs_dir="$dir"
else
abs_dir="${repo_root}/${dir}"
fi
if [[ ! -d "$abs_dir" ]]; then
echo "[pousse][WARN] Skipping build ${dir} (directory not found)" >&2
continue
fi
echo "[pousse] Building ${dir}..."
(cd "$abs_dir" && npm run build) || {
echo "[pousse][ERROR] Build failed in ${dir}" >&2
exit 1
}
done
echo "[pousse] Build check OK"
else
echo "[pousse] No build_dirs in project config (or no projects/<id>/conf.json / jq); skipping build check"
fi
msg_file="$(mktemp -t pousse-commit-msg.XXXXXX)"
cleanup() {
rm -f "$msg_file"
}
trap cleanup EXIT
cat >"$msg_file" || true
if [[ ! -s "$msg_file" ]]; then
echo "[pousse][ERROR] Empty commit message on STDIN" >&2
exit 1
fi
if [[ "$bump_version" == "true" ]]; then
version_file="${git_work_root}/VERSION"
if [[ ! -f "$version_file" ]]; then
echo "[pousse][ERROR] VERSION not found at ${version_file}" >&2
exit 1
fi
current="$(cat "$version_file" | sed 's/[[:space:]]//g')"
if ! [[ "$current" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "[pousse][ERROR] VERSION format must be X.Y.Z (got: '${current}')" >&2
exit 1
fi
maj="${current%%.*}"
min="${current#*.}"
min="${min%%.*}"
patch="${current##*.}"
patch=$((patch + 1))
new_version="${maj}.${min}.${patch}"
echo "$new_version" > "$version_file"
echo "[pousse] Bumped VERSION: ${current} -> ${new_version}"
fi
# Stage all changes
git add -A
git_status_short="$(git status -sb)"
echo "$git_status_short"
# Prevent committing potentially sensitive files
staged_files="$(git diff --cached --name-only || true)"
if [[ -n "$staged_files" ]]; then
if echo "$staged_files" | grep -Eiq '^(\.secrets/|\.env($|\.)|\.env\.|.*\.(key|pem|p12)$|.*credentials.*)'; then
echo "[pousse][ERROR] Refusing to commit: staged files look sensitive:" >&2
echo "$staged_files" | grep -Ei '^(\.secrets/|\.env($|\.)|\.env\.|.*\.(key|pem|p12)$|.*credentials.*)' >&2
exit 1
fi
fi
if git diff --cached --quiet; then
echo "[pousse] No staged changes to commit" >&2
exit 0
fi
echo "[pousse] Staged changes:"
git diff --cached --stat
git commit -F "$msg_file"
if ! git rev-parse "${remote}/${branch}" >/dev/null 2>&1; then
echo "[pousse][ERROR] Branch '${branch}' does not exist on remote '${remote}'. Refusing to push (would create a new remote branch). Create the branch on the remote first or push manually." >&2
exit 1
fi
git push -u "$remote" HEAD