[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
|
|
"""Predict which customized SCSS will break the next version bump.
|
|
|
|
|
|
|
|
|
|
|
|
Background
|
|
|
|
|
|
----------
|
|
|
|
|
|
Customizing a website writes SCSS into ``ir_attachment``: files whose URL
|
|
|
|
|
|
carries ``.custom.``. That copy is frozen the day it is written, and it keeps
|
|
|
|
|
|
using the variables of THAT version.
|
|
|
|
|
|
|
|
|
|
|
|
A module can rename its variables between two Odoo versions. Measured on a
|
|
|
|
|
|
real database: ``website/static/src/scss/primary_variables.scss`` declared
|
|
|
|
|
|
``$o-theme-font-number`` in 12.0 and replaced the whole mechanism in 13.0 with
|
|
|
|
|
|
a ``$o-website-values-palettes`` map. A customization written in 2020 still
|
|
|
|
|
|
asked for the old name. The bundle then stops on::
|
|
|
|
|
|
|
|
|
|
|
|
Error: Undefined variable: "$o-theme-font-number".
|
|
|
|
|
|
This error occured while compiling the bundle 'web.assets_frontend'
|
|
|
|
|
|
|
|
|
|
|
|
So the rule is the same as for COW views:
|
|
|
|
|
|
|
|
|
|
|
|
a customization breaks when what it USES stops being DEFINED between
|
|
|
|
|
|
version N and version N+1.
|
|
|
|
|
|
|
|
|
|
|
|
Why not just start Odoo and open a page
|
|
|
|
|
|
---------------------------------------
|
|
|
|
|
|
Because that only tells you *after* the bump, on a page that renders a
|
|
|
|
|
|
« Style error » with no name attached: not which attachment, not which
|
|
|
|
|
|
variable, not since when. And a page has to be reached at all — a broken
|
|
|
|
|
|
frontend bundle is exactly when it is not.
|
|
|
|
|
|
|
|
|
|
|
|
Reading the stored SCSS against the target sources answers before the bump,
|
|
|
|
|
|
in a second, and names the attachment. Nothing is written here.
|
|
|
|
|
|
|
|
|
|
|
|
What it can miss
|
|
|
|
|
|
----------------
|
|
|
|
|
|
Which files a bundle really contains is decided by Odoo at runtime. This
|
|
|
|
|
|
compares against every SCSS of the INSTALLED modules of the target version,
|
|
|
|
|
|
which is wider: a variable defined in a module file that the bundle does not
|
|
|
|
|
|
include would be counted as defined. That direction is deliberate — it under-
|
|
|
|
|
|
reports rather than crying wolf on every customization.
|
|
|
|
|
|
|
|
|
|
|
|
Exit codes: 0 nothing to report, 1 customizations at risk, 2 tool failure.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
import glob
|
|
|
|
|
|
import os
|
|
|
|
|
|
import re
|
|
|
|
|
|
import subprocess
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
sys.path.append(
|
|
|
|
|
|
os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
from script.todo.todo_i18n import t
|
|
|
|
|
|
except Exception: # pragma: no cover - repli si i18n indisponible
|
|
|
|
|
|
|
|
|
|
|
|
def t(key: str) -> str:
|
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
|
try:
|
|
|
|
|
|
from script.todo import auto_ask
|
|
|
|
|
|
except Exception: # pragma: no cover - repli si le pilote est absent
|
|
|
|
|
|
auto_ask = None
|
|
|
|
|
|
|
|
|
|
|
|
|
[FIX] migration: never ask a question the terminal cannot show
The theme uninstaller ends by asking what to do with the leftovers, and
the migration ran it through the capturing executor. Its stdout was a
pipe, Python buffers by blocks, so the prompt stayed invisible while the
process waited. It reads as a freeze: you press Enter blind, the first
keystroke answers unseen and the extra ones fall into the next question.
Two locks, because one is not enough. The migration runs the script on
the real terminal. And the three tools that ask now require BOTH ends:
something to read the answer from, and something to show the question
on. Guarding on stdin alone was the bug — measured, stdin tty=True with
stdout tty=False, and the question was asked into a pipe.
--- FR ---
[FIX] migration : ne jamais poser une question que le terminal ne montre pas
Le désinstalleur de thème finit par demander quoi faire des restes, et
la migration le lançait par l'exécuteur qui capture. Sa sortie partait
dans un tube, Python bufferise par blocs, et l'invite restait invisible
pendant l'attente. Cela se lit comme un blocage : on tape Entrée à
l'aveugle, la première frappe répond sans être vue et les suivantes
tombent dans la question d'après.
Deux verrous, car un seul ne suffit pas. La migration lance le script
sur le vrai terminal. Et les trois outils qui questionnent exigent
désormais les DEUX bouts : de quoi lire la réponse, et de quoi montrer
la question. Ne garder que stdin était le défaut — mesuré, stdin
tty=True et stdout tty=False, la question partait dans un tube.
Assisted-by: Claude Opus 5
2026-08-17 02:41:40 -04:00
|
|
|
|
def can_ask():
|
|
|
|
|
|
"""Peut-on poser une question ICI ?
|
|
|
|
|
|
|
|
|
|
|
|
Il faut deux choses, pas une : de quoi LIRE la réponse (stdin sur un
|
|
|
|
|
|
terminal) et de quoi MONTRER la question (stdout aussi). Ne tester que
|
|
|
|
|
|
stdin laisse poser une invite qui part dans un tube : elle reste en
|
|
|
|
|
|
tampon, invisible, pendant que le processus attend — on croit à un
|
|
|
|
|
|
blocage et l'on tape Entrée à l'aveugle.
|
|
|
|
|
|
"""
|
|
|
|
|
|
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
# Une variable SCSS commence par $ ; celles dont le nom commence par un tiret
|
|
|
|
|
|
# sont locales au fichier par convention Odoo ($-seen, $-font-numbers) et se
|
|
|
|
|
|
# définissent toujours au-dessus de leur usage.
|
|
|
|
|
|
RE_ANY = re.compile(r"\$([a-zA-Z][\w-]*)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def used_names(content):
|
|
|
|
|
|
"""Noms LUS par le fichier, sans les liaisons.
|
|
|
|
|
|
|
|
|
|
|
|
Un « $nom » suivi d'un deux-points est toujours une liaison — déclaration,
|
|
|
|
|
|
valeur par défaut, ou argument nommé d'un @include — jamais la lecture
|
|
|
|
|
|
d'une variable. Sans cette réserve, « @include o-position-absolute(
|
|
|
|
|
|
$right: 50%) » se lisait comme l'usage d'un $right inexistant.
|
|
|
|
|
|
|
|
|
|
|
|
Le test se fait ici et non par une négation dans le motif : une négation
|
|
|
|
|
|
en tête de motif fait rétrograder le nom pour satisfaire la condition, et
|
|
|
|
|
|
rapporte « $botto » au lieu d'écarter « $bottom ». Mesuré.
|
|
|
|
|
|
"""
|
|
|
|
|
|
names = set()
|
|
|
|
|
|
for match in RE_ANY.finditer(content):
|
|
|
|
|
|
if content[match.end() :].lstrip(" \t").startswith(":"):
|
|
|
|
|
|
continue
|
|
|
|
|
|
names.add(match.group(1))
|
|
|
|
|
|
return names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RE_DEF = re.compile(r"^\s*\$([a-zA-Z][\w-]*)\s*:", re.M)
|
|
|
|
|
|
|
|
|
|
|
|
# Une variable peut aussi être LIÉE sans être définie : paramètre de mixin ou
|
|
|
|
|
|
# de fonction, variable de boucle. Les ignorer donnait six faux positifs sur
|
|
|
|
|
|
# le seul fichier mesuré — $bottom, $counter, $off, $on, $right, $value — et
|
|
|
|
|
|
# un détecteur qui crie à tort finit ignoré, ce qui vaut moins que rien.
|
|
|
|
|
|
RE_PARAM = re.compile(r"@(?:mixin|function)\s+[\w-]+\s*\(([^)]*)\)")
|
|
|
|
|
|
RE_EACH = re.compile(r"@each\s+([^i]+?)\s+in\s", re.S)
|
|
|
|
|
|
RE_FOR = re.compile(r"@for\s+\$([\w-]+)\s+from\s")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bound_names(content):
|
|
|
|
|
|
"""Noms liés par le fichier lui-même, hors définitions « $x: … »."""
|
|
|
|
|
|
names = set()
|
|
|
|
|
|
for group in RE_PARAM.findall(content):
|
|
|
|
|
|
names.update(RE_ANY.findall(group))
|
|
|
|
|
|
for group in RE_EACH.findall(content):
|
|
|
|
|
|
names.update(RE_ANY.findall(group))
|
|
|
|
|
|
names.update(RE_FOR.findall(content))
|
|
|
|
|
|
return names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_psql(database, sql):
|
|
|
|
|
|
"""Interroger la base, lecture seule garantie par le serveur."""
|
|
|
|
|
|
env = os.environ.copy()
|
|
|
|
|
|
env["PGOPTIONS"] = (
|
|
|
|
|
|
"-c default_transaction_read_only=on -c statement_timeout=60000"
|
|
|
|
|
|
)
|
|
|
|
|
|
env["PSQLRC"] = ""
|
|
|
|
|
|
done = subprocess.run(
|
|
|
|
|
|
[
|
|
|
|
|
|
"psql",
|
|
|
|
|
|
"-X",
|
|
|
|
|
|
"-w",
|
|
|
|
|
|
"-v",
|
|
|
|
|
|
"ON_ERROR_STOP=1",
|
|
|
|
|
|
"-d",
|
|
|
|
|
|
database,
|
|
|
|
|
|
"-tAF",
|
|
|
|
|
|
"\x1f",
|
|
|
|
|
|
"-c",
|
|
|
|
|
|
sql,
|
|
|
|
|
|
],
|
|
|
|
|
|
capture_output=True,
|
|
|
|
|
|
text=True,
|
|
|
|
|
|
env=env,
|
|
|
|
|
|
)
|
|
|
|
|
|
if done.returncode:
|
|
|
|
|
|
raise RuntimeError(done.stderr.strip() or "psql failed")
|
|
|
|
|
|
return [line.split("\x1f") for line in done.stdout.splitlines() if line]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def filestore_dir(database, override=None):
|
|
|
|
|
|
if override:
|
|
|
|
|
|
return override
|
|
|
|
|
|
return os.path.join(
|
|
|
|
|
|
os.path.expanduser("~"),
|
|
|
|
|
|
".local",
|
|
|
|
|
|
"share",
|
|
|
|
|
|
"Odoo",
|
|
|
|
|
|
"filestore",
|
|
|
|
|
|
database,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def custom_scss(database, filestore):
|
|
|
|
|
|
"""[(id, url, contenu)] pour chaque SCSS personnalisé lisible."""
|
|
|
|
|
|
rows = run_psql(
|
|
|
|
|
|
database,
|
|
|
|
|
|
"SELECT id, url, COALESCE(store_fname, ''),"
|
|
|
|
|
|
" COALESCE(encode(db_datas, 'escape'), '') FROM ir_attachment"
|
|
|
|
|
|
" WHERE url LIKE '%.custom.%' AND url NOT LIKE '%.css'"
|
|
|
|
|
|
" ORDER BY id;",
|
|
|
|
|
|
)
|
|
|
|
|
|
lst = []
|
|
|
|
|
|
for row in rows:
|
|
|
|
|
|
if len(row) < 4:
|
|
|
|
|
|
continue
|
|
|
|
|
|
att_id, url, store_fname, db_datas = row[0], row[1], row[2], row[3]
|
|
|
|
|
|
content = ""
|
|
|
|
|
|
if store_fname:
|
|
|
|
|
|
path = os.path.join(filestore, store_fname)
|
|
|
|
|
|
if os.path.isfile(path):
|
|
|
|
|
|
with open(path, "r", encoding="utf-8", errors="replace") as h:
|
|
|
|
|
|
content = h.read()
|
|
|
|
|
|
elif db_datas:
|
|
|
|
|
|
content = db_datas
|
|
|
|
|
|
if content:
|
|
|
|
|
|
lst.append((int(att_id), url, content))
|
|
|
|
|
|
return lst
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def installed_modules(database):
|
|
|
|
|
|
return {
|
|
|
|
|
|
row[0]
|
|
|
|
|
|
for row in run_psql(
|
|
|
|
|
|
database,
|
|
|
|
|
|
"SELECT name FROM ir_module_module WHERE state = 'installed';",
|
|
|
|
|
|
)
|
|
|
|
|
|
if row and row[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def defined_in_sources(version_dir, lst_module):
|
|
|
|
|
|
"""Variables définies par les SCSS des modules installés de la cible.
|
|
|
|
|
|
|
|
|
|
|
|
Le nom du module est le répertoire qui contient « static » : c'est ce qui
|
|
|
|
|
|
permet de ne pas compter les variables d'un module absent de la base, dont
|
|
|
|
|
|
aucune ligne n'atteindra jamais un bundle.
|
|
|
|
|
|
"""
|
|
|
|
|
|
defined = set()
|
|
|
|
|
|
pattern = os.path.join(version_dir, "**", "static", "**", "*.scss")
|
|
|
|
|
|
for path in glob.iglob(pattern, recursive=True):
|
|
|
|
|
|
parts = path.split(os.sep)
|
|
|
|
|
|
try:
|
|
|
|
|
|
module = parts[parts.index("static") - 1]
|
|
|
|
|
|
except (ValueError, IndexError):
|
|
|
|
|
|
continue
|
|
|
|
|
|
if module not in lst_module:
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(path, "r", encoding="utf-8", errors="replace") as handle:
|
|
|
|
|
|
defined.update(RE_DEF.findall(handle.read()))
|
|
|
|
|
|
except OSError:
|
|
|
|
|
|
continue
|
|
|
|
|
|
return defined
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
def module_file(base_url, version_dir):
|
|
|
|
|
|
"""Le fichier du module que la personnalisation masque, ou None.
|
|
|
|
|
|
|
|
|
|
|
|
Le premier segment de l'URL est le nom du module ; le reste est son
|
|
|
|
|
|
chemin. C'est ce fichier que reset_asset rendrait, donc c'est contre lui
|
|
|
|
|
|
que se lit ce que la copie a réellement changé.
|
|
|
|
|
|
"""
|
|
|
|
|
|
parts = base_url.strip("/").split("/")
|
|
|
|
|
|
if len(parts) < 2:
|
|
|
|
|
|
return None
|
|
|
|
|
|
pattern = os.path.join(version_dir, "**", parts[0], *parts[1:])
|
|
|
|
|
|
for candidate in glob.iglob(pattern, recursive=True):
|
|
|
|
|
|
if os.path.isfile(candidate):
|
|
|
|
|
|
return candidate
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
def analyse(database, version_dir, filestore=None):
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
"""Un dictionnaire par personnalisation à risque.
|
|
|
|
|
|
|
|
|
|
|
|
Le contenu et le fichier du module y sont joints : l'appelant qui veut
|
|
|
|
|
|
montrer l'écart ne doit pas relire la base ni retrouver le chemin.
|
|
|
|
|
|
"""
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
filestore = filestore_dir(database, filestore)
|
|
|
|
|
|
lst_module = installed_modules(database)
|
|
|
|
|
|
defined = defined_in_sources(version_dir, lst_module)
|
|
|
|
|
|
lst_finding = []
|
|
|
|
|
|
for att_id, url, content in custom_scss(database, filestore):
|
|
|
|
|
|
own = set(RE_DEF.findall(content)) | bound_names(content)
|
|
|
|
|
|
missing = sorted(
|
|
|
|
|
|
{
|
|
|
|
|
|
name
|
|
|
|
|
|
for name in used_names(content)
|
|
|
|
|
|
if name not in own and name not in defined
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
if not missing:
|
|
|
|
|
|
continue
|
|
|
|
|
|
base_url, bundle = split_custom_url(url)
|
|
|
|
|
|
path = module_file(base_url, version_dir)
|
|
|
|
|
|
module_content = ""
|
|
|
|
|
|
if path:
|
|
|
|
|
|
with open(path, "r", encoding="utf-8", errors="replace") as handle:
|
|
|
|
|
|
module_content = handle.read()
|
|
|
|
|
|
lst_finding.append(
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": att_id,
|
|
|
|
|
|
"url": url,
|
|
|
|
|
|
"missing": missing,
|
|
|
|
|
|
"custom": content,
|
|
|
|
|
|
"base_url": base_url,
|
|
|
|
|
|
"bundle": bundle,
|
|
|
|
|
|
"module_path": path,
|
|
|
|
|
|
"module_content": module_content,
|
|
|
|
|
|
"version_dir": version_dir,
|
|
|
|
|
|
"database": database,
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
return lst_finding
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
def render_diff(finding):
|
|
|
|
|
|
"""Ce que la copie a changé, comparée au fichier du module de la cible.
|
|
|
|
|
|
|
|
|
|
|
|
C'est la seule chose qu'abandonner la copie ferait perdre. Sans elle, on
|
|
|
|
|
|
répond « oui, réinitialise » sans savoir si l'on jette trois lignes ou
|
|
|
|
|
|
une page entière.
|
|
|
|
|
|
"""
|
|
|
|
|
|
import difflib
|
|
|
|
|
|
|
|
|
|
|
|
lines = [
|
|
|
|
|
|
f"── id={finding['id']} {finding['url']} ──",
|
|
|
|
|
|
"",
|
|
|
|
|
|
f" {t('Missing:')} "
|
|
|
|
|
|
+ ", ".join("$" + name for name in finding["missing"]),
|
|
|
|
|
|
"",
|
|
|
|
|
|
]
|
|
|
|
|
|
if not finding["module_path"]:
|
|
|
|
|
|
lines += [
|
|
|
|
|
|
f" {t('No module file of that name in')}"
|
|
|
|
|
|
f" {finding['version_dir']} :",
|
|
|
|
|
|
f" {t('the target no longer ships it, so there is nothing to')}",
|
|
|
|
|
|
f" {t('fall back on. Read the copy before dropping it.')}",
|
|
|
|
|
|
]
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
diff = list(
|
|
|
|
|
|
difflib.unified_diff(
|
|
|
|
|
|
finding["module_content"].splitlines(),
|
|
|
|
|
|
finding["custom"].splitlines(),
|
|
|
|
|
|
fromfile=finding["module_path"],
|
|
|
|
|
|
tofile=f"custom id={finding['id']}",
|
|
|
|
|
|
lineterm="",
|
|
|
|
|
|
n=2,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
if len(diff) <= 2:
|
|
|
|
|
|
lines.append(f" {t('The copy is identical to the module file.')}")
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
lines += [f" {line}" for line in diff]
|
|
|
|
|
|
plus = sum(1 for x in diff if x[:1] == "+" and not x.startswith("+++"))
|
|
|
|
|
|
minus = sum(1 for x in diff if x[:1] == "-" and not x.startswith("---"))
|
|
|
|
|
|
lines += [
|
|
|
|
|
|
"",
|
|
|
|
|
|
f" +{plus}/-{minus} {t('line(s): that is what resetting gives up.')}",
|
|
|
|
|
|
]
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
[FIX] migration: reset the stale SCSS after the bump, not before
The fix was offered while the checkout was still on the previous
version. Answering « a » ran reset_asset in an Odoo 12 shell, which has
no web_editor.assets: KeyError, nothing changed, and the migration went
on to break at the next bump — measured on a real run.
Predicting early is right; fixing early is not. The early call is now
--report-only, and a second call comes after the bump, on the upgraded
database, where the checkout can do it. The tool also refuses on its
own: it reads the checkout sources for reset_asset rather than trusting
a version number, and says when to come back.
--- FR ---
[FIX] migration : réinitialiser le SCSS périmé après le palier, pas avant
La correction était proposée alors que le checkout était encore sur la
version précédente. Répondre « a » lançait reset_asset dans un shell
Odoo 12, sans web_editor.assets : KeyError, rien de modifié, et la
migration continuait jusqu'à casser au palier suivant. Mesuré.
Prédire tôt est juste ; corriger tôt ne l'est pas. L'appel précoce est
désormais --report-only, et un second vient après le palier, sur la
base montée de version, là où le checkout sait le faire. L'outil refuse
aussi de lui-même : il cherche reset_asset dans les sources du checkout
plutôt que de se fier à un numéro, et dit quand revenir.
Assisted-by: Claude Opus 5
(cherry picked from commit 13d4d0a33c63efcbafb5fb29313a5377ba5f508e)
2026-08-15 16:34:26 -04:00
|
|
|
|
def running_odoo_dir():
|
|
|
|
|
|
"""Le répertoire de la version qui répondra au shell, d'après le checkout.
|
|
|
|
|
|
|
|
|
|
|
|
Ce n'est PAS la cible : au moment où l'on prédit ce que le palier
|
|
|
|
|
|
cassera, le checkout est encore sur la version d'avant. C'est elle qui
|
|
|
|
|
|
exécutera reset_asset — ou ne le saura pas.
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(".odoo-version", "r", encoding="utf-8") as handle:
|
|
|
|
|
|
return "odoo" + handle.read().strip()
|
|
|
|
|
|
except OSError:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reset_supported(odoo_dir=None):
|
|
|
|
|
|
"""Cette version sait-elle faire reset_asset ?
|
|
|
|
|
|
|
|
|
|
|
|
Mesuré : `web_editor.assets` et `reset_asset` n'existent qu'à partir de
|
|
|
|
|
|
13.0. Lancé sous odoo12.0, l'appel lève « KeyError: 'web_editor.assets' »
|
|
|
|
|
|
et ne change rien — c'est arrivé sur une vraie migration, et le correctif
|
|
|
|
|
|
a été cru appliqué alors qu'il avait échoué.
|
|
|
|
|
|
|
|
|
|
|
|
On regarde les sources plutôt qu'un numéro : c'est ce qui répondra.
|
|
|
|
|
|
"""
|
|
|
|
|
|
odoo_dir = odoo_dir or running_odoo_dir()
|
|
|
|
|
|
if not odoo_dir or not os.path.isdir(odoo_dir):
|
|
|
|
|
|
return True # Rien pour trancher : ne pas bloquer sur une supposition.
|
|
|
|
|
|
pattern = os.path.join(odoo_dir, "**", "web_editor", "models", "assets.py")
|
|
|
|
|
|
for path in glob.iglob(pattern, recursive=True):
|
|
|
|
|
|
with open(path, "r", encoding="utf-8", errors="replace") as handle:
|
|
|
|
|
|
if "def reset_asset" in handle.read():
|
|
|
|
|
|
return True
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def too_early_message(odoo_dir, database):
|
|
|
|
|
|
"""Pourquoi on ne peut pas encore corriger, et quand on pourra."""
|
|
|
|
|
|
return (
|
|
|
|
|
|
f"⛔ {t('Resetting needs Odoo 13.0 or later; this checkout is on')}"
|
|
|
|
|
|
f" {odoo_dir or '?'}.\n"
|
|
|
|
|
|
f" {t('The prediction stands, the fix does not: run it again once')}"
|
|
|
|
|
|
f" {t('the bump is done, on the upgraded database.')}\n"
|
|
|
|
|
|
f" {t('Applying it from here fails with')}"
|
|
|
|
|
|
" KeyError: 'web_editor.assets'"
|
|
|
|
|
|
f" {t('and changes nothing.')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
def reset_command(lst_finding, database, config_path="./config.conf"):
|
|
|
|
|
|
"""La commande qui rend les fichiers de module. Rien n'est lancé ici."""
|
|
|
|
|
|
lines = [f"./odoo_bin.sh shell -c {config_path} -d {database} <<'PY'"]
|
|
|
|
|
|
for finding in lst_finding:
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
"env['web_editor.assets'].reset_asset"
|
|
|
|
|
|
f"('{finding['base_url']}', '{finding['bundle']}')"
|
|
|
|
|
|
)
|
|
|
|
|
|
lines += ["env.cr.commit()", "PY"]
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_reset(lst_finding, database, config_path="./config.conf"):
|
|
|
|
|
|
"""Rendre les fichiers de module. ÉCRIT en base — le seul endroit ici.
|
|
|
|
|
|
|
|
|
|
|
|
Réversible seulement au sens où la personnalisation peut être réécrite :
|
|
|
|
|
|
reset_asset supprime la pièce jointe. D'où la sauvegarde préalable, qui
|
|
|
|
|
|
n'est pas une politesse mais la condition pour dire « oui » sans risque.
|
|
|
|
|
|
"""
|
|
|
|
|
|
script = "\n".join(
|
|
|
|
|
|
"env['web_editor.assets'].reset_asset"
|
|
|
|
|
|
f"('{f['base_url']}', '{f['bundle']}')"
|
|
|
|
|
|
for f in lst_finding
|
|
|
|
|
|
)
|
|
|
|
|
|
done = subprocess.run(
|
|
|
|
|
|
["./odoo_bin.sh", "shell", "-c", config_path, "-d", database],
|
|
|
|
|
|
input=script + "\nenv.cr.commit()\n",
|
|
|
|
|
|
capture_output=True,
|
|
|
|
|
|
text=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
return done.returncode, done.stdout + done.stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def backup_custom(lst_finding, database):
|
|
|
|
|
|
"""Écrire les copies sur disque AVANT de les abandonner.
|
|
|
|
|
|
|
|
|
|
|
|
reset_asset supprime la pièce jointe : sans ceci, les lignes réellement
|
|
|
|
|
|
personnalisées ne seraient plus nulle part.
|
|
|
|
|
|
"""
|
|
|
|
|
|
directory = os.path.join(
|
|
|
|
|
|
"private", "odoo", "migration", database, "scss_backup"
|
|
|
|
|
|
)
|
|
|
|
|
|
os.makedirs(directory, exist_ok=True)
|
|
|
|
|
|
lst_path = []
|
|
|
|
|
|
for finding in lst_finding:
|
|
|
|
|
|
name = f"{finding['id']}_" + finding["url"].strip("/").replace(
|
|
|
|
|
|
"/", "_"
|
|
|
|
|
|
)
|
|
|
|
|
|
path = os.path.join(directory, name)
|
|
|
|
|
|
with open(path, "w", encoding="utf-8") as handle:
|
|
|
|
|
|
handle.write(finding["custom"])
|
|
|
|
|
|
lst_path.append(path)
|
|
|
|
|
|
return lst_path
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
def render(lst_finding, database, version_dir):
|
|
|
|
|
|
if not lst_finding:
|
|
|
|
|
|
return (
|
|
|
|
|
|
f"✅ -> {t('No customized SCSS is at risk in')} {version_dir}.\n"
|
|
|
|
|
|
)
|
|
|
|
|
|
lines = [
|
|
|
|
|
|
f"⚠️ {len(lst_finding)}"
|
|
|
|
|
|
f" {t('customized SCSS use(s) a variable that')} {version_dir}"
|
|
|
|
|
|
f" {t('no longer defines: the bundle will not compile.')}"
|
|
|
|
|
|
]
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
for finding in lst_finding:
|
|
|
|
|
|
lines.append(f" - id={finding['id']} {finding['url']}")
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
" " + ", ".join("$" + name for name in finding["missing"])
|
|
|
|
|
|
)
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
lines += [
|
|
|
|
|
|
f" {t('Each one is a copy frozen on an older version. Dropping it')}"
|
|
|
|
|
|
f" {t('restores the module file:')}",
|
|
|
|
|
|
]
|
|
|
|
|
|
lines += [
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
" " + line
|
|
|
|
|
|
for line in reset_command(lst_finding, database).splitlines()
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
]
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
lines.append(
|
|
|
|
|
|
f" {t('Read it first: what it holds beyond the stale variable is')}"
|
|
|
|
|
|
f" {t('a real customization, to re-apply as a small file.')}"
|
|
|
|
|
|
)
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
|
def prompt(lst_finding, database, config_path="./config.conf", ask=None):
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
"""Montrer, puis proposer de corriger. Rend True si l'on a écrit.
|
|
|
|
|
|
|
|
|
|
|
|
Répondre « oui, réinitialise » sans avoir vu l'écart, c'est accepter de
|
|
|
|
|
|
perdre on ne sait quoi. L'invite revient donc après chaque lecture :
|
|
|
|
|
|
regarder ne répond pas à la question.
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
|
|
|
|
|
|
|
Le défaut suit ce que le checkout SAIT faire : réinitialiser quand
|
|
|
|
|
|
`reset_asset` existe, rien du tout avant la 13.0. Un défaut « a » là où
|
|
|
|
|
|
la remise à zéro n'existe pas ferait boucler l'invite sur elle-même.
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
"""
|
[FIX] migration: reset the stale SCSS after the bump, not before
The fix was offered while the checkout was still on the previous
version. Answering « a » ran reset_asset in an Odoo 12 shell, which has
no web_editor.assets: KeyError, nothing changed, and the migration went
on to break at the next bump — measured on a real run.
Predicting early is right; fixing early is not. The early call is now
--report-only, and a second call comes after the bump, on the upgraded
database, where the checkout can do it. The tool also refuses on its
own: it reads the checkout sources for reset_asset rather than trusting
a version number, and says when to come back.
--- FR ---
[FIX] migration : réinitialiser le SCSS périmé après le palier, pas avant
La correction était proposée alors que le checkout était encore sur la
version précédente. Répondre « a » lançait reset_asset dans un shell
Odoo 12, sans web_editor.assets : KeyError, rien de modifié, et la
migration continuait jusqu'à casser au palier suivant. Mesuré.
Prédire tôt est juste ; corriger tôt ne l'est pas. L'appel précoce est
désormais --report-only, et un second vient après le palier, sur la
base montée de version, là où le checkout sait le faire. L'outil refuse
aussi de lui-même : il cherche reset_asset dans les sources du checkout
plutôt que de se fier à un numéro, et dit quand revenir.
Assisted-by: Claude Opus 5
(cherry picked from commit 13d4d0a33c63efcbafb5fb29313a5377ba5f508e)
2026-08-15 16:34:26 -04:00
|
|
|
|
odoo_dir = running_odoo_dir()
|
|
|
|
|
|
can_reset = reset_supported(odoo_dir)
|
|
|
|
|
|
if not can_reset:
|
|
|
|
|
|
print(too_early_message(odoo_dir, database))
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
|
defaut = "a" if can_reset else "n"
|
|
|
|
|
|
if ask is None:
|
|
|
|
|
|
ask = (
|
|
|
|
|
|
auto_ask.make_ask(defaut)
|
|
|
|
|
|
if auto_ask
|
|
|
|
|
|
else (lambda prompt="": input(prompt) or defaut)
|
|
|
|
|
|
)
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
while True:
|
[FIX] migration: reset the stale SCSS after the bump, not before
The fix was offered while the checkout was still on the previous
version. Answering « a » ran reset_asset in an Odoo 12 shell, which has
no web_editor.assets: KeyError, nothing changed, and the migration went
on to break at the next bump — measured on a real run.
Predicting early is right; fixing early is not. The early call is now
--report-only, and a second call comes after the bump, on the upgraded
database, where the checkout can do it. The tool also refuses on its
own: it reads the checkout sources for reset_asset rather than trusting
a version number, and says when to come back.
--- FR ---
[FIX] migration : réinitialiser le SCSS périmé après le palier, pas avant
La correction était proposée alors que le checkout était encore sur la
version précédente. Répondre « a » lançait reset_asset dans un shell
Odoo 12, sans web_editor.assets : KeyError, rien de modifié, et la
migration continuait jusqu'à casser au palier suivant. Mesuré.
Prédire tôt est juste ; corriger tôt ne l'est pas. L'appel précoce est
désormais --report-only, et un second vient après le palier, sur la
base montée de version, là où le checkout sait le faire. L'outil refuse
aussi de lui-même : il cherche reset_asset dans les sources du checkout
plutôt que de se fier à un numéro, et dit quand revenir.
Assisted-by: Claude Opus 5
(cherry picked from commit 13d4d0a33c63efcbafb5fb29313a5377ba5f508e)
2026-08-15 16:34:26 -04:00
|
|
|
|
choix = (
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
|
f" {t('Enter = reset them onto the module file')},"
|
|
|
|
|
|
if can_reset
|
|
|
|
|
|
else ""
|
[FIX] migration: reset the stale SCSS after the bump, not before
The fix was offered while the checkout was still on the previous
version. Answering « a » ran reset_asset in an Odoo 12 shell, which has
no web_editor.assets: KeyError, nothing changed, and the migration went
on to break at the next bump — measured on a real run.
Predicting early is right; fixing early is not. The early call is now
--report-only, and a second call comes after the bump, on the upgraded
database, where the checkout can do it. The tool also refuses on its
own: it reads the checkout sources for reset_asset rather than trusting
a version number, and says when to come back.
--- FR ---
[FIX] migration : réinitialiser le SCSS périmé après le palier, pas avant
La correction était proposée alors que le checkout était encore sur la
version précédente. Répondre « a » lançait reset_asset dans un shell
Odoo 12, sans web_editor.assets : KeyError, rien de modifié, et la
migration continuait jusqu'à casser au palier suivant. Mesuré.
Prédire tôt est juste ; corriger tôt ne l'est pas. L'appel précoce est
désormais --report-only, et un second vient après le palier, sur la
base montée de version, là où le checkout sait le faire. L'outil refuse
aussi de lui-même : il cherche reset_asset dans les sources du checkout
plutôt que de se fier à un numéro, et dit quand revenir.
Assisted-by: Claude Opus 5
(cherry picked from commit 13d4d0a33c63efcbafb5fb29313a5377ba5f508e)
2026-08-15 16:34:26 -04:00
|
|
|
|
)
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
answer = (
|
|
|
|
|
|
ask(
|
|
|
|
|
|
f"💬 {t('What do you want to do with these customizations?')}"
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
|
f" ({choix}"
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
f" v = {t('what the copy changed')},"
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
|
f" w = {t('full screen')},"
|
|
|
|
|
|
f" n = {t('nothing')}) : "
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
)
|
|
|
|
|
|
.strip()
|
|
|
|
|
|
.lower()
|
|
|
|
|
|
)
|
|
|
|
|
|
if answer == "v":
|
|
|
|
|
|
for finding in lst_finding:
|
|
|
|
|
|
print(render_diff(finding))
|
|
|
|
|
|
print()
|
|
|
|
|
|
continue
|
|
|
|
|
|
if answer == "w":
|
|
|
|
|
|
try:
|
|
|
|
|
|
from check_stale_scss_tui import run_tui
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
print(f"ℹ️ {t('Full screen view unavailable.')}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
if not run_tui(lst_finding):
|
|
|
|
|
|
for finding in lst_finding:
|
|
|
|
|
|
print(render_diff(finding))
|
|
|
|
|
|
continue
|
[FIX] migration: reset the stale SCSS after the bump, not before
The fix was offered while the checkout was still on the previous
version. Answering « a » ran reset_asset in an Odoo 12 shell, which has
no web_editor.assets: KeyError, nothing changed, and the migration went
on to break at the next bump — measured on a real run.
Predicting early is right; fixing early is not. The early call is now
--report-only, and a second call comes after the bump, on the upgraded
database, where the checkout can do it. The tool also refuses on its
own: it reads the checkout sources for reset_asset rather than trusting
a version number, and says when to come back.
--- FR ---
[FIX] migration : réinitialiser le SCSS périmé après le palier, pas avant
La correction était proposée alors que le checkout était encore sur la
version précédente. Répondre « a » lançait reset_asset dans un shell
Odoo 12, sans web_editor.assets : KeyError, rien de modifié, et la
migration continuait jusqu'à casser au palier suivant. Mesuré.
Prédire tôt est juste ; corriger tôt ne l'est pas. L'appel précoce est
désormais --report-only, et un second vient après le palier, sur la
base montée de version, là où le checkout sait le faire. L'outil refuse
aussi de lui-même : il cherche reset_asset dans les sources du checkout
plutôt que de se fier à un numéro, et dit quand revenir.
Assisted-by: Claude Opus 5
(cherry picked from commit 13d4d0a33c63efcbafb5fb29313a5377ba5f508e)
2026-08-15 16:34:26 -04:00
|
|
|
|
if answer == "a" and can_reset:
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
lst_path = backup_custom(lst_finding, database)
|
|
|
|
|
|
print(f"📦 {t('Saved before resetting')} :")
|
|
|
|
|
|
for path in lst_path:
|
|
|
|
|
|
print(f" {path}")
|
|
|
|
|
|
status, output = apply_reset(lst_finding, database, config_path)
|
|
|
|
|
|
print(output.strip()[-2000:])
|
|
|
|
|
|
if status:
|
|
|
|
|
|
print(f"❌ {t('Reset failed, nothing was changed.')}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
print(f"✅ -> {t('Reset done.')}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
def split_custom_url(url):
|
|
|
|
|
|
"""« /a/b.custom.web.assets_frontend.scss » -> (« /a/b.scss », bundle).
|
|
|
|
|
|
|
|
|
|
|
|
C'est la convention d'Odoo : le nom du bundle est encastré dans celui du
|
|
|
|
|
|
fichier. La reconstruire évite de faire deviner à l'utilisateur les deux
|
|
|
|
|
|
arguments de reset_asset au moment où il veut juste réparer.
|
|
|
|
|
|
"""
|
|
|
|
|
|
directory, name = os.path.split(url)
|
|
|
|
|
|
head, _sep, tail = name.partition(".custom.")
|
|
|
|
|
|
bundle = tail[:-5] if tail.endswith(".scss") else tail
|
|
|
|
|
|
return os.path.join(directory, head + ".scss"), bundle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(argv=None):
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
|
description=(
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
"Predict which customized SCSS will break on the target version."
|
|
|
|
|
|
" Read-only unless --apply."
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
parser.add_argument("-d", "--database", required=True)
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
"-t",
|
|
|
|
|
|
"--target_version",
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
help="target Odoo source directory, e.g. odoo13.0",
|
|
|
|
|
|
)
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
"--filestore",
|
|
|
|
|
|
default=None,
|
|
|
|
|
|
help="filestore directory (default: ~/.local/share/Odoo/filestore/<db>)",
|
|
|
|
|
|
)
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
"-c",
|
|
|
|
|
|
"--config",
|
|
|
|
|
|
default="./config.conf",
|
|
|
|
|
|
help="Odoo config used by the shell for --apply",
|
|
|
|
|
|
)
|
[FIX] migration: reset the stale SCSS after the bump, not before
The fix was offered while the checkout was still on the previous
version. Answering « a » ran reset_asset in an Odoo 12 shell, which has
no web_editor.assets: KeyError, nothing changed, and the migration went
on to break at the next bump — measured on a real run.
Predicting early is right; fixing early is not. The early call is now
--report-only, and a second call comes after the bump, on the upgraded
database, where the checkout can do it. The tool also refuses on its
own: it reads the checkout sources for reset_asset rather than trusting
a version number, and says when to come back.
--- FR ---
[FIX] migration : réinitialiser le SCSS périmé après le palier, pas avant
La correction était proposée alors que le checkout était encore sur la
version précédente. Répondre « a » lançait reset_asset dans un shell
Odoo 12, sans web_editor.assets : KeyError, rien de modifié, et la
migration continuait jusqu'à casser au palier suivant. Mesuré.
Prédire tôt est juste ; corriger tôt ne l'est pas. L'appel précoce est
désormais --report-only, et un second vient après le palier, sur la
base montée de version, là où le checkout sait le faire. L'outil refuse
aussi de lui-même : il cherche reset_asset dans les sources du checkout
plutôt que de se fier à un numéro, et dit quand revenir.
Assisted-by: Claude Opus 5
(cherry picked from commit 13d4d0a33c63efcbafb5fb29313a5377ba5f508e)
2026-08-15 16:34:26 -04:00
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
"--report-only",
|
|
|
|
|
|
action="store_true",
|
|
|
|
|
|
help="never ask anything, even in front of a terminal",
|
|
|
|
|
|
)
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
"--diff",
|
|
|
|
|
|
action="store_true",
|
|
|
|
|
|
help="print what each copy changed, without asking",
|
|
|
|
|
|
)
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
"--tui",
|
|
|
|
|
|
action="store_true",
|
|
|
|
|
|
help="browse the differences full screen",
|
|
|
|
|
|
)
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
|
"--apply",
|
|
|
|
|
|
action="store_true",
|
|
|
|
|
|
help="reset them onto the module file (WRITES; saves a copy first)",
|
|
|
|
|
|
)
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
config = parser.parse_args(argv)
|
|
|
|
|
|
|
|
|
|
|
|
if not os.path.isdir(config.target_version):
|
|
|
|
|
|
print(
|
|
|
|
|
|
f"❌ {t('Target version directory not found')} :"
|
|
|
|
|
|
f" '{config.target_version}'"
|
|
|
|
|
|
)
|
|
|
|
|
|
return 2
|
|
|
|
|
|
try:
|
|
|
|
|
|
lst_finding = analyse(
|
|
|
|
|
|
config.database, config.target_version, config.filestore
|
|
|
|
|
|
)
|
|
|
|
|
|
except RuntimeError as exc:
|
|
|
|
|
|
print(f"❌ {exc}")
|
|
|
|
|
|
return 2
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
print(render(lst_finding, config.database, config.target_version))
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
if not lst_finding:
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
if config.diff:
|
|
|
|
|
|
for finding in lst_finding:
|
|
|
|
|
|
print(render_diff(finding))
|
|
|
|
|
|
print()
|
|
|
|
|
|
if config.tui:
|
|
|
|
|
|
from check_stale_scss_tui import run_tui
|
|
|
|
|
|
|
|
|
|
|
|
if not run_tui(lst_finding):
|
|
|
|
|
|
for finding in lst_finding:
|
|
|
|
|
|
print(render_diff(finding))
|
|
|
|
|
|
if config.apply:
|
[FIX] migration: reset the stale SCSS after the bump, not before
The fix was offered while the checkout was still on the previous
version. Answering « a » ran reset_asset in an Odoo 12 shell, which has
no web_editor.assets: KeyError, nothing changed, and the migration went
on to break at the next bump — measured on a real run.
Predicting early is right; fixing early is not. The early call is now
--report-only, and a second call comes after the bump, on the upgraded
database, where the checkout can do it. The tool also refuses on its
own: it reads the checkout sources for reset_asset rather than trusting
a version number, and says when to come back.
--- FR ---
[FIX] migration : réinitialiser le SCSS périmé après le palier, pas avant
La correction était proposée alors que le checkout était encore sur la
version précédente. Répondre « a » lançait reset_asset dans un shell
Odoo 12, sans web_editor.assets : KeyError, rien de modifié, et la
migration continuait jusqu'à casser au palier suivant. Mesuré.
Prédire tôt est juste ; corriger tôt ne l'est pas. L'appel précoce est
désormais --report-only, et un second vient après le palier, sur la
base montée de version, là où le checkout sait le faire. L'outil refuse
aussi de lui-même : il cherche reset_asset dans les sources du checkout
plutôt que de se fier à un numéro, et dit quand revenir.
Assisted-by: Claude Opus 5
(cherry picked from commit 13d4d0a33c63efcbafb5fb29313a5377ba5f508e)
2026-08-15 16:34:26 -04:00
|
|
|
|
odoo_dir = running_odoo_dir()
|
|
|
|
|
|
if not reset_supported(odoo_dir):
|
|
|
|
|
|
print(too_early_message(odoo_dir, config.database))
|
|
|
|
|
|
return 2
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
lst_path = backup_custom(lst_finding, config.database)
|
|
|
|
|
|
print(f"📦 {t('Saved before resetting')} :")
|
|
|
|
|
|
for path in lst_path:
|
|
|
|
|
|
print(f" {path}")
|
|
|
|
|
|
status, output = apply_reset(
|
|
|
|
|
|
lst_finding, config.database, config.config
|
|
|
|
|
|
)
|
|
|
|
|
|
print(output.strip()[-2000:])
|
|
|
|
|
|
if status:
|
|
|
|
|
|
print(f"❌ {t('Reset failed, nothing was changed.')}")
|
|
|
|
|
|
return 2
|
|
|
|
|
|
print(f"✅ -> {t('Reset done.')}")
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
# Aucun drapeau : on demande, plutôt que d'imprimer un rapport et de
|
|
|
|
|
|
# laisser retrouver soi-même les deux arguments de reset_asset. Mais
|
|
|
|
|
|
# seulement devant un terminal — dans un tube, une invite bloquerait
|
|
|
|
|
|
# l'appelant sans que personne ne voie la question.
|
[FIX] migration: never ask a question the terminal cannot show
The theme uninstaller ends by asking what to do with the leftovers, and
the migration ran it through the capturing executor. Its stdout was a
pipe, Python buffers by blocks, so the prompt stayed invisible while the
process waited. It reads as a freeze: you press Enter blind, the first
keystroke answers unseen and the extra ones fall into the next question.
Two locks, because one is not enough. The migration runs the script on
the real terminal. And the three tools that ask now require BOTH ends:
something to read the answer from, and something to show the question
on. Guarding on stdin alone was the bug — measured, stdin tty=True with
stdout tty=False, and the question was asked into a pipe.
--- FR ---
[FIX] migration : ne jamais poser une question que le terminal ne montre pas
Le désinstalleur de thème finit par demander quoi faire des restes, et
la migration le lançait par l'exécuteur qui capture. Sa sortie partait
dans un tube, Python bufferise par blocs, et l'invite restait invisible
pendant l'attente. Cela se lit comme un blocage : on tape Entrée à
l'aveugle, la première frappe répond sans être vue et les suivantes
tombent dans la question d'après.
Deux verrous, car un seul ne suffit pas. La migration lance le script
sur le vrai terminal. Et les trois outils qui questionnent exigent
désormais les DEUX bouts : de quoi lire la réponse, et de quoi montrer
la question. Ne garder que stdin était le défaut — mesuré, stdin
tty=True et stdout tty=False, la question partait dans un tube.
Assisted-by: Claude Opus 5
2026-08-17 02:41:40 -04:00
|
|
|
|
if not (config.diff or config.tui or config.report_only) and can_ask():
|
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
|
|
|
|
if prompt(lst_finding, config.database, config.config):
|
|
|
|
|
|
return 0
|
|
|
|
|
|
return 1
|
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
sys.exit(main())
|