Compare commits

...

6 Commits

Author SHA1 Message Date
480a61b071 ci: fix hotpatch (yaml datetime -> json safe)
All checks were successful
CI / build-and-anchors (push) Successful in 1m45s
SMOKE / smoke (push) Successful in 22s
2026-02-26 21:29:52 +01:00
390f2c33e5 ci: fix deploy workflow (warmup + hotpatch)
All checks were successful
CI / build-and-anchors (push) Successful in 2m2s
SMOKE / smoke (push) Successful in 17s
2026-02-26 21:06:01 +01:00
16485dc4a9 Merge pull request 'ci: shard annotations by para + deploy deep-merge hotpatch' (#135) from chore/anno-shard-deepmerge into main
Some checks failed
CI / build-and-anchors (push) Successful in 1m54s
Deploy staging+live (annotations) / deploy (push) Failing after 7m20s
SMOKE / smoke (push) Successful in 13s
Reviewed-on: #135
2026-02-26 19:58:22 +01:00
0519ae2dd0 Merge pull request 'ci: fix deploy checkout (no eval) + workflow_dispatch fallback' (#134) from chore/fix-deploy-container-conflict into main
All checks were successful
CI / build-and-anchors (push) Successful in 1m52s
Deploy staging+live (annotations) / deploy (push) Successful in 8m48s
SMOKE / smoke (push) Successful in 19s
Reviewed-on: #134
2026-02-26 18:51:53 +01:00
342e21b9ea Merge pull request 'ci: fix deploy checkout (no eval) + workflow_dispatch fallback' (#133) from chore/fix-deploy-checkout-quote into main
Some checks failed
CI / build-and-anchors (push) Successful in 1m40s
Deploy staging+live (annotations) / deploy (push) Failing after 10m11s
SMOKE / smoke (push) Successful in 16s
Reviewed-on: #133
2026-02-26 18:01:24 +01:00
c7ae883c6a Merge pull request 'ci: fix deploy workflow (workflow_dispatch checkout + gate)' (#132) from chore/fix-deploy-workflow into main
Some checks failed
CI / build-and-anchors (push) Successful in 1m37s
Deploy staging+live (annotations) / deploy (push) Failing after 19s
SMOKE / smoke (push) Successful in 17s
Reviewed-on: #132
2026-02-26 17:44:28 +01:00

View File

@@ -189,12 +189,9 @@ jobs:
docker image tag archicratie-web:blue "archicratie-web:blue.BAK.${TS}" || true
docker image tag archicratie-web:green "archicratie-web:green.BAK.${TS}" || true
# ✅ use cache (DO NOT --no-cache)
docker compose -p "$PROJ" -f docker-compose.yml build web_blue
# ✅ hard fix: remove existing container if name conflicts
docker rm -f archicratie-web-blue || true
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_blue
curl -fsS "http://127.0.0.1:8081/para-index.json" >/dev/null
@@ -256,6 +253,7 @@ jobs:
python3 - <<'PY'
import os, re, json, glob, datetime
import yaml
import datetime as dt
ROOT = os.getcwd()
ANNO_ROOT = os.path.join(ROOT, "src", "annotations")
@@ -263,6 +261,26 @@ jobs:
def is_obj(x): return isinstance(x, dict)
def is_arr(x): return isinstance(x, list)
# --- KEY FIX: YAML timestamps -> datetime; JSON can't dump them
def iso_dt(x):
if isinstance(x, dt.datetime):
if x.tzinfo is None:
return x.isoformat()
return x.astimezone(dt.timezone.utc).isoformat().replace("+00:00","Z")
if isinstance(x, dt.date):
return x.isoformat()
return None
def normalize(x):
s = iso_dt(x)
if s is not None:
return s
if isinstance(x, dict):
return {str(k): normalize(v) for k, v in x.items()}
if isinstance(x, list):
return [normalize(v) for v in x]
return x
def key_media(it):
return str((it or {}).get("src",""))
@@ -282,17 +300,19 @@ jobs:
seen = set()
out = []
for x in (dst_list or []):
x = normalize(x)
k = key_fn(x)
if k and k not in seen:
seen.add(k); out.append(x)
for x in (src_list or []):
x = normalize(x)
k = key_fn(x)
if k and k not in seen:
seen.add(k); out.append(x)
return out
def deep_merge(dst, src):
# non destructif : ne supprime rien, n'écrase pas les scalaires existants
src = normalize(src)
for k, v in (src or {}).items():
if k in ("media", "refs", "comments_editorial") and is_arr(v):
if k == "media":
@@ -305,21 +325,22 @@ jobs:
if is_obj(v):
if not is_obj(dst.get(k)):
dst[k] = dst.get(k) if is_obj(dst.get(k)) else {}
dst[k] = {} if not is_obj(dst.get(k)) else dst.get(k)
deep_merge(dst[k], v)
continue
if is_arr(v):
# fallback: union by json string
cur = dst.get(k, [])
if not is_arr(cur): cur = []
seen = set()
out = []
for x in cur:
x = normalize(x)
s = json.dumps(x, sort_keys=True, ensure_ascii=False)
if s not in seen:
seen.add(s); out.append(x)
for x in v:
x = normalize(x)
s = json.dumps(x, sort_keys=True, ensure_ascii=False)
if s not in seen:
seen.add(s); out.append(x)
@@ -327,6 +348,7 @@ jobs:
continue
# scalaires: set seulement si absent / vide
v = normalize(v)
if k not in dst or dst.get(k) in (None, ""):
dst[k] = v
@@ -338,11 +360,16 @@ jobs:
for k in ("media","refs","comments_editorial"):
arr = entry.get(k)
if not is_arr(arr): continue
def ts(x):
x = normalize(x)
try:
return datetime.datetime.fromisoformat(str((x or {}).get("ts","")).replace("Z","+00:00")).timestamp()
s = str((x or {}).get("ts",""))
return dt.datetime.fromisoformat(s.replace("Z","+00:00")).timestamp() if s else 0
except Exception:
return 0
arr = [normalize(x) for x in arr]
arr.sort(key=lambda x: (ts(x), json.dumps(x, sort_keys=True, ensure_ascii=False)))
entry[k] = arr
@@ -357,8 +384,11 @@ jobs:
try:
with open(fp, "r", encoding="utf-8") as f:
doc = yaml.safe_load(f) or {}
doc = normalize(doc)
if not isinstance(doc, dict) or doc.get("schema") != 1:
continue
page = str(doc.get("page","")).strip().strip("/")
paras = doc.get("paras") or {}
if not page or not isinstance(paras, dict):
@@ -376,7 +406,6 @@ jobs:
except Exception as e:
errors.append({"file": os.path.relpath(fp, ROOT), "error": str(e)})
# tri paras
for page, obj in pages.items():
keys = list((obj.get("paras") or {}).keys())
keys.sort(key=lambda k: (para_num(k), k))
@@ -384,7 +413,7 @@ jobs:
out = {
"schema": 1,
"generatedAt": datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat().replace("+00:00","Z"),
"generatedAt": dt.datetime.utcnow().replace(tzinfo=dt.timezone.utc).isoformat().replace("+00:00","Z"),
"pages": pages,
"stats": {
"pages": len(pages),
@@ -394,19 +423,19 @@ jobs:
"errors": errors,
}
out = normalize(out)
with open("/tmp/annotations-index.json", "w", encoding="utf-8") as f:
json.dump(out, f, ensure_ascii=False)
print("OK: wrote /tmp/annotations-index.json pages=", out["stats"]["pages"], "paras=", out["stats"]["paras"], "errors=", out["stats"]["errors"])
PY
# inject into running containers
for c in archicratie-web-blue archicratie-web-green; do
echo "== patch $c =="
docker cp /tmp/annotations-index.json "${c}:/usr/share/nginx/html/annotations-index.json"
done
# quick smoke: check new file exists and is readable
for p in 8081 8082; do
echo "== smoke annotations-index on $p =="
curl -fsS "http://127.0.0.1:${p}/annotations-index.json" | python3 -c 'import sys,json; j=json.load(sys.stdin); print("generatedAt:", j.get("generatedAt")); print("pages:", len(j.get("pages") or {}))'