@@ -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 :-<empty>}"
echo "BEFORE: ${BEFORE :-<empty>}"
echo "AFTER: ${AFTER:-<empty>}"
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
@@ -96,61 +101,97 @@ jobs:
- name : Gate — decide SKIP vs HOTPATCH vs FULL rebuild
env :
INPUT_FORCE : ${{ inputs.force }}
EVENT_JSON : /var/run/act/workflow/event.json
run : |
set -euo pipefail
source /tmp/deploy.env
FORCE="${INPUT_FORCE:-0}"
# 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
# Lire before/after du push depuis event.json (merge-proof )
node --input-type=module <<'NODE'
import fs from "node:fs";
const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON, "utf8"));
const before = ev?.before || "";
const after = ev?.after || ev?.sha || "";
const shq = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'";
fs.writeFileSync("/tmp/gate.env", [
`EV_BEFORE=${shq(before)}`,
`EV_AFTER=${shq(after)}`
].join("\n") + "\n");
NODE
echo "== changed files =="
echo "$CHANGED" | sed -n '1,260p'
source /tmp/gate.env
# 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
BEFORE="${EV_BEFORE:-}"
AFTER="${EV_AFTER:-}"
if [[ -z "${AFTER:-}" ]]; th en
AFTER="${SHA:-}"
fi
# 1) Détection des classes de changements
echo "Gate ctx: BEFORE=${BEFORE:-<empty>} AFTER=${AFTER:-<empty>} FORCE=${FORCE}"
# Produire une liste CHANGED fiable :
# - si BEFORE/AFTER valides -> git diff before..after
# - sinon fallback -> diff parent1..after ou show after
CHANGED=""
Z40="0000000000000000000000000000000000000000"
if [[ -n "${BEFORE:-}" && "${BEFORE}" != "${Z40}" ]] \
&& git cat-file -e "${BEFORE}^{commit}" 2>/dev/null \
&& git cat-file -e "${AFTER}^{commit}" 2>/dev/null; then
CHANGED="$(git diff --name-only "${BEFORE}" "${AFTER}" || true)"
else
P1="$(git rev-parse "${AFTER}^" 2>/dev/null || true)"
if [[ -n "${P1:-}" ]] && git cat-file -e "${P1}^{commit}" 2>/dev/null; then
CHANGED="$(git diff --name-only "${P1}" "${AFTER}" || true)"
else
CHANGED="$(git show --name-only --pretty="" "${AFTER}" | sed '/^$/d' || true)"
fi
fi
printf "%s\n" "${CHANGED}" > /tmp/changed.txt
echo "== changed files (first 200) =="
sed -n '1,200p' /tmp/changed.txt || true
# Flags
HAS_FULL=0
HAS_HOTPATCH=0
# FULL si build-impacting (zéro surprise )
if echo "$CHANGED" | grep -qE '^(src/content/|src/anchors/|src/pages/|scripts/)'; then
# FULL si build-impacting (ce que tu veux : content/anchors/pages/scripts )
if grep -qE '^(src/content/|src/anchors/|src/pages/|scripts/)' /tmp/changed.txt ; then
HAS_FULL=1
fi
# HOTPATCH si annotations/media
if echo "$CHANGED" | grep -qE '^(src/annotations/|public/media/)'; then
# HOTPATCH si annotations/media touchés
if grep -qE '^(src/annotations/|public/media/)' /tmp/changed.txt ; 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
# Décision
if [[ "${FORCE} " == "1" ]]; then
GO=1
MODE=" full"
echo "✅ force=1 -> MODE=full (rebuild+restart)"
elif [[ "${HAS_FULL}" == "1" ]]; then
GO=1
MODE="full"
echo "✅ build-impacting change -> MODE=full (rebuild+restart)"
exit 0
fi
if [[ "$HAS_HOTPATCH" == "1" ]]; then
echo "GO=1" >> /tmp/deploy.env
echo "MODE='hotpatch'" >> /tmp/deploy.env
elif [[ "${HAS_HOTPATCH}" == "1" ]]; then
GO=1
MODE="hotpatch"
echo "✅ annotations/media change -> MODE=hotpatch"
exit 0
else
GO=0
MODE="skip"
echo "ℹ ️ no relevant change -> skip deploy"
fi
echo "GO=0 " >> /tmp/deploy.env
echo "MODE='skip '" >> /tmp/deploy.env
echo "ℹ ️ no deploy-relevant change -> skip deploy"
echo "GO=${GO} " >> /tmp/deploy.env
echo "MODE='${MODE} '" >> /tmp/deploy.env
- name : Toolchain sanity + resolve COMPOSE_PROJECT_NAME
run : |