Publier aussi l'inventaire complet des pages
derniers-articles.json s'arrête à vingt entrées : ce qu'il faut pour la section de l'accueil, trop peu pour la page des mises à jour d'alpinux.org, qui veut montrer tout ce que le wiki publie. Le build écrit donc un second fichier, toutes-les-pages.json, sans plafond et avec la rubrique de chaque page — la section de premier niveau de la navigation, celle à laquelle un lecteur range les choses. Un seul parcours de l'historique git produit les deux : le second est la liste entière, le premier son début. derniers-articles.json ne bouge pas d'un octet dans sa forme — mêmes quatre clés, même ordre — pour que l'accueil continue de le lire sans rien savoir de tout ceci. L'en-tête CORS couvre maintenant les deux fichiers, et seulement eux ; le bloc n'est écrit que pour ceux qui manquent au .htaccess, de sorte qu'un second build ne le duplique pas. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYfYUZRcJmmEWJwAxZb4o2
This commit is contained in:
parent
cf92993ec2
commit
c9e2624466
2 changed files with 97 additions and 44 deletions
|
|
@ -181,14 +181,20 @@ Une exécution réussie encadre le build par une ligne `Deploy started` et une l
|
|||
|
||||
---
|
||||
|
||||
## Ce que le build publie pour la page d'accueil
|
||||
## Ce que le build publie pour alpinux.org
|
||||
|
||||
Chaque build écrit, à la racine du site, un fichier **`derniers-articles.json`** : les
|
||||
vingt dernières pages créées ou modifiées, avec leur titre, leur adresse et la date du
|
||||
commit qui a touché leur contenu. C'est ce fichier que lit la page d'accueil
|
||||
d'alpinux.org pour afficher « Le wiki, fraîchement mis à jour ».
|
||||
Chaque build écrit deux fichiers à la racine du site, avec pour chaque page son titre,
|
||||
son adresse et la date du commit qui a touché son contenu :
|
||||
|
||||
Il est produit par le hook `hooks/derniers_articles.py`, déclaré dans `mkdocs.yml`.
|
||||
| Fichier | Ce qu'il porte | Qui le lit |
|
||||
|---|---|---|
|
||||
| `derniers-articles.json` | Les vingt dernières pages créées ou modifiées | L'accueil d'alpinux.org, section « Le wiki, fraîchement mis à jour » |
|
||||
| `toutes-les-pages.json` | Toutes les pages, sans limite, avec leur rubrique | La page des mises à jour d'alpinux.org |
|
||||
|
||||
Le second est la liste entière, le premier son début : un seul parcours de l'historique
|
||||
git les produit tous les deux, et ils portent la même date de génération.
|
||||
|
||||
Ils sont produits par le hook `hooks/derniers_articles.py`, déclaré dans `mkdocs.yml`.
|
||||
Trois choses à savoir :
|
||||
|
||||
- **Un déplacement n'est pas une mise à jour.** Les dates viennent de `git log` avec
|
||||
|
|
@ -198,14 +204,15 @@ Trois choses à savoir :
|
|||
typiquement la redirection laissée derrière un article déplacé — n'apparaît jamais
|
||||
dans la liste.
|
||||
- **Le hook n'échoue jamais.** Sans historique git, il n'écrit rien et le build
|
||||
continue ; l'accueil d'alpinux.org masque alors simplement sa section wiki. Ses
|
||||
continue ; alpinux.org masque alors simplement ce qu'il ne peut pas remplir. Ses
|
||||
messages sont pour cela de niveau *info* et jamais *warning* : le déploiement
|
||||
construit avec `--strict`, où un seul avertissement suffit à interrompre le build,
|
||||
et donc la publication.
|
||||
|
||||
Le même hook pose l'en-tête `Access-Control-Allow-Origin` sur ce seul fichier, au
|
||||
moyen d'un `.htaccess` déposé à la racine du site construit. Sans lui, le navigateur
|
||||
refuserait à alpinux.org le droit de lire une réponse venue de wiki.alpinux.org.
|
||||
Le même hook pose l'en-tête `Access-Control-Allow-Origin` sur ces deux fichiers, et
|
||||
sur eux seuls, au moyen d'un `.htaccess` déposé à la racine du site construit. Sans
|
||||
lui, le navigateur refuserait à alpinux.org le droit de lire une réponse venue de
|
||||
wiki.alpinux.org.
|
||||
|
||||
!!! warning "Deux conditions"
|
||||
**Le clone doit avoir son historique.** Un clone `--depth 1` ne porte qu'un
|
||||
|
|
@ -220,6 +227,7 @@ refuserait à alpinux.org le droit de lire une réponse venue de wiki.alpinux.or
|
|||
```bash
|
||||
# vérifier ce que le dernier build a publié
|
||||
curl -s https://wiki.alpinux.org/derniers-articles.json | head -20
|
||||
curl -s https://wiki.alpinux.org/toutes-les-pages.json | head -20
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -256,4 +264,5 @@ Ouvrez [http://localhost:8000](http://localhost:8000) — MkDocs recharge automa
|
|||
| Déployer à la main (webhook en panne) | `deploy-wiki.sh`, sur le serveur |
|
||||
| Voir le journal des déploiements | `tail -20 /var/log/wiki-deploy.log` |
|
||||
| Vérifier la liste envoyée à l'accueil | `curl -s https://wiki.alpinux.org/derniers-articles.json` |
|
||||
| Vérifier l'inventaire complet publié | `curl -s https://wiki.alpinux.org/toutes-les-pages.json` |
|
||||
| Régénérer les fichiers du logo (poste local) | `python3 scripts/build-assets.py` |
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
"""Publie `derniers-articles.json` : ce que le wiki a écrit ou repris récemment.
|
||||
"""Publie ce que le wiki a écrit ou repris, pour qui l'affiche ailleurs.
|
||||
|
||||
La page d'accueil d'alpinux.org lit ce fichier pour afficher « Le wiki, fraîchement
|
||||
mis à jour ». Il est donc écrit à chaque build, à côté des pages, et servi avec
|
||||
l'en-tête CORS sans lequel un autre domaine ne peut pas le lire.
|
||||
Deux fichiers, écrits à chaque build à côté des pages et servis avec l'en-tête CORS
|
||||
sans lequel un autre domaine ne peut pas les lire :
|
||||
|
||||
- `derniers-articles.json` — les vingt dernières pages touchées, que la page d'accueil
|
||||
d'alpinux.org lit pour sa section « Le wiki, fraîchement mis à jour » ;
|
||||
- `toutes-les-pages.json` — le même inventaire, sans limite de nombre et avec la
|
||||
rubrique de chaque page, pour la page des mises à jour d'alpinux.org.
|
||||
|
||||
Les deux sortent du même calcul : le second est la liste entière, le premier son
|
||||
début.
|
||||
|
||||
Les dates viennent de git, pas du système de fichiers : un `git clone` repose tous les
|
||||
fichiers à la même seconde, et une page recopiée depuis Obsidian aurait l'air neuve.
|
||||
|
|
@ -20,10 +27,11 @@ from urllib.parse import urljoin
|
|||
|
||||
from mkdocs.plugins import log
|
||||
|
||||
FICHIER = "derniers-articles.json"
|
||||
FICHIER_RECENTS = "derniers-articles.json"
|
||||
FICHIER_TOUTES = "toutes-les-pages.json"
|
||||
|
||||
# Ce que le fichier porte au plus. L'accueil n'en affiche que six, mais le fichier
|
||||
# sert aussi à qui voudrait en faire autre chose.
|
||||
# Ce que le premier fichier porte au plus. L'accueil n'en affiche que six, mais le
|
||||
# fichier sert aussi à qui voudrait en faire autre chose. Le second ne plafonne pas.
|
||||
NB_MAX = 20
|
||||
|
||||
# En deçà, un article est annoncé comme « publié » plutôt que « mis à jour ».
|
||||
|
|
@ -34,10 +42,24 @@ EXCLUS = {"index.md"}
|
|||
|
||||
_pages: dict[str, dict[str, str]] = {}
|
||||
_dans_la_nav: set[str] = set()
|
||||
_rubriques: dict[str, str] = {}
|
||||
|
||||
|
||||
def _rubrique_par_page(items, rubrique=""):
|
||||
"""Associe chaque page de la navigation au titre de la section qui la porte.
|
||||
|
||||
On garde la section de premier niveau — « Guides », « Contribuer » — plutôt que la
|
||||
plus proche : c'est l'échelle à laquelle un lecteur range les pages.
|
||||
"""
|
||||
for item in items:
|
||||
if item.is_section:
|
||||
yield from _rubrique_par_page(item.children, rubrique or item.title)
|
||||
elif item.is_page:
|
||||
yield item.file.src_uri, rubrique
|
||||
|
||||
|
||||
def on_nav(nav, config, files):
|
||||
"""Retient les pages de la navigation.
|
||||
"""Retient les pages de la navigation, et sous quelle rubrique elles vivent.
|
||||
|
||||
Ce qui en est absent — les redirections laissées derrière un article déplacé, par
|
||||
exemple — n'a pas à être annoncé comme une nouveauté.
|
||||
|
|
@ -45,6 +67,9 @@ def on_nav(nav, config, files):
|
|||
_dans_la_nav.clear()
|
||||
_dans_la_nav.update(page.file.src_uri for page in nav.pages)
|
||||
|
||||
_rubriques.clear()
|
||||
_rubriques.update(_rubrique_par_page(nav.items))
|
||||
|
||||
|
||||
def on_page_context(context, page, config, nav):
|
||||
"""Retient le titre et l'URL de chaque page rendue."""
|
||||
|
|
@ -106,24 +131,30 @@ def _historique(depot: Path, dossier: str) -> tuple[dict, dict]:
|
|||
return modifiees, creees
|
||||
|
||||
|
||||
def _autoriser_la_lecture_croisee(site_dir: Path) -> None:
|
||||
"""Pose l'en-tête CORS sur le seul fichier qui en a besoin.
|
||||
def _autoriser_la_lecture_croisee(site_dir: Path, fichiers: list[str]) -> None:
|
||||
"""Pose l'en-tête CORS sur les seuls fichiers qui en ont besoin.
|
||||
|
||||
Le fichier est lu depuis alpinux.org : sans cet en-tête, le navigateur refuse la
|
||||
Ils sont lus depuis alpinux.org : sans cet en-tête, le navigateur refuse la
|
||||
réponse et la section reste masquée. Le bloc s'ajoute à un `.htaccess` déjà là
|
||||
plutôt que de le remplacer — on ne sait pas ce qu'il porte d'autre.
|
||||
plutôt que de le remplacer — on ne sait pas ce qu'il porte d'autre — et n'est
|
||||
écrit que pour les fichiers qui n'y figurent pas encore.
|
||||
"""
|
||||
bloc = (
|
||||
"<IfModule mod_headers.c>\n"
|
||||
f' <Files "{FICHIER}">\n'
|
||||
' Header set Access-Control-Allow-Origin "*"\n'
|
||||
" </Files>\n"
|
||||
"</IfModule>\n"
|
||||
)
|
||||
fichier = site_dir / ".htaccess"
|
||||
existant = fichier.read_text(encoding="utf-8") if fichier.exists() else ""
|
||||
if FICHIER in existant:
|
||||
|
||||
manquants = [nom for nom in fichiers if nom not in existant]
|
||||
if not manquants:
|
||||
return
|
||||
|
||||
bloc = "<IfModule mod_headers.c>\n"
|
||||
for nom in manquants:
|
||||
bloc += (
|
||||
f' <Files "{nom}">\n'
|
||||
' Header set Access-Control-Allow-Origin "*"\n'
|
||||
" </Files>\n"
|
||||
)
|
||||
bloc += "</IfModule>\n"
|
||||
|
||||
separateur = "\n" if existant and not existant.endswith("\n") else ""
|
||||
fichier.write_text(existant + separateur + bloc, encoding="utf-8")
|
||||
|
||||
|
|
@ -144,7 +175,7 @@ def on_post_build(config):
|
|||
log.info("derniers-articles : clone superficiel, les dates ne sont pas fiables")
|
||||
|
||||
seuil = datetime.now(timezone.utc) - timedelta(days=JOURS_NOUVEAU)
|
||||
articles = []
|
||||
pages = []
|
||||
|
||||
for chemin, modifiee in modifiees.items():
|
||||
prefixe = docs_dir.name + "/"
|
||||
|
|
@ -162,30 +193,43 @@ def on_post_build(config):
|
|||
except ValueError:
|
||||
nouveau = False
|
||||
|
||||
articles.append({
|
||||
"titre": page["titre"],
|
||||
"url": page["url"],
|
||||
"date": creee if nouveau else modifiee,
|
||||
"nouveau": nouveau,
|
||||
pages.append({
|
||||
"titre": page["titre"],
|
||||
"url": page["url"],
|
||||
"date": creee if nouveau else modifiee,
|
||||
"nouveau": nouveau,
|
||||
"rubrique": _rubriques.get(src_uri, ""),
|
||||
})
|
||||
|
||||
if not articles:
|
||||
if not pages:
|
||||
log.info("derniers-articles : aucune page datée, fichier non écrit")
|
||||
return
|
||||
|
||||
articles.sort(key=lambda article: article["date"], reverse=True)
|
||||
articles = articles[:NB_MAX]
|
||||
pages.sort(key=lambda page: page["date"], reverse=True)
|
||||
|
||||
genere = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
||||
contenu = json.dumps({"genere": genere, "articles": articles},
|
||||
ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
# L'accueil lit ce fichier depuis longtemps : il garde ses quatre clés, sans la
|
||||
# rubrique dont il n'a que faire.
|
||||
recents = [
|
||||
{clef: page[clef] for clef in ("titre", "url", "date", "nouveau")}
|
||||
for page in pages[:NB_MAX]
|
||||
]
|
||||
|
||||
ecrits = {
|
||||
FICHIER_RECENTS: {"genere": genere, "articles": recents},
|
||||
FICHIER_TOUTES: {"genere": genere, "pages": pages},
|
||||
}
|
||||
|
||||
try:
|
||||
(site_dir / FICHIER).write_text(contenu, encoding="utf-8")
|
||||
_autoriser_la_lecture_croisee(site_dir)
|
||||
for nom, contenu in ecrits.items():
|
||||
texte = json.dumps(contenu, ensure_ascii=False, indent=2) + "\n"
|
||||
(site_dir / nom).write_text(texte, encoding="utf-8")
|
||||
_autoriser_la_lecture_croisee(site_dir, list(ecrits))
|
||||
except OSError as erreur:
|
||||
log.info("derniers-articles : écriture impossible (%s), fichier non écrit",
|
||||
erreur)
|
||||
return
|
||||
|
||||
log.info("derniers-articles : %d article(s) publié(s) dans %s", len(articles), FICHIER)
|
||||
log.info("derniers-articles : %d page(s) publiée(s) dans %s, %d dans %s",
|
||||
len(pages), FICHIER_TOUTES, len(recents), FICHIER_RECENTS)
|
||||
|
|
|
|||
Loading…
Reference in a new issue