diff --git a/.gitea/workflows/deploy-staging-live.yml b/.gitea/workflows/deploy-staging-live.yml index 74ebfe2..42fbe68 100644 --- a/.gitea/workflows/deploy-staging-live.yml +++ b/.gitea/workflows/deploy-staging-live.yml @@ -47,48 +47,53 @@ jobs: node --input-type=module <<'NODE' import fs from "node:fs"; + const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON, "utf8")); const repoObj = ev?.repository || {}; + const cloneUrl = repoObj?.clone_url || (repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : ""); if (!cloneUrl) throw new Error("No repository clone_url/html_url in event.json"); const defaultBranch = repoObj?.default_branch || "main"; - const sha = + + // Push-range (most reliable for change detection) + const before = String(ev?.before || "").trim(); + const after = (process.env.GITHUB_SHA && String(process.env.GITHUB_SHA).trim()) || - ev?.after || - ev?.sha || - ev?.head_commit?.id || - ev?.pull_request?.head?.sha || - ""; + String(ev?.after || ev?.sha || ev?.head_commit?.id || ev?.pull_request?.head?.sha || "").trim(); const shq = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'"; + fs.writeFileSync("/tmp/deploy.env", [ `REPO_URL=${shq(cloneUrl)}`, `DEFAULT_BRANCH=${shq(defaultBranch)}`, - `SHA=${shq(sha)}` + `BEFORE=${shq(before)}`, + `AFTER=${shq(after)}` ].join("\n") + "\n"); NODE source /tmp/deploy.env echo "Repo URL: $REPO_URL" echo "Default branch: $DEFAULT_BRANCH" - echo "SHA: ${SHA:-}" + echo "BEFORE: ${BEFORE:-}" + echo "AFTER: ${AFTER:-}" rm -rf .git git init -q git remote add origin "$REPO_URL" - if [[ -n "${SHA:-}" ]]; then - git fetch --depth 1 origin "$SHA" + # Checkout AFTER (or default branch if missing) + if [[ -n "${AFTER:-}" ]]; then + git fetch --depth 50 origin "$AFTER" git -c advice.detachedHead=false checkout -q FETCH_HEAD else - git fetch --depth 1 origin "$DEFAULT_BRANCH" + git fetch --depth 50 origin "$DEFAULT_BRANCH" git -c advice.detachedHead=false checkout -q "origin/$DEFAULT_BRANCH" - SHA="$(git rev-parse HEAD)" - echo "SHA='$SHA'" >> /tmp/deploy.env - echo "Resolved SHA: $SHA" + AFTER="$(git rev-parse HEAD)" + echo "AFTER='$AFTER'" >> /tmp/deploy.env + echo "Resolved AFTER: $AFTER" fi git log -1 --oneline @@ -101,57 +106,69 @@ jobs: source /tmp/deploy.env FORCE="${INPUT_FORCE:-0}" + BEFORE="${BEFORE:-}" + AFTER="${AFTER:-}" + + # helper: true si sha = 0000... (push delete / weird payload) + is_zeros() { [[ -n "${1:-}" && "$1" =~ ^0+$ ]]; } + + CHANGED="" + + if [[ "$FORCE" == "1" ]]; then + echo "GO=1" >> /tmp/deploy.env + echo "MODE='full'" >> /tmp/deploy.env + echo "Gate flags: HAS_FULL=1 HAS_HOTPATCH=0" + echo "✅ force=1 -> MODE=full (rebuild+restart)" + exit 0 + fi + + # ✅ Push range (robuste, merge-proof) + if [[ -n "$BEFORE" && -n "$AFTER" && ! is_zeros "$BEFORE" ]]; then + # On s'assure que BEFORE est présent localement (checkout a fetch AFTER) + git cat-file -e "$BEFORE^{commit}" 2>/dev/null || git fetch --depth 50 origin "$BEFORE" || true + CHANGED="$(git diff --name-only "$BEFORE" "$AFTER" | sed '/^$/d' || true)" + else + # Fallback (workflow_dispatch, etc.) : diff HEAD^..HEAD si possible + if git cat-file -e "${AFTER}^" 2>/dev/null; then + CHANGED="$(git diff --name-only "${AFTER}^" "$AFTER" | sed '/^$/d' || true)" + else + # Merge commit sans parents dispo / autre cas : force la version merge-aware + CHANGED="$(git show -m --first-parent --name-only --pretty="" "$AFTER" | sed '/^$/d' || true)" + fi + fi - # liste fichiers touchés (utile pour copier les médias) - CHANGED="$(git show --name-only --pretty="" "$SHA" | sed '/^$/d' || true)" printf "%s\n" "$CHANGED" > /tmp/changed.txt echo "== changed files ==" echo "$CHANGED" | sed -n '1,260p' - # 0) Forçage manuel - if [[ "$FORCE" == "1" ]]; then - echo "GO=1" >> /tmp/deploy.env - echo "MODE='full'" >> /tmp/deploy.env - echo "✅ force=1 -> MODE=full (rebuild+restart)" - exit 0 - fi - - # 1) Détection des classes de changements - HAS_FULL=0 + # --- Flags --- HAS_HOTPATCH=0 + echo "$CHANGED" | grep -qE '^(src/annotations/|public/media/)' && HAS_HOTPATCH=1 - # FULL si build-impacting (zéro surprise) - if echo "$CHANGED" | grep -qE '^(src/content/|src/anchors/|src/pages/|scripts/)'; then + HAS_FULL=0 + echo "$CHANGED" | grep -qE '^(src/(content|anchors|pages|components|layouts|styles|lib|plugins)/|scripts/|astro\.config\.mjs|package(-lock)?\.json|Dockerfile|nginx\.conf|docker-compose\.yml)' && HAS_FULL=1 + # public/ hors media => FULL (sinon ce n'est jamais déployé par hotpatch) + if echo "$CHANGED" | grep -qE '^public/' && ! echo "$CHANGED" | grep -qE '^public/media/'; then HAS_FULL=1 fi - # HOTPATCH si annotations/media - if echo "$CHANGED" | grep -qE '^(src/annotations/|public/media/)'; then - HAS_HOTPATCH=1 - fi + echo "Gate flags: HAS_FULL=${HAS_FULL} HAS_HOTPATCH=${HAS_HOTPATCH}" - echo "Gate flags: HAS_FULL=$HAS_FULL HAS_HOTPATCH=$HAS_HOTPATCH" - - # 2) Décision (priorité au FULL) if [[ "$HAS_FULL" == "1" ]]; then echo "GO=1" >> /tmp/deploy.env echo "MODE='full'" >> /tmp/deploy.env echo "✅ build-impacting change -> MODE=full (rebuild+restart)" - exit 0 - fi - - if [[ "$HAS_HOTPATCH" == "1" ]]; then + elif [[ "$HAS_HOTPATCH" == "1" ]]; then echo "GO=1" >> /tmp/deploy.env echo "MODE='hotpatch'" >> /tmp/deploy.env echo "✅ annotations/media change -> MODE=hotpatch" - exit 0 + else + echo "GO=0" >> /tmp/deploy.env + echo "MODE='skip'" >> /tmp/deploy.env + echo "ℹ️ no deploy-relevant change -> skip deploy" fi - echo "GO=0" >> /tmp/deploy.env - echo "MODE='skip'" >> /tmp/deploy.env - echo "ℹ️ no deploy-relevant change -> skip deploy" - - name: Toolchain sanity + resolve COMPOSE_PROJECT_NAME run: | set -euo pipefail