From 349c881087c1efd0082583b59b2a0e7fa5e3a805 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sun, 29 Mar 2026 21:58:03 -0400 Subject: [PATCH 01/15] [UPD] script manifest update with merge configuration manifest --- script/manifest/update_manifest_dev.sh | 3 +++ script/manifest/update_manifest_local_dev_code_generator.sh | 3 +++ script/manifest/update_manifest_local_prod.sh | 3 +++ script/manifest/update_manifest_prod.sh | 3 +++ 4 files changed, 12 insertions(+) diff --git a/script/manifest/update_manifest_dev.sh b/script/manifest/update_manifest_dev.sh index 3558c24..f36cd22 100755 --- a/script/manifest/update_manifest_dev.sh +++ b/script/manifest/update_manifest_dev.sh @@ -5,6 +5,9 @@ #EL_MANIFEST_PROD="./default.xml" #EL_MANIFEST_DEV="./manifest/default.dev.xml" +# Generate local manifest +.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA + # Update git-repo .venv.erplibre/bin/repo init -u https://github.com/ERPLibre/ERPLibre -b $(git rev-parse --verify HEAD) -m ${EL_MANIFEST_DEV} .venv.erplibre/bin/repo sync -v diff --git a/script/manifest/update_manifest_local_dev_code_generator.sh b/script/manifest/update_manifest_local_dev_code_generator.sh index ec52a82..e9635fd 100755 --- a/script/manifest/update_manifest_local_dev_code_generator.sh +++ b/script/manifest/update_manifest_local_dev_code_generator.sh @@ -21,6 +21,9 @@ else JOBS="$(sysctl -n hw.ncpu)" fi +# Generate local manifest +.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA + .venv.erplibre/bin/repo init -u git://127.0.0.1:9418/ -b $(git rev-parse --verify HEAD) -m ${MANIFEST_TARGET} -g base,code_generator .venv.erplibre/bin/repo sync -c -j "$JOBS" -v -m ${MANIFEST_TARGET} diff --git a/script/manifest/update_manifest_local_prod.sh b/script/manifest/update_manifest_local_prod.sh index 66c029a..09c7d57 100755 --- a/script/manifest/update_manifest_local_prod.sh +++ b/script/manifest/update_manifest_local_prod.sh @@ -21,6 +21,9 @@ else JOBS="$(sysctl -n hw.ncpu)" fi +# Generate local manifest +.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA + .venv.erplibre/bin/repo init -u git://127.0.0.1:9418/ -b $(git rev-parse --verify HEAD) -m ${MANIFEST_TARGET} .venv.erplibre/bin/repo sync -c -j "$JOBS" -v -m ${MANIFEST_TARGET} diff --git a/script/manifest/update_manifest_prod.sh b/script/manifest/update_manifest_prod.sh index 4a8e8e2..fa3beb0 100755 --- a/script/manifest/update_manifest_prod.sh +++ b/script/manifest/update_manifest_prod.sh @@ -11,6 +11,9 @@ else JOBS="$(sysctl -n hw.ncpu)" fi +# Generate local manifest +.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA + # Update git-repo .venv.erplibre/bin/repo init -u https://github.com/ERPLibre/ERPLibre -b $(git rev-parse --verify HEAD) .venv.erplibre/bin/repo sync -v -j "$JOBS" From 4e262d63c6d6aca49175e9d2f5e2e2237945e7d2 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 30 Mar 2026 01:07:08 -0400 Subject: [PATCH 02/15] [FIX] installation keep repo when add project --- manifest/git_manifest_erplibre_odoo_dev.xml | 4 ++++ script/git/git_merge_repo_manifest.py | 25 +++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/manifest/git_manifest_erplibre_odoo_dev.xml b/manifest/git_manifest_erplibre_odoo_dev.xml index ac648c3..bfdb291 100644 --- a/manifest/git_manifest_erplibre_odoo_dev.xml +++ b/manifest/git_manifest_erplibre_odoo_dev.xml @@ -1,5 +1,9 @@ + + + + Date: Sun, 29 Mar 2026 23:19:48 -0400 Subject: [PATCH 03/15] [IMP] update_env_version: add mobile context detection to make version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit make version had no visibility into whether the mobile project was active. Add detection via presence of mobile/erplibre_home_mobile directory (cloned by repo sync --with_mobile) and display its status alongside the existing Odoo/Python/Poetry version info. --- FR --- make version n'avait aucune visibilité sur l'activité du projet mobile. Ajoute une détection par la présence du répertoire mobile/erplibre_home_mobile, cloné par repo sync --with_mobile, et affiche son état à côté des versions Odoo, Python et Poetry déjà présentes. Assisted-by: Claude Sonnet 4.6 --- script/version/update_env_version.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/script/version/update_env_version.py b/script/version/update_env_version.py index 9831847..ece8d3f 100755 --- a/script/version/update_env_version.py +++ b/script/version/update_env_version.py @@ -24,6 +24,7 @@ VERSION_ERPLIBRE_FILE = os.path.join(".erplibre-version") VERSION_ODOO_FILE = os.path.join(".odoo-version") VERSION_POETRY_FILE = os.path.join(".poetry-version") ADDONS_PATH = os.path.join("addons") +MOBILE_PATH = os.path.join("mobile", "erplibre_home_mobile") VENV_TEMPLATE_FILE = ".venv.%s" MANIFEST_FILE = "default.dev.xml" MANIFEST_TEMPLATE_FILE = "default.dev.odoo%s.xml" @@ -144,6 +145,7 @@ class Update: self.data_version = None self.odoo_version = None self.python_version = None + self.mobile_active = False self.detected_version_erplibre = None self.new_version_erplibre = None self.new_version_odoo = None @@ -188,10 +190,16 @@ class Update: with open(VERSION_POETRY_FILE) as txt: poetry_version = txt.read().strip() + # Detect mobile context + self.mobile_active = os.path.isdir(MOBILE_PATH) + # Show actual version _logger.info(f"Python version: {self.python_version}") _logger.info(f"Odoo version: {self.odoo_version}") _logger.info(f"Poetry version: {poetry_version}") + _logger.info( + f"Mobile context: {'active (' + MOBILE_PATH + ')' if self.mobile_active else 'inactive'}" + ) erplibre_version_to_search = ERPLIBRE_TEMPLATE_VERSION % ( self.odoo_version, self.python_version, From 2922e1f277a4e459ce684c584bb47d92560f6dee Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 30 Mar 2026 14:46:56 -0400 Subject: [PATCH 04/15] [ADD] installation: add optional extra modules (CybroOdoo) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CybroOdoo repos are large and slow to clone, making them unsuitable for default installation. Moves them to opt-in per-version extra manifests, introduces .erplibre-state.json to track installation options per Odoo version, and surfaces the choice in the TODO CLI sub-menu. Switch auto-detects extra from state and warns when no state is recorded. --- FR --- Les dépôts CybroOdoo sont volumineux et lents à cloner, ce qui les rend inadaptés à une installation par défaut. Ils passent dans des manifestes extra par version, activés à la demande ; .erplibre-state.json est introduit pour suivre les options d'installation de chaque version d'Odoo, et le choix apparaît dans le sous-menu du CLI TODO. Le changement de version détecte l'option extra depuis cet état et avertit quand aucun état n'est enregistré. Assisted-by: Claude Sonnet 4.6 --- conf/make.installation.Makefile | 32 +++++ manifest/git_manifest_extra_odoo12.0.xml | 12 ++ manifest/git_manifest_extra_odoo13.0.xml | 12 ++ manifest/git_manifest_extra_odoo14.0.xml | 12 ++ manifest/git_manifest_extra_odoo15.0.xml | 12 ++ manifest/git_manifest_extra_odoo16.0.xml | 21 +++ manifest/git_manifest_extra_odoo17.0.xml | 20 +++ manifest/git_manifest_extra_odoo18.0.xml | 21 +++ manifest/git_manifest_odoo12.0.xml | 7 - manifest/git_manifest_odoo13.0.xml | 7 - manifest/git_manifest_odoo14.0.xml | 7 - manifest/git_manifest_odoo15.0.xml | 7 - manifest/git_manifest_odoo16.0.xml | 15 --- manifest/git_manifest_odoo17.0.xml | 15 --- manifest/git_manifest_odoo18.0.xml | 15 --- script/git/git_merge_repo_manifest.py | 20 +++ script/todo/todo.py | 31 +++++ script/todo/todo_i18n.py | 12 ++ script/version/erplibre_state.py | 161 +++++++++++++++++++++++ script/version/update_env_version.py | 57 +++++++- 20 files changed, 420 insertions(+), 76 deletions(-) create mode 100644 manifest/git_manifest_extra_odoo12.0.xml create mode 100644 manifest/git_manifest_extra_odoo13.0.xml create mode 100644 manifest/git_manifest_extra_odoo14.0.xml create mode 100644 manifest/git_manifest_extra_odoo15.0.xml create mode 100644 manifest/git_manifest_extra_odoo16.0.xml create mode 100644 manifest/git_manifest_extra_odoo17.0.xml create mode 100644 manifest/git_manifest_extra_odoo18.0.xml create mode 100644 script/version/erplibre_state.py diff --git a/conf/make.installation.Makefile b/conf/make.installation.Makefile index ca3e4a4..7365faf 100644 --- a/conf/make.installation.Makefile +++ b/conf/make.installation.Makefile @@ -113,6 +113,38 @@ switch_odoo_12_update: ./script/version/update_env_version.py --erplibre_version odoo12.0_python3.7.17 --switch --switch_update ./script/make.sh config_gen_all +#################### +# INSTALL WITH EXTRA (CybroOdoo) +#################### + +.PHONY: install_odoo_18_with_extra +install_odoo_18_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo18.0_python3.12.10 --install_dev --with_extra + +.PHONY: install_odoo_17_with_extra +install_odoo_17_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo17.0_python3.10.18 --install_dev --with_extra + +.PHONY: install_odoo_16_with_extra +install_odoo_16_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo16.0_python3.10.18 --install_dev --with_extra + +.PHONY: install_odoo_15_with_extra +install_odoo_15_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo15.0_python3.8.20 --install_dev --with_extra + +.PHONY: install_odoo_14_with_extra +install_odoo_14_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo14.0_python3.8.20 --install_dev --with_extra + +.PHONY: install_odoo_13_with_extra +install_odoo_13_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo13.0_python3.7.17 --install_dev --with_extra + +.PHONY: install_odoo_12_with_extra +install_odoo_12_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo12.0_python3.7.17 --install_dev --with_extra + .PHONY: install_odoo_all_version install_odoo_all_version: ./script/make.sh install_odoo_18 diff --git a/manifest/git_manifest_extra_odoo12.0.xml b/manifest/git_manifest_extra_odoo12.0.xml new file mode 100644 index 0000000..9e51d4f --- /dev/null +++ b/manifest/git_manifest_extra_odoo12.0.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/manifest/git_manifest_extra_odoo13.0.xml b/manifest/git_manifest_extra_odoo13.0.xml new file mode 100644 index 0000000..c7a74a4 --- /dev/null +++ b/manifest/git_manifest_extra_odoo13.0.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/manifest/git_manifest_extra_odoo14.0.xml b/manifest/git_manifest_extra_odoo14.0.xml new file mode 100644 index 0000000..94cd0fb --- /dev/null +++ b/manifest/git_manifest_extra_odoo14.0.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/manifest/git_manifest_extra_odoo15.0.xml b/manifest/git_manifest_extra_odoo15.0.xml new file mode 100644 index 0000000..c096a17 --- /dev/null +++ b/manifest/git_manifest_extra_odoo15.0.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/manifest/git_manifest_extra_odoo16.0.xml b/manifest/git_manifest_extra_odoo16.0.xml new file mode 100644 index 0000000..6be7a49 --- /dev/null +++ b/manifest/git_manifest_extra_odoo16.0.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/manifest/git_manifest_extra_odoo17.0.xml b/manifest/git_manifest_extra_odoo17.0.xml new file mode 100644 index 0000000..d18e6fb --- /dev/null +++ b/manifest/git_manifest_extra_odoo17.0.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/manifest/git_manifest_extra_odoo18.0.xml b/manifest/git_manifest_extra_odoo18.0.xml new file mode 100644 index 0000000..c41344b --- /dev/null +++ b/manifest/git_manifest_extra_odoo18.0.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/manifest/git_manifest_odoo12.0.xml b/manifest/git_manifest_odoo12.0.xml index 4af28fa..08c03df 100644 --- a/manifest/git_manifest_odoo12.0.xml +++ b/manifest/git_manifest_odoo12.0.xml @@ -26,13 +26,6 @@ - - - - - - - - - - dict: + """Return the current state, or an empty state if the file does not exist.""" + if not os.path.isfile(STATE_FILE): + return _deep_copy(_EMPTY_STATE) + try: + with open(STATE_FILE, "r", encoding="utf-8") as f: + return json.load(f) + except (json.JSONDecodeError, OSError) as e: + _logger.warning(f"Cannot read {STATE_FILE}: {e}. Using empty state.") + return _deep_copy(_EMPTY_STATE) + + +def write_state(state: dict) -> None: + """Persist state to .erplibre-state.json.""" + with open(STATE_FILE, "w", encoding="utf-8") as f: + json.dump(state, f, indent=4, ensure_ascii=False) + f.write("\n") + + +def set_version_installed( + odoo_version: str, + extra: bool = False, + python: str = None, + poetry: str = None, +) -> None: + """Record that an Odoo version has been installed (or reinstalled).""" + state = read_state() + entry = state["odoo_versions"].get(odoo_version, _deep_copy(_EMPTY_VERSION_ENTRY)) + entry["installed"] = True + entry["extra"] = extra + if python: + entry["python"] = python + if poetry: + entry["poetry"] = poetry + entry["installed_at"] = str(date.today()) + state["odoo_versions"][odoo_version] = entry + state["current_odoo_version"] = odoo_version + write_state(state) + _logger.info( + f"State updated: odoo {odoo_version} installed" + f" (extra={extra}, python={python}, poetry={poetry})" + ) + + +def set_version_switched(odoo_version: str) -> None: + """Record that the workspace was switched to an Odoo version.""" + state = read_state() + entry = state["odoo_versions"].get(odoo_version, _deep_copy(_EMPTY_VERSION_ENTRY)) + entry["switched_at"] = str(date.today()) + state["odoo_versions"][odoo_version] = entry + state["current_odoo_version"] = odoo_version + write_state(state) + + +def set_mobile_active(active: bool) -> None: + """Record whether the mobile project is currently synced.""" + state = read_state() + state["mobile"]["active"] = active + if active: + state["mobile"]["installed_at"] = str(date.today()) + write_state(state) + _logger.info(f"State updated: mobile active={active}") + + +def get_version_extra(odoo_version: str) -> bool: + """Return True if the given Odoo version was installed with extra modules.""" + state = read_state() + entry = state["odoo_versions"].get(odoo_version) + if entry is None: + return False + return bool(entry.get("extra", False)) + + +def get_version_installed(odoo_version: str) -> bool: + """Return True if the given Odoo version has an installation record.""" + state = read_state() + entry = state["odoo_versions"].get(odoo_version) + if entry is None: + return False + return bool(entry.get("installed", False)) + + +def get_current_version() -> str | None: + """Return the currently active Odoo version, or None.""" + return read_state().get("current_odoo_version") + + +def get_mobile_active() -> bool: + """Return True if mobile is recorded as active.""" + return bool(read_state().get("mobile", {}).get("active", False)) + + +def print_state() -> None: + """Log a human-readable summary of the current state.""" + state = read_state() + current = state.get("current_odoo_version") or "unknown" + mobile = state.get("mobile", {}) + mobile_status = "active" if mobile.get("active") else "inactive" + + _logger.info(f"Current Odoo version : {current}") + _logger.info(f"Mobile context : {mobile_status}") + + versions = state.get("odoo_versions", {}) + if versions: + installed = [ + f"{v}{' +extra' if d.get('extra') else ''}" + for v, d in sorted(versions.items()) + if d.get("installed") + ] + if installed: + _logger.info(f"Installed versions : {', '.join(installed)}") + else: + _logger.info("Installed versions : none recorded") + else: + _logger.info("Installed versions : none recorded") + + +def _deep_copy(d: dict) -> dict: + """Simple deep copy for plain dicts/lists (no external deps).""" + return json.loads(json.dumps(d)) diff --git a/script/version/update_env_version.py b/script/version/update_env_version.py index ece8d3f..154913b 100755 --- a/script/version/update_env_version.py +++ b/script/version/update_env_version.py @@ -12,6 +12,21 @@ import subprocess import sys import time +# erplibre_state is in the same directory; import lazily to avoid hard failure +# when the script runs outside the project root (e.g. during bare install). +try: + from script.version.erplibre_state import ( + get_version_extra, + get_version_installed, + print_state, + set_version_installed, + set_version_switched, + ) + + _STATE_AVAILABLE = True +except ImportError: + _STATE_AVAILABLE = False + logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO")) _logger = logging.getLogger(__name__) @@ -124,6 +139,14 @@ def get_config(): action="store_true", help="Force all, include force_install, force_repo.", ) + parser.add_argument( + "--with_extra", + action="store_true", + help=( + "Install or switch with extra modules (e.g. CybroOdoo)." + " State is saved in .erplibre-state.json." + ), + ) args = parser.parse_args() if args.force: @@ -200,6 +223,11 @@ class Update: _logger.info( f"Mobile context: {'active (' + MOBILE_PATH + ')' if self.mobile_active else 'inactive'}" ) + if _STATE_AVAILABLE: + extra = get_version_extra(self.odoo_version) + _logger.info( + f"Extra modules (CybroOdoo): {'active' if extra else 'inactive'}" + ) erplibre_version_to_search = ERPLIBRE_TEMPLATE_VERSION % ( self.odoo_version, self.python_version, @@ -413,15 +441,38 @@ class Update: if self.config.install_dev: _logger.info("Installation.") status = self.install_erplibre() + if _STATE_AVAILABLE and not status: + set_version_installed( + self.new_version_odoo, + extra=bool(self.config.with_extra), + python=self.new_version_python, + poetry=self.new_version_poetry, + ) elif ( self.config.is_in_switch and self.config.is_in_switch_force_update ): _logger.info("Switch") self.execute_log.append(f"System update") - status = os.system( - "./script/manifest/update_manifest_local_dev.sh" - ) + # Auto-detect extra from state, warn if no state recorded + with_extra = bool(self.config.with_extra) + if _STATE_AVAILABLE and not with_extra: + if not get_version_installed(self.new_version_odoo): + _logger.warning( + f"No installation state found for odoo" + f" {self.new_version_odoo}. Proceeding without" + f" extra modules (CybroOdoo). To install with" + f" extra: make" + f" install_odoo_{self.new_version_odoo.replace('.', '')}_with_extra" + ) + else: + with_extra = get_version_extra(self.new_version_odoo) + manifest_script = "./script/manifest/update_manifest_local_dev.sh" + if with_extra: + manifest_script += " --with_extra" + status = os.system(manifest_script) + if _STATE_AVAILABLE and not status: + set_version_switched(self.new_version_odoo) # To support multiple addons directory, remove TEMP # for addons_path in os.listdir("."): From 66b21422c407f0db9eafe8273a65a4ccca296e34 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 30 Mar 2026 21:45:28 -0400 Subject: [PATCH 05/15] [UPD] improve speed install odoo 18 minimal --- manifest/git_manifest_extra_odoo18.0.xml | 17 ++++++++++++++++- manifest/git_manifest_odoo18.0.xml | 7 ------- manifest/git_manifest_odoo18.0_dev.xml | 7 ------- script/generate_config.sh | 2 -- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/manifest/git_manifest_extra_odoo18.0.xml b/manifest/git_manifest_extra_odoo18.0.xml index c41344b..a69a128 100644 --- a/manifest/git_manifest_extra_odoo18.0.xml +++ b/manifest/git_manifest_extra_odoo18.0.xml @@ -1,8 +1,9 @@ + - + + + diff --git a/manifest/git_manifest_odoo18.0.xml b/manifest/git_manifest_odoo18.0.xml index b6baaef..27d1cc5 100644 --- a/manifest/git_manifest_odoo18.0.xml +++ b/manifest/git_manifest_odoo18.0.xml @@ -290,13 +290,6 @@ remote="ERPLibre_origin_MathBenTech" groups="addons,base,odoo18.0" /> - - diff --git a/script/generate_config.sh b/script/generate_config.sh index 834100f..6cbc959 100755 --- a/script/generate_config.sh +++ b/script/generate_config.sh @@ -27,8 +27,6 @@ printf "longpolling_port = ${EL_LONGPOLLING_PORT}\n" >> "${EL_CONFIG_FILE}" printf "addons_path = ${EL_HOME_ODOO}/addons,${EL_HOME_ODOO}/odoo/addons,${EL_HOME}/odoo${EL_ODOO_VERSION}/addons/addons," >> "${EL_CONFIG_FILE}" if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then - printf "${EL_HOME_ODOO_PROJECT}/addons/CybroOdoo_CybroAddons," >> "${EL_CONFIG_FILE}" - printf "${EL_HOME_ODOO_PROJECT}/addons/CybroOdoo_OpenHRMS," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/ERPLibre_erplibre_addons," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/MathBenTech_development," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_account-analytic," >> "${EL_CONFIG_FILE}" From cf78ed1bdc4d1bedfcbb5482afbd44dd99bde64d Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 30 Mar 2026 22:08:00 -0400 Subject: [PATCH 06/15] [IMP] installation: parallelize repo sync and poetry install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Poetry install and git-repo sync are independent (different write paths). Running them sequentially wastes time. Split install_locally.sh into EL_PHASE=setup|poetry|all phases so install_locally_dev.sh can background the repo sync while poetry runs in the foreground, reducing total install time by up to 50% on slow connections. Set EL_PARALLEL_INSTALL=0 to restore sequential behavior for debugging. --- FR --- L'installation poetry et la synchronisation git-repo sont indépendantes — elles écrivent à des endroits différents — et les enchaîner fait perdre du temps. install_locally.sh est découpé en phases EL_PHASE=setup|poetry|all pour qu'install_locally_dev.sh puisse lancer la synchronisation des dépôts en arrière-plan pendant que poetry tourne au premier plan, réduisant la durée totale jusqu'à 50 % sur une connexion lente. EL_PARALLEL_INSTALL=0 rétablit le comportement séquentiel pour le débogage. Assisted-by: Claude Sonnet 4.6 --- script/install/install_locally.sh | 155 ++++++++++++++------------ script/install/install_locally_dev.sh | 99 +++++++++++++--- 2 files changed, 164 insertions(+), 90 deletions(-) diff --git a/script/install/install_locally.sh b/script/install/install_locally.sh index d709e3d..8148e50 100755 --- a/script/install/install_locally.sh +++ b/script/install/install_locally.sh @@ -1,5 +1,3 @@ -#!/usr/bin/env bash - . ./env_var.sh EL_USER=${USER} EL_HOME=$PWD @@ -17,14 +15,6 @@ FILE_INSTALLATION_VERSION=".repo/installed_odoo_version.txt" Red='\033[0;31m' # Red Color_Off='\033[0m' # Text Reset -./script/generate_config.sh - -# Generate empty addons if missing -path_addons_addons="./odoo${EL_ODOO_VERSION}/addons/addons" -if [[ ! -d "${path_addons_addons}" ]]; then - mkdir -p "${path_addons_addons}" -fi - # example, 3.7.8 will be 3.7 into PYTHON_VERSION_MAJOR PYTHON_VERSION_MAJOR=$(echo "$EL_PYTHON_ODOO_VERSION" | sed 's/\.[^\.]*$//') VENV_ERPLIBRE_PATH=$(cat "conf/python-erplibre-venv" | xargs) @@ -32,71 +22,92 @@ VENV_ODOO_PATH=".venv.${EL_ERPLIBRE_VERSION}" POETRY_ODOO_PATH=${VENV_ERPLIBRE_PATH}/bin/poetry export WITH_POETRY_INSTALLATION=1 -if [[ ! -n "${DOCKER_BUILD}" ]]; then - # Install ERPLibre venv - echo -e "Install ${VENV_ERPLIBRE_PATH} with ${EL_PYTHON_ERPLIBRE_VERSION}" - ./script/install/install_venv.sh "ERPLibre" "${VENV_ERPLIBRE_PATH}" "${EL_PYTHON_ERPLIBRE_VERSION}" - # Install Odoo venv - echo -e "Install ${VENV_ODOO_PATH} with ${EL_PYTHON_ODOO_VERSION}" - ./script/install/install_venv.sh "Odoo" "${VENV_ODOO_PATH}" "${EL_PYTHON_ODOO_VERSION}" -else - mkdir .venv -fi +# EL_PHASE controls which steps to execute. Used by install_locally_dev.sh +# for parallel installation — do not set manually unless you know what you do. +# all (default) – full install: setup + poetry phases +# setup – prereqs only: venvs, pip-erplibre, git-repo +# poetry – python packages: poetry install + post-install +EL_PHASE=${EL_PHASE:-all} -source ./${VENV_ERPLIBRE_PATH}/bin/activate -echo -e "Upgrade pip to ${VENV_ERPLIBRE_PATH}" -pip install --upgrade pip -pip install -r requirement/erplibre_require-ments.txt +# ── Setup phase (venvs + pip-erplibre + git-repo) ──────────────────────────── +if [[ "${EL_PHASE}" != "poetry" ]]; then + ./script/generate_config.sh -./script/install/install_git_repo.sh - -source ${VENV_ODOO_PATH}/bin/activate -echo -e "Upgrade pip to ${VENV_ODOO_PATH}" -pip install --upgrade pip - -echo -e "\n---- Installing poetry dependency ----" - -if [[ -z "${EL_POETRY_VERSION}" ]]; then - echo -e "${Red}Error${Color_Off} missing poetry version, please check file .poetry-version" - cat .poetry-version - ls -la - exit 1 -fi - -# Delete artifacts created by pip, cause error in next "poetry install" -if [[ ! -f "${POETRY_ODOO_PATH}" ]]; then - echo -e "Install Poetry ${POETRY_ODOO_PATH}" - pip install poetry==${EL_POETRY_VERSION} - poetry --version - # Fix broken poetry by installing ignored dependence - # poetry lock --no-update - # To fix keyring problem when installation is blocked, use - export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring - if [[ ${WITH_POETRY_INSTALLATION} -ne 0 ]]; then - poetry install --no-root -vvv + # Generate empty addons if missing + path_addons_addons="./odoo${EL_ODOO_VERSION}/addons/addons" + if [[ ! -d "${path_addons_addons}" ]]; then + mkdir -p "${path_addons_addons}" fi - retVal=$? - if [[ $retVal -ne 0 ]]; then - echo "Poetry installation error with status ${retVal}" + + if [[ ! -n "${DOCKER_BUILD}" ]]; then + # Install ERPLibre venv + echo -e "Install ${VENV_ERPLIBRE_PATH} with ${EL_PYTHON_ERPLIBRE_VERSION}" + ./script/install/install_venv.sh "ERPLibre" "${VENV_ERPLIBRE_PATH}" "${EL_PYTHON_ERPLIBRE_VERSION}" + # Install Odoo venv + echo -e "Install ${VENV_ODOO_PATH} with ${EL_PYTHON_ODOO_VERSION}" + ./script/install/install_venv.sh "Odoo" "${VENV_ODOO_PATH}" "${EL_PYTHON_ODOO_VERSION}" + else + mkdir .venv + fi + + source ./${VENV_ERPLIBRE_PATH}/bin/activate + echo -e "Upgrade pip to ${VENV_ERPLIBRE_PATH}" + pip install --upgrade pip + pip install -r requirement/erplibre_require-ments.txt + + ./script/install/install_git_repo.sh +fi + +# ── Poetry phase (install python packages + post-install) ──────────────────── +if [[ "${EL_PHASE}" != "setup" ]]; then + source ${VENV_ODOO_PATH}/bin/activate + echo -e "Upgrade pip to ${VENV_ODOO_PATH}" + pip install --upgrade pip + + echo -e "\n---- Installing poetry dependency ----" + + if [[ -z "${EL_POETRY_VERSION}" ]]; then + echo -e "${Red}Error${Color_Off} missing poetry version, please check file .poetry-version" + cat .poetry-version + ls -la exit 1 fi -fi - -# Delete artifacts created by pip, cause error in next "poetry install" -rm -rf artifacts - -# Link for dev tools into Odoo -echo -e "\n---- Add link dependency in site-packages of Python ----" -# TODO this link can break, the symbolic link is maybe not created -ln -fs "${EL_HOME_ODOO}/odoo" "${EL_HOME}/${VENV_ODOO_PATH}/lib/python${PYTHON_VERSION_MAJOR}/site-packages/" - -# Force to return to erplibre source -source ./${VENV_ERPLIBRE_PATH}/bin/activate - -# Add trace of installation -LINE_TO_ADD="odoo${EL_ODOO_VERSION}" -mkdir -p "$(dirname "$FILE_INSTALLATION_VERSION")" -touch "$FILE_INSTALLATION_VERSION" -if ! grep -qxF "$LINE_TO_ADD" "$FILE_INSTALLATION_VERSION"; then - echo "$LINE_TO_ADD" >> "$FILE_INSTALLATION_VERSION" + + # Delete artifacts created by pip, cause error in next "poetry install" + if [[ ! -f "${POETRY_ODOO_PATH}" ]]; then + echo -e "Install Poetry ${POETRY_ODOO_PATH}" + pip install poetry==${EL_POETRY_VERSION} + poetry --version + # Fix broken poetry by installing ignored dependence + # poetry lock --no-update + # To fix keyring problem when installation is blocked, use + export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring + if [[ ${WITH_POETRY_INSTALLATION} -ne 0 ]]; then + poetry install --no-root -vvv + fi + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo "Poetry installation error with status ${retVal}" + exit 1 + fi + fi + + # Delete artifacts created by pip, cause error in next "poetry install" + rm -rf artifacts + + # Link for dev tools into Odoo + echo -e "\n---- Add link dependency in site-packages of Python ----" + # TODO this link can break, the symbolic link is maybe not created + ln -fs "${EL_HOME_ODOO}/odoo" "${EL_HOME}/${VENV_ODOO_PATH}/lib/python${PYTHON_VERSION_MAJOR}/site-packages/" + + # Force to return to erplibre source + source ./${VENV_ERPLIBRE_PATH}/bin/activate + + # Add trace of installation + LINE_TO_ADD="odoo${EL_ODOO_VERSION}" + mkdir -p "$(dirname "$FILE_INSTALLATION_VERSION")" + touch "$FILE_INSTALLATION_VERSION" + if ! grep -qxF "$LINE_TO_ADD" "$FILE_INSTALLATION_VERSION"; then + echo "$LINE_TO_ADD" >> "$FILE_INSTALLATION_VERSION" + fi fi diff --git a/script/install/install_locally_dev.sh b/script/install/install_locally_dev.sh index e0e9e8d..023c33d 100755 --- a/script/install/install_locally_dev.sh +++ b/script/install/install_locally_dev.sh @@ -3,27 +3,90 @@ . ./env_var.sh Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow Color_Off='\033[0m' # Text Reset -echo "" -echo "RUN ./script/install/install_locally.sh" -echo "" -./script/install/install_locally.sh -retVal=$? -if [[ $retVal -ne 0 ]]; then - echo -e "${Red}Error${Color_Off} ./script/install/install_locally.sh" - exit 1 -fi +# EL_PARALLEL_INSTALL=1 (default): run repo sync in parallel with poetry install +# EL_PARALLEL_INSTALL=0: legacy sequential behavior (set to debug or force order) +EL_PARALLEL_INSTALL=${EL_PARALLEL_INSTALL:-1} + +if [[ ${EL_PARALLEL_INSTALL} -eq 1 ]]; then + + # ── Phase 1 : common prereqs (~1-3 min) ────────────────────────────────── + # Create venvs, pip-erplibre, install git-repo. Must finish before both + # repo sync (.venv.erplibre/bin/repo) and poetry (.venv.odooXX) can start. + echo "" + echo "RUN ./script/install/install_locally.sh [phase: setup]" + echo "" + EL_PHASE=setup ./script/install/install_locally.sh + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} ./script/install/install_locally.sh [setup]" + exit 1 + fi + + # ── Phase 2 : parallel — repo sync (bg) + poetry (fg) ─────────────────── + REPO_SYNC_LOG="/tmp/el_repo_sync_$$.log" + echo "" + echo -e "${Yellow}Starting repo sync in background → ${REPO_SYNC_LOG}${Color_Off}" + echo -e "${Yellow} Follow live: tail -f ${REPO_SYNC_LOG}${Color_Off}" + echo "" + ./script/manifest/update_manifest_local_dev.sh >"${REPO_SYNC_LOG}" 2>&1 & + PID_REPO=$! + # Safety: kill background job if this script exits unexpectedly + trap "kill ${PID_REPO} 2>/dev/null; wait ${PID_REPO} 2>/dev/null; rm -f ${REPO_SYNC_LOG}" EXIT + + echo "" + echo "RUN ./script/install/install_locally.sh [phase: poetry]" + echo "" + EL_PHASE=poetry ./script/install/install_locally.sh + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} ./script/install/install_locally.sh [poetry]" + exit 1 + fi + + # ── Wait for repo sync ──────────────────────────────────────────────────── + trap - EXIT + echo "" + echo "Waiting for repo sync to complete (pid ${PID_REPO})..." + wait ${PID_REPO} + REPO_STATUS=$? + if [[ ${REPO_STATUS} -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} manifest update (repo sync) failed." + echo -e "Last 50 lines of ${REPO_SYNC_LOG}:" + tail -n 50 "${REPO_SYNC_LOG}" + rm -f "${REPO_SYNC_LOG}" + exit 1 + fi + echo -e "${Green}Repo sync completed successfully.${Color_Off}" + rm -f "${REPO_SYNC_LOG}" + +else + + # ── Sequential mode (legacy) ────────────────────────────────────────────── + echo "" + echo "RUN ./script/install/install_locally.sh" + echo "" + ./script/install/install_locally.sh + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} ./script/install/install_locally.sh" + exit 1 + fi + + # Update git-repo + echo "" + echo "RUN ./script/manifest/update_manifest_local_dev.sh" + echo "" + ./script/manifest/update_manifest_local_dev.sh + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} manifest update, check git-repo." + exit 1 + fi -# Update git-repo -echo "" -echo "RUN ./script/manifest/update_manifest_local_dev.sh" -echo "" -./script/manifest/update_manifest_local_dev.sh -retVal=$? -if [[ $retVal -ne 0 ]]; then - echo -e "${Red}Error${Color_Off} manifest update, check git-repo." - exit 1 fi npm install From 3c4841d221108f004ef9c4024e52cd31dbc49014 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 30 Mar 2026 22:18:53 -0400 Subject: [PATCH 07/15] [FIX] generate_config: drop odoo_design-themes from the addons path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The path was listed for every version, but git_manifest_odoo18.0.xml does not declare that repository — only 12.0 to 17.0 and the extra 18.0 manifest do. On Odoo 18 without the extra modules, repo never syncs the directory, so Odoo was handed an addons path entry that does not exist. --- FR --- Le chemin était listé pour toutes les versions, alors que git_manifest_odoo18.0.xml ne déclare pas ce dépôt — seuls les manifestes de 12.0 à 17.0 et le manifeste extra de 18.0 le font. Sur Odoo 18 sans les modules extra, repo ne synchronise jamais ce répertoire : on donnait donc à Odoo une entrée de chemin d'addons qui n'existe pas. Assisted-by: Claude Opus 5 --- script/generate_config.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/script/generate_config.sh b/script/generate_config.sh index 6cbc959..574a857 100755 --- a/script/generate_config.sh +++ b/script/generate_config.sh @@ -153,7 +153,6 @@ if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then printf "${EL_HOME_ODOO_PROJECT}/addons/dhongu_deltatech," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/muk-it_odoo-modules," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/novacode-nl_odoo-formio," >> "${EL_CONFIG_FILE}" - printf "${EL_HOME_ODOO_PROJECT}/addons/odoo_design-themes," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/odoomates_odooapps," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/openeducat_openeducat_erp," >> "${EL_CONFIG_FILE}" fi From 9e551c9baf7cf559dbbaaea2695caf9e8e517c37 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 11 Apr 2026 00:39:56 -0400 Subject: [PATCH 08/15] [FIX] script todo database_manager: try click for first installation --- script/todo/database_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/script/todo/database_manager.py b/script/todo/database_manager.py index 8011cb4..617a8e6 100644 --- a/script/todo/database_manager.py +++ b/script/todo/database_manager.py @@ -8,15 +8,16 @@ import logging import os import zipfile -import click - from script.todo.todo_i18n import t _logger = logging.getLogger(__name__) try: + import click + from script.todo import todo_file_browser except Exception: + click = None todo_file_browser = None From c41c771ca1e9ad2506a60c376bfb3b49a570d634 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 11 Apr 2026 04:21:55 -0400 Subject: [PATCH 09/15] [FIX] install: bump Node.js to v22 for Capacitor v8 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @capacitor/cli v8.x requires Node.js >=22.0.0. The install script was pinning NODE_MAJOR=20, causing a fatal error when installing the mobile app via todo.py. --- FR --- @capacitor/cli v8.x exige Node.js >= 22.0.0. Le script d'installation figeait NODE_MAJOR=20, d'où une erreur fatale à l'installation de l'application mobile depuis todo.py. Assisted-by: Claude Sonnet 4.6 --- script/install/install_debian_dependency.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/install/install_debian_dependency.sh b/script/install/install_debian_dependency.sh index 8df7730..adbc2d5 100755 --- a/script/install/install_debian_dependency.sh +++ b/script/install/install_debian_dependency.sh @@ -127,7 +127,8 @@ if [ "18.04" == "${UBUNTU_VERSION}" ]; then sudo apt remove nodeJS npm NODE_MAJOR=16 else - NODE_MAJOR=20 + # Node 22+ required by @capacitor/cli v8.x (mobile app dependency) + NODE_MAJOR=22 fi echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list From e8f34a9acaaf114167deeb362941248e55acde45 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sat, 11 Apr 2026 04:40:22 -0400 Subject: [PATCH 10/15] [UPD] manifest mobile : ajout whisper.cpp (transcription audio locale) Ajout remote ggerganov et projet whisper.cpp (clone shallow, revision master) dans le manifest Google Repo mobile. Chemin cible : mobile/erplibre_home_mobile/android/app/src/main/cpp/whisper --- EN --- Adds the ggerganov remote and the whisper.cpp project (shallow clone, revision master) to the mobile Google Repo manifest. Target path: mobile/erplibre_home_mobile/android/app/src/main/cpp/whisper Assisted-by: Claude Sonnet 4.6 --- manifest/git_manifest_mobile_home.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/manifest/git_manifest_mobile_home.xml b/manifest/git_manifest_mobile_home.xml index 549bbf3..4496ab2 100644 --- a/manifest/git_manifest_mobile_home.xml +++ b/manifest/git_manifest_mobile_home.xml @@ -1,6 +1,7 @@ + + + + From fa5039c204bb20353b9d37815d92206560861cc8 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Tue, 14 Apr 2026 02:35:16 -0400 Subject: [PATCH 11/15] [ADD] git_manifest_mobile_home: add sentencepiece for MarianMT NDK build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sentencepiece provides the SentencePiece tokenizer (C++) needed by the MarianMT Android plugin. Cloned shallow (depth=1) alongside whisper.cpp. --- FR --- sentencepiece fournit le tokeniseur SentencePiece, en C++, dont le greffon Android MarianMT a besoin. Cloné en profondeur 1, à côté de whisper.cpp. Assisted-by: Claude Sonnet 4.6 --- manifest/git_manifest_mobile_home.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/manifest/git_manifest_mobile_home.xml b/manifest/git_manifest_mobile_home.xml index 4496ab2..54d42ec 100644 --- a/manifest/git_manifest_mobile_home.xml +++ b/manifest/git_manifest_mobile_home.xml @@ -2,6 +2,7 @@ + + + + Date: Wed, 20 May 2026 02:54:29 -0400 Subject: [PATCH 12/15] [FIX] todo upgrade: download_database_backup_cli lives on db_manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Choosing « remote » as the migration source raised AttributeError: the method had moved to DatabaseManager, and todo_upgrade still called it on the TODO object. The remote path was therefore unusable — the only way to start a migration from a production backup rather than a local zip. --- FR --- Choisir « remote » comme source de migration levait une AttributeError : la méthode avait été déplacée vers DatabaseManager, et todo_upgrade l'appelait toujours sur l'objet TODO. Le chemin distant était donc inutilisable — le seul moyen de démarrer une migration depuis une sauvegarde de production plutôt que depuis un zip local. Assisted-by: Claude Opus 5 --- script/todo/todo_upgrade.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/todo/todo_upgrade.py b/script/todo/todo_upgrade.py index a83f199..b06a78c 100755 --- a/script/todo/todo_upgrade.py +++ b/script/todo/todo_upgrade.py @@ -511,7 +511,7 @@ class TodoUpgrade: file_browser.run_main_frame() elif self.file_path == "remote": status, self.file_path, default_database_name = ( - self.todo.download_database_backup_cli() + self.todo.db_manager.download_database_backup_cli() ) if status: _logger.error( From 203678b9e20fd51c3a94a9f04c8a5768c3751efe Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Tue, 26 May 2026 18:10:04 -0400 Subject: [PATCH 13/15] [ADD] requirement erplibre python-magic to support detect extension --- requirement/erplibre_require-ments.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirement/erplibre_require-ments.txt b/requirement/erplibre_require-ments.txt index c385ec8..16203b1 100644 --- a/requirement/erplibre_require-ments.txt +++ b/requirement/erplibre_require-ments.txt @@ -19,6 +19,7 @@ isort pykeepass click aioshutil +python-magic openai humanize requests From 133c1ce1f96245f2586b2bd71e8be4cfed9e3355 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Mon, 29 Jun 2026 03:22:58 -0400 Subject: [PATCH 14/15] [FIX] script todo upgrade: don't create branch if already exist --- script/todo/todo_upgrade.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/todo/todo_upgrade.py b/script/todo/todo_upgrade.py index b06a78c..d77b6dc 100755 --- a/script/todo/todo_upgrade.py +++ b/script/todo/todo_upgrade.py @@ -2038,7 +2038,9 @@ class TodoUpgrade: if not has_existing_target_branch: cmd_git_clone = ( f"cd {target_addons_path} " - f"&& git checkout -b {branch_target} && cd ~-" + f"&& if git rev-parse --verify --quiet refs/heads/{branch_target}; then " + f"git checkout {branch_target}; else git checkout -b {branch_target}; fi " + f"&& cd ~-" ) status, cmd_executed, lst_output = self.todo_upgrade_execute( cmd_git_clone, From eaa9e7ffa89d6300a934f366a5fd7cdeb1b7d397 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 17 Jun 2026 13:18:11 -0400 Subject: [PATCH 15/15] [IMP] make docker: docker_show_absolute_path - will show all absolute path for workspace docker container --- conf/make.docker.Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conf/make.docker.Makefile b/conf/make.docker.Makefile index 5effd0d..d772876 100644 --- a/conf/make.docker.Makefile +++ b/conf/make.docker.Makefile @@ -28,6 +28,10 @@ docker_show_databases: docker_show_logs_live: docker compose logs -f +.PHONY: docker_show_absolute_path +docker_show_absolute_path: + docker inspect $(docker ps -q) --format '{{.Name}}{{range .Mounts}} | {{.Source}} → {{.Destination}}{{end}}' + .PHONY: docker_show_process docker_show_process: docker compose ps