CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus gros, sont passes au generateur — chargement, rendu et sauvegarde. L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25 applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe un formulaire genere d un formulaire qui en a l air — un champ visible a l ecran et perdu en silence a l enregistrement serait le pire des deux mondes. test_rendu_gui.py le mesure a chaque make prouver. TROIS DEFAUTS TROUVES EN CHEMIN. Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il n existe aucun defaut fixe — deriver_ressources calcule depuis les roles portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere faux fait croire qu on connait la valeur. Le schema nomme le champ derive, et l ecran montre la valeur reelle de cet hote. L option vide d un select dit desormais ce qu elle produira : « (defaut : asgard) ». Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs : elle lisait encore data.nomenclature. Elle n avait jamais leve parce que la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne voit que les chemins vivants : verifier_gui.py fait donc aussi une verification STATIQUE, qui voit ce qui dort. La validation client s accrochait a data-v, pose a la main sur trois champs. Le formulaire genere l aurait perdu et la validation serait passee au vert sur ZERO champ. Le generateur marque chaque controle, et la sauvegarde refuse si elle n en inspecte aucun. DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la matrice des integrations montre les universelles et les exemptions, et l editeur de liens contraint le role a meta/liens.yml. Le generateur s efface plutot que de remplacer un editeur qui en sait plus que lui. LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur. make prouver : CONFORME, 61 OK, 0 echec, 1 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
107 lines
4.6 KiB
Python
107 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Verifie la syntaxe du JavaScript embarque dans inventory_gui.py.
|
|
|
|
Garde-fou : py_compile ne voit pas le JS (il est dans une chaine Python).
|
|
Une seule erreur de syntaxe JS casse toute la page. On extrait le JS et on le
|
|
passe a `node --check`. Si node est absent, la verification est sautee (signale).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
import inventory_gui as g # noqa: E402
|
|
|
|
|
|
# Les noms qui RESSEMBLENT a un global sans en etre un. `data` est le cas fondateur :
|
|
# chaque `charger*`/`sauvegarder*` en declare un LOCAL, si bien que le nom se lit partout
|
|
# comme s'il etait disponible — et ne l'est pas.
|
|
NON_GLOBALES = ("data",)
|
|
|
|
# Une fonction de premier niveau du <script> : ` function nom(args) {` ou sa variante
|
|
# `async`. Le GUI les ecrit toutes a quatre espaces d'indentation.
|
|
_FONCTION = re.compile(r"^ {4}(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)", re.M)
|
|
|
|
|
|
def variables_orphelines(js: str) -> list[str]:
|
|
"""Fonctions qui LISENT un nom surveille sans le declarer ni le recevoir.
|
|
|
|
POURQUOI CETTE GARDE (2026-09-08). `node --check` ne voit que la syntaxe. Le
|
|
formulaire genere des bases lisait `data.schema` — or `data` n'est pas un global de
|
|
cette page : c'est une const locale de `charger()`. Syntaxe parfaite,
|
|
`ReferenceError` a l'ouverture, et personne pour le dire.
|
|
|
|
`test_rendu_gui.py` l'attrape maintenant en EXECUTANT les vues — mais seulement les
|
|
chemins vivants. La preuve en est qu'une SECONDE occurrence dormait dans
|
|
`sourceDeValeurs` : la vue Serveurs, seule a emprunter cette source, avait encore un
|
|
formulaire ecrit a la main. Elle a leve a la seconde ou le generateur l'a prise.
|
|
|
|
Cette verification-ci est statique : elle voit aussi ce qui dort.
|
|
|
|
Sa limite, dite franchement : elle raisonne par fonction de premier niveau et par
|
|
motif, pas par portee lexicale. Une fonction imbriquee qui hériterait du `data` de
|
|
sa parente serait signalee a tort — on la nommerait alors autrement, ce qui est de
|
|
toute facon plus lisible.
|
|
"""
|
|
orphelins = []
|
|
bornes = [(m.start(), m.group(1), m.group(2)) for m in _FONCTION.finditer(js)]
|
|
for i, (debut, nom, args) in enumerate(bornes):
|
|
fin = bornes[i + 1][0] if i + 1 < len(bornes) else len(js)
|
|
corps = js[debut:fin]
|
|
parametres = {a.strip().split("=")[0].strip() for a in args.split(",") if a.strip()}
|
|
for surveille in NON_GLOBALES:
|
|
if surveille in parametres:
|
|
continue
|
|
# La declaration peut etre un SECOND declarateur : `const di = …, data = …`.
|
|
# Sans ce cas, `dessinerReseau()` etait signalee a tort — et un faux positif
|
|
# dans une garde finit toujours par la faire desactiver.
|
|
if re.search(rf"\b(?:const|let|var)\b[^;\n]*?(?<![\w.$]){surveille}\s*=", corps):
|
|
continue
|
|
for ligne in corps.split("\n"):
|
|
nue = ligne.strip()
|
|
if nue.startswith("//") or nue.startswith("*"):
|
|
continue
|
|
if re.search(rf"(?<![\w.$]){surveille}\s*[.\[]", nue):
|
|
orphelins.append(f"{nom}() lit `{surveille}` : {nue[:88]}")
|
|
break
|
|
return orphelins
|
|
|
|
|
|
def main() -> int:
|
|
if not shutil.which("node"):
|
|
print("node absent : verification de la syntaxe JS du GUI sautee.")
|
|
return 0
|
|
html = g.HTML.replace("__JETON__", "TEST")
|
|
blocs = re.findall(r"<script>(.*?)</script>", html, re.S)
|
|
if not blocs:
|
|
print("erreur: aucun bloc <script> trouve dans le GUI.", file=sys.stderr)
|
|
return 1
|
|
with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False, encoding="utf-8") as fichier:
|
|
fichier.write("\n".join(blocs))
|
|
chemin = fichier.name
|
|
resultat = subprocess.run(["node", "--check", chemin], capture_output=True, text=True)
|
|
if resultat.returncode != 0:
|
|
print(resultat.stderr, file=sys.stderr)
|
|
return 1
|
|
|
|
orphelins = variables_orphelines("\n".join(blocs))
|
|
if orphelins:
|
|
print("erreur: des fonctions lisent une variable qu'elles n'ont pas :", file=sys.stderr)
|
|
for ligne in orphelins:
|
|
print(f" - {ligne}", file=sys.stderr)
|
|
print(" (declarer un global, ou passer la valeur en parametre)", file=sys.stderr)
|
|
return 1
|
|
print(f"JS du GUI : syntaxe valide (node --check), {len(NON_GLOBALES)} nom(s) surveille(s) "
|
|
f"sans reference orpheline.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|