hreflang: aplicarlo solo cuando el espejo publica, sin que nadie se acuerde
Las parejas se curan a mano, pero solo se pueden declarar cuando los dos lados están publicados: un hreflang a una URL programada apunta a un 404. Había cinco esperando, repartidas entre el 4 y el 27 de agosto, y la alternativa era acordarse de correr `aplicar` cinco veces en las fechas justas. Eso no es una solución: es una lista que se cumple a medias, y el hreflang que falta no da error en ninguna parte, así que nadie se entera. - hreflang_watch.py + timer diario a las 06:40 UTC (40 min tras la cola, antes de canibal-watch). Telegram SOLO si algo se ha abierto o ha fallado: 26 días de cada 31 no habrá nada, y si esos días avisara, el día que importa se leería igual que los otros. - `aplica()` extraída de cmd_aplicar: el automático escribe por el MISMO camino que el manual, no por una copia suya. - `comprueba` separa PENDIENTE de ROTO. Antes daba diez ✗ de 404 fijos por los espejos programados; diez rojos permanentes que no se pueden arreglar son un informe que se deja de leer. Verificado por el camino real, no en seco: se le quitó el bloque al lado ES de la pareja de Varginha (avería visible en la web pública, 0 hreflang), el vigilante la detectó, la escribió, la verificó contra las URLs exactas y avisó por Telegram. Restaurada y comprobada después. Y `verifica()` se ve fallar con una pareja cruzada a propósito, así que el ✅ no es vacuo. Nueve tests. Ensayado además arrancando el unit por systemd, que es donde los CLIs de node mueren con 127 si falta el bin de nvm en el PATH. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+53
-14
@@ -231,13 +231,14 @@ def cmd_plan(a):
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_aplicar(a):
|
||||
plan, avisos, _ = cambios()
|
||||
for w in avisos:
|
||||
print(f" ⚠ {w}")
|
||||
if not plan:
|
||||
print("nada que hacer: ya está todo puesto")
|
||||
return 0
|
||||
def aplica(plan, log=print):
|
||||
"""Escribe el plan en Ghost. Devuelve (hechos, fallos, ruta_de_la_copia).
|
||||
|
||||
Vive fuera de cmd_aplicar para que el vigilante automático (hreflang_watch)
|
||||
escriba por ESTE camino y no por una copia suya: si el manual y el
|
||||
automático no comparten la escritura, el ensayo de uno no dice nada del
|
||||
otro.
|
||||
"""
|
||||
os.makedirs(BDIR, exist_ok=True)
|
||||
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
bpath = os.path.join(BDIR, f"hreflang-pristine-{ts}.json")
|
||||
@@ -245,8 +246,8 @@ def cmd_aplicar(a):
|
||||
json.dump([{"site": s, "id": p["id"], "slug": p["slug"],
|
||||
"codeinjection_head": p.get("codeinjection_head")}
|
||||
for s, p, _ in plan], f, ensure_ascii=False, indent=1)
|
||||
print(f"copia previa: {bpath}\n")
|
||||
fallos = 0
|
||||
log(f"copia previa: {bpath}\n")
|
||||
hechos, fallos = [], []
|
||||
for site, p, nuevo in plan:
|
||||
fd, ppath = tempfile.mkstemp(suffix=".json")
|
||||
with os.fdopen(fd, "w") as f:
|
||||
@@ -254,20 +255,56 @@ def cmd_aplicar(a):
|
||||
r = sh(f'{SITIOS[site]["cli"]} post update {p["id"]} --from-json "{ppath}"')
|
||||
os.unlink(ppath)
|
||||
ok = "Slug:" in r.stdout
|
||||
fallos += 0 if ok else 1
|
||||
print(f" {'✓' if ok else '✗'} [{site}] {p['slug'][:56]}")
|
||||
(hechos if ok else fallos).append((site, p["slug"]))
|
||||
log(f" {'✓' if ok else '✗'} [{site}] {p['slug'][:56]}")
|
||||
if not ok:
|
||||
print(f" {(r.stdout + r.stderr)[:200]}")
|
||||
print(f"\n{len(plan) - fallos}/{len(plan)} aplicados")
|
||||
log(f" {(r.stdout + r.stderr)[:200]}")
|
||||
return hechos, fallos, bpath
|
||||
|
||||
|
||||
def cmd_aplicar(a):
|
||||
plan, avisos, _ = cambios()
|
||||
for w in avisos:
|
||||
print(f" ⚠ {w}")
|
||||
if not plan:
|
||||
print("nada que hacer: ya está todo puesto")
|
||||
return 0
|
||||
hechos, fallos, _ = aplica(plan)
|
||||
print(f"\n{len(hechos)}/{len(plan)} aplicados")
|
||||
return 1 if fallos else 0
|
||||
|
||||
|
||||
def pendientes(parejas):
|
||||
"""Parejas en las que algún lado no está publicado todavía.
|
||||
|
||||
Se separan de los fallos porque NO son un fallo: la cola de agosto tiene
|
||||
cinco espejos programados, y comprobarlos daba diez ✗ de 404 en cada
|
||||
ejecución. Un informe con diez rojos permanentes que no se pueden arreglar
|
||||
es un informe que se deja de leer, y entonces el rojo de verdad tampoco se
|
||||
ve. Mismo criterio que `cambios()`: aquí solo se juzga lo que ya vive.
|
||||
"""
|
||||
en = trae("en", {a for a, _ in parejas})
|
||||
es = trae("es", {b for _, b in parejas})
|
||||
out = {}
|
||||
for a, b in parejas:
|
||||
pa, pb = en.get(a), es.get(b)
|
||||
if not pa or not pb:
|
||||
out[(a, b)] = "no encontrado en Ghost"
|
||||
elif pa["status"] != "published" or pb["status"] != "published":
|
||||
out[(a, b)] = f"{pa['status']}/{pb['status']}"
|
||||
return out
|
||||
|
||||
|
||||
def cmd_comprueba(a):
|
||||
"""La verdad está en la página pública: que Ghost lo guarde no basta."""
|
||||
import urllib.request
|
||||
parejas = lee_parejas()
|
||||
espera = pendientes(parejas)
|
||||
malos = 0
|
||||
for slug_en, slug_es in parejas:
|
||||
if (slug_en, slug_es) in espera:
|
||||
print(f" … [pendiente {espera[(slug_en, slug_es)]}] {slug_en[:46]}")
|
||||
continue
|
||||
for site, slug in (("en", slug_en), ("es", slug_es)):
|
||||
url = f"{SITIOS[site]['base']}/{slug}/"
|
||||
try:
|
||||
@@ -290,7 +327,9 @@ def cmd_comprueba(a):
|
||||
print(f" {'✓' if not mal else '✗'} [{site}] {slug[:52]}")
|
||||
for k, (tiene, debe) in mal.items():
|
||||
print(f" {k}: dice {tiene} — debería ser {debe}")
|
||||
print(f"\n{'✓ todas las páginas declaran las dos versiones' if not malos else f'✗ {malos} con problema'}")
|
||||
vivas = len(parejas) - len(espera)
|
||||
print(f"\n{f'✓ las {vivas} parejas vivas declaran las dos versiones' if not malos else f'✗ {malos} con problema'}"
|
||||
+ (f" · {len(espera)} pendientes de que publique el espejo" if espera else ""))
|
||||
return 1 if malos else 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user