Merge branch 'develop'

- fix osx manifest
- update snap firefox ubuntu 25.10
- dynamic option to update config path addons from database or backup
This commit is contained in:
Mathieu Benoit 2026-02-14 04:55:11 -05:00
commit 4b0f9e15b7
18 changed files with 819 additions and 273 deletions

View file

@ -0,0 +1,66 @@
#!/usr/bin/env python3
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import argparse
import logging
import os
import sys
new_path = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "..")
)
sys.path.append(new_path)
from script.execute import execute
from script.git.git_tool import GitTool
_logger = logging.getLogger(__name__)
def get_config():
"""Parse command line arguments, extracting the config file name,
returning the union of config file and command line arguments
:return: dict of config file settings and command line arguments
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""\
Will print list of installed module from database
""",
epilog="""\
""",
)
parser.add_argument(
"--database",
required=True,
help="The database name to extract",
)
args = parser.parse_args()
return args
def main():
config = get_config()
execute_cmd = execute.Execute()
if config.database:
cmd = f"cat ./script/odoo/util/show_installed_module.py | ./odoo_bin.sh shell -d {config.database}"
status, output = execute_cmd.exec_command_live(
cmd,
quiet=True,
source_erplibre=False,
single_source_erplibre=True,
return_status_and_output=True,
)
has_find = False
for line in output:
if has_find:
print(line.strip())
if line.strip() == "Installed modules:":
has_find = True
if __name__ == "__main__":
main()

View file

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import argparse
import json
import logging
import os
import sys
import zipfile
new_path = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "..")
)
sys.path.append(new_path)
from script.execute import execute
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
_logger = logging.getLogger(__name__)
def get_config():
"""Parse command line arguments, extracting the config file name,
returning the union of config file and command line arguments
:return: dict of config file settings and command line arguments
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""\
Extract all repo path from installed module into backup.
Use --backup_path or --backup_name
""",
epilog="""\
""",
)
parser.add_argument("--backup_path", help="Backup file path")
parser.add_argument("--backup_name", help="Backup file name")
parser.add_argument(
"--debug",
action="store_true",
help="Will show all debug and execution information",
)
args = parser.parse_args()
die(
not (bool(args.backup_path) or bool(args.backup_name)),
"Take --backup_path or --backup_name",
)
return args
def main():
config = get_config()
if config.backup_path:
file_path = config.backup_path
elif not config.backup_name.endswith(".zip"):
file_path = os.path.join("image_db", f"{config.backup_name}.zip")
else:
file_path = os.path.join("image_db", config.backup_name)
with zipfile.ZipFile(file_path, "r") as zip_ref:
manifest_file = zip_ref.open("manifest.json")
json_manifest_file = json.load(manifest_file)
lst_module = set(json_manifest_file.get("modules").keys())
str_module = ";".join(lst_module)
cmd = f'./script/database/get_repo_from_module.py --module "{str_module}"'
execute_cmd = execute.Execute()
status, output = execute_cmd.exec_command_live(
cmd,
source_erplibre=False,
single_source_erplibre=True,
return_status_and_output=True,
)
def die(cond, message, code=1):
if cond:
print(message, file=sys.stderr)
sys.exit(code)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import argparse
import json
import logging
import os
import sys
import zipfile
new_path = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "..")
)
sys.path.append(new_path)
from script.execute import execute
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
_logger = logging.getLogger(__name__)
def get_config():
"""Parse command line arguments, extracting the config file name,
returning the union of config file and command line arguments
:return: dict of config file settings and command line arguments
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""\
Extract all repo path from installed module into backup.
Use --backup_path or --backup_name
""",
epilog="""\
""",
)
parser.add_argument("--module", help="Module list separate by ;")
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
return args
def main():
config = get_config()
lst_module = set(config.module.split(";"))
cmd = "parallel ::: " + " ".join(
[
f"'./script/addons/check_addons_exist.py --output_path -m \"{a}\"'"
for a in lst_module
]
)
execute_cmd = execute.Execute()
status, output = execute_cmd.exec_command_live(
cmd,
quiet=not config.debug,
source_erplibre=False,
single_source_erplibre=True,
return_status_and_output=True,
)
set_path_repo = set()
for line_module_path in output:
line_repo = os.path.dirname(line_module_path)
set_path_repo.add(line_repo)
for path_repo in set_path_repo:
print(path_repo)
def die(cond, message, code=1):
if cond:
print(message, file=sys.stderr)
sys.exit(code)
if __name__ == "__main__":
main()

View file

@ -359,6 +359,7 @@ def run_cmd(cmd, quiet=False, sys_exit=True):
if not quiet: if not quiet:
_logger.info(f"Run cmd: {cmd}") _logger.info(f"Run cmd: {cmd}")
debut = time.time() debut = time.time()
# Maybe can use exec_command_live
process = subprocess.Popen( process = subprocess.Popen(
cmd, cmd,
shell=True, shell=True,

View file

189
script/execute/execute.py Normal file
View file

@ -0,0 +1,189 @@
#!/usr/bin/env python3
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import datetime
import logging
import os
import shutil
import subprocess
import sys
import time
import humanize
cst_venv_erplibre = ".venv.erplibre"
new_path = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "..")
)
sys.path.append(new_path)
logging.basicConfig(
format=(
"%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d]"
" %(message)s"
),
datefmt="%Y-%m-%d:%H:%M:%S",
level=logging.INFO,
)
_logger = logging.getLogger(__name__)
class Execute:
def __init__(self):
self.cmd_source_erplibre = ""
self.cmd_source_default = ""
exec_path_gnome_terminal = shutil.which("gnome-terminal")
if exec_path_gnome_terminal:
self.cmd_source_erplibre = (
f"gnome-terminal -- bash -c 'source"
f" ./{cst_venv_erplibre}/bin/activate;%s'"
)
self.cmd_source_default = "gnome-terminal -- bash -c '" f"%s'"
else:
exec_path_tell = shutil.which("osascript")
if exec_path_tell:
self.cmd_source_erplibre = (
"osascript -e 'tell application \"Terminal\"'"
)
self.cmd_source_erplibre += " -e 'tell application \"System Events\" to keystroke \"t\" using {command down}' -e 'delay 0.1' -e 'do script \""
self.cmd_source_erplibre += f"cd {os.getcwd()}; source ./{cst_venv_erplibre}/bin/activate; %s\" in front window'"
self.cmd_source_erplibre += " -e 'end tell'"
else:
self.cmd_source_erplibre = (
f"source ./{cst_venv_erplibre}/bin/activate;%s"
)
def exec_command_live(
self,
commande,
source_erplibre=True,
quiet=False,
single_source_erplibre=False,
new_window=False,
single_source_odoo=False,
source_odoo="",
new_env=None,
return_status_and_command=False,
return_status_and_output=False,
return_status_and_output_and_command=False,
):
"""
Exécute une commande et affiche la sortie en direct.
Args:
commande (str): La commande à exécuter (sous forme de chaîne de caractères).
"""
my_env = os.environ.copy()
if new_env:
my_env.update(new_env)
process_start_time = time.time()
return_status = None
if source_erplibre:
# commande = f"source ./{cst_venv_erplibre}/bin/activate && " + commande
# cmd = (
# f"gnome-terminal --tab -- bash -c 'source"
# f" ./{cst_venv_erplibre}/bin/activate;{commande}'"
# )
commande = self.cmd_source_erplibre % commande
# os.system(f"./script/terminal/open_terminal.sh {commande}")
elif single_source_erplibre:
commande = (
f"source ./{cst_venv_erplibre}/bin/activate && %s" % commande
)
elif single_source_odoo:
if not source_odoo and os.path.exists("./.erplibre-version"):
with open("./.erplibre-version") as f:
source_odoo = f.read()
if not source_odoo:
_logger.error(
f"You cannot execute Odoo command if no version is installed. Command : {commande}"
)
return -1
commande = (
f"source ./.venv.{source_odoo}/bin/activate && {commande}"
)
if new_window and self.cmd_source_default:
commande = self.cmd_source_default % commande
if not quiet:
print("🏠 ⬇ Execute command :")
print(commande)
lst_output = []
try:
process = subprocess.Popen(
commande,
shell=True,
executable="/bin/bash",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1, # Désactive la mise en tampon pour la sortie en direct
universal_newlines=True, # Pour traiter les sauts de lignes correctement
env=my_env,
)
while True:
ligne = process.stdout.readline()
if not ligne:
break
if not quiet:
print(ligne, end="")
if (
return_status_and_output
or return_status_and_output_and_command
):
# Remove last \n char
lst_output.append(
ligne.removesuffix("\r\n")
.removesuffix("\n")
.removesuffix("\r")
)
process.wait() # Attendre la fin du process
return_status = process.returncode
if process.returncode != 0 and not quiet:
print(
"La commande a retourné un code d'erreur :"
f" {process.returncode}"
)
except FileNotFoundError:
if not quiet:
if "password" in commande:
print(
f"Erreur : La commande '{commande.split(' ')[0]}'[...] n'a"
" pas été trouvée."
)
else:
print(
f"Erreur : La commande '{commande}' n'a pas été trouvée."
)
except Exception as e:
if not quiet:
print(f"Une erreur s'est produite : {e}")
process_end_time = time.time()
duration_sec = process_end_time - process_start_time
if humanize:
duration_delta = datetime.timedelta(seconds=duration_sec)
humain_time = humanize.precisedelta(duration_delta)
if not quiet:
print(f"🏠 ⬆ Executed ({humain_time}) :")
else:
if not quiet:
print(f"🏠 ⬆ Executed ({duration_sec:.2f} sec.) :")
if not quiet:
print(commande)
print()
if return_status_and_output_and_command:
return return_status, commande, lst_output
if return_status_and_command:
return return_status, commande
if return_status_and_output:
return return_status, lst_output
return return_status

View file

@ -7,14 +7,12 @@ import logging
import os import os
import sys import sys
import git
from git import Repo
new_path = os.path.normpath( new_path = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "..") os.path.join(os.path.dirname(__file__), "..", "..")
) )
sys.path.append(new_path) sys.path.append(new_path)
from script.execute import execute
from script.git.git_tool import GitTool from script.git.git_tool import GitTool
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -26,9 +24,6 @@ def get_config():
:return: dict of config file settings and command line arguments :return: dict of config file settings and command line arguments
""" """
config = GitTool.get_project_config()
# TODO update description
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
description="""\ description="""\
@ -52,6 +47,22 @@ def get_config():
action="store_true", action="store_true",
help="Will remove odoo path, need this feature for OpenUpgrade when Odoo <= 13", help="Will remove odoo path, need this feature for OpenUpgrade when Odoo <= 13",
) )
parser.add_argument(
"--from_backup_path",
help="Will read backup path and whitelist the repo",
)
parser.add_argument(
"--from_backup_name",
help="Will read backup name and whitelist the repo",
)
parser.add_argument(
"--add_repo",
help="Add repo, separate by ; for list",
)
parser.add_argument(
"--database",
help="Add repo from module found into database.",
)
args = parser.parse_args() args = parser.parse_args()
return args return args
@ -59,13 +70,63 @@ def get_config():
def main(): def main():
config = get_config() config = get_config()
git_tool = GitTool() git_tool = GitTool()
execute_cmd = execute.Execute()
index_to_remove = len(os.getcwd()) + 1
filter_group = config.group if config.group else None filter_group = config.group if config.group else None
lst_whitelist = []
if config.from_backup_path or config.from_backup_name:
# script/database/get_repo_from_backup.py
# --backup_name bpir_prod_5_dec_2025_2026-02-04_14h27m54s.zip
if config.from_backup_path:
cmd = f"./script/database/get_repo_from_backup.py --backup_path {config.from_backup_path}"
else:
cmd = f"./script/database/get_repo_from_backup.py --backup_name {config.from_backup_name}"
status, output = execute_cmd.exec_command_live(
cmd,
quiet=True,
source_erplibre=False,
single_source_erplibre=True,
return_status_and_output=True,
)
for line in output:
repo_name = line[index_to_remove:].strip()
lst_whitelist.append(repo_name)
if config.database:
cmd = f"./script/database/get_module_list_from_database.py --database {config.database}"
status, output = execute_cmd.exec_command_live(
cmd,
quiet=True,
source_erplibre=False,
single_source_erplibre=True,
return_status_and_output=True,
)
str_module = ";".join(output)
cmd = f'./script/database/get_repo_from_module.py --module "{str_module}"'
status, output = execute_cmd.exec_command_live(
cmd,
quiet=True,
source_erplibre=False,
single_source_erplibre=True,
return_status_and_output=True,
)
for line in output:
repo_name = line[index_to_remove:].strip()
lst_whitelist.append(repo_name)
if config.add_repo:
lst_add_repo = [a.strip() for a in config.add_repo.split(";")]
else:
lst_add_repo = []
git_tool.generate_generate_config( git_tool.generate_generate_config(
filter_group=filter_group, filter_group=filter_group,
extra_path=config.extra_addons_path, extra_path=config.extra_addons_path,
ignore_odoo_path=config.ignore_odoo_path, ignore_odoo_path=config.ignore_odoo_path,
lst_add_repo=lst_add_repo,
lst_whitelist=lst_whitelist,
) )

View file

@ -74,7 +74,7 @@ class GitTool:
def get_transformed_repo_info_from_url( def get_transformed_repo_info_from_url(
self, self,
url: str, url: str,
repo_path: str = "./", repo_path: str = ".",
get_obj: bool = True, get_obj: bool = True,
is_submodule: bool = True, is_submodule: bool = True,
organization_force: str = None, organization_force: str = None,
@ -103,15 +103,16 @@ class GitTool:
repo_name = repo_name[:-4] repo_name = repo_name[:-4]
if is_submodule: if is_submodule:
if not sub_path or sub_path == ".": if not sub_path or sub_path == ".":
path = f"{repo_name}" path = repo_name
else: else:
path = f"{sub_path}/{organization}_{repo_name}" path = os.path.join(sub_path, f"{organization}_{repo_name}")
else: else:
path = f"{repo_path}" path = repo_path
if repo_path[-1] == "/": relative_path = os.path.join(repo_path, path)
relative_path = f"{repo_path}{path}" # if repo_path[-1] == "/":
else: # relative_path = f"{repo_path}{path}"
relative_path = f"{repo_path}/{path}" # else:
# relative_path = f"{repo_path}/{path}"
relative_path = os.path.normpath(relative_path) relative_path = os.path.normpath(relative_path)
original_organization = organization original_organization = organization
@ -150,7 +151,7 @@ class GitTool:
def get_repo_info( def get_repo_info(
self, self,
repo_path: str = "./", repo_path: str = ".",
add_root: bool = False, add_root: bool = False,
is_manifest: bool = True, is_manifest: bool = True,
filter_group=None, filter_group=None,
@ -166,7 +167,7 @@ class GitTool:
) )
def get_repo_info_submodule( def get_repo_info_submodule(
self, repo_path: str = "./", add_root: bool = False self, repo_path: str = ".", add_root: bool = False
) -> list: ) -> list:
""" """
Get information about submodule from repo_path Get information about submodule from repo_path
@ -182,7 +183,7 @@ class GitTool:
"name": name of the submodule "name": name of the submodule
}] }]
""" """
filename = f"{repo_path}/.gitmodules" filename = os.path.join(repo_path, ".gitmodules")
lst_repo = [] lst_repo = []
with open(filename) as file: with open(filename) as file:
txt = file.readlines() txt = file.readlines()
@ -200,7 +201,7 @@ class GitTool:
"url_https": url_https, "url_https": url_https,
"url_git": url_git, "url_git": url_git,
"path": path, "path": path,
"relative_path": f"{repo_path}/{path}", "relative_path": os.path.join(repo_path, path),
"name": name, "name": name,
} }
lst_repo.append(data) lst_repo.append(data)
@ -225,7 +226,7 @@ class GitTool:
"url_https": url_https, "url_https": url_https,
"url_git": url_git, "url_git": url_git,
"path": path, "path": path,
"relative_path": f"{repo_path}/{path}", "relative_path": os.path.join(repo_path, path),
"name": name, "name": name,
} }
lst_repo.append(data) lst_repo.append(data)
@ -248,7 +249,7 @@ class GitTool:
return lst_repo return lst_repo
def get_repo_info_manifest_xml( def get_repo_info_manifest_xml(
self, repo_path: str = "./", add_root: bool = False, filter_group=None self, repo_path: str = ".", add_root: bool = False, filter_group=None
) -> list: ) -> list:
""" """
Get information about manifest of Repo from repo_path Get information about manifest of Repo from repo_path
@ -318,7 +319,7 @@ class GitTool:
"url_https": url_https, "url_https": url_https,
"url_git": url_git, "url_git": url_git,
"path": path, "path": path,
"relative_path": f"{repo_path}/{path}", "relative_path": os.path.join(repo_path, path),
"name": name, "name": name,
"group": groups, "group": groups,
} }
@ -351,7 +352,7 @@ class GitTool:
return lst_repo return lst_repo
def get_manifest_xml_info( def get_manifest_xml_info(
self, repo_path: str = "./", filename=None, add_root: bool = False self, repo_path: str = ".", filename=None, add_root: bool = False
) -> list: ) -> list:
""" """
Get contain of manifest Get contain of manifest
@ -388,7 +389,7 @@ class GitTool:
return dct_remote, dct_project, default_remote return dct_remote, dct_project, default_remote
@staticmethod @staticmethod
def get_project_config(repo_path="./"): def get_project_config(repo_path="."):
""" """
Get information about configuration in env_var.sh Get information about configuration in env_var.sh
:param repo_path: path of repo to get information env_var.sh :param repo_path: path of repo to get information env_var.sh
@ -397,7 +398,7 @@ class GitTool:
CST_EL_GITHUB_TOKEN: TOKEN, CST_EL_GITHUB_TOKEN: TOKEN,
} }
""" """
filename = f"{repo_path}env_var.sh" filename = os.path.join(repo_path, "env_var.sh")
with open(filename) as file: with open(filename) as file:
txt = file.readlines() txt = file.readlines()
txt = [a[:-1] for a in txt if "=" in a] txt = [a[:-1] for a in txt if "=" in a]
@ -421,17 +422,29 @@ class GitTool:
def generate_generate_config( def generate_generate_config(
self, self,
repo_path="./", repo_path=".",
filter_group=None, filter_group=None,
extra_path=None, extra_path=None,
ignore_odoo_path=None, ignore_odoo_path=None,
lst_add_repo=None,
lst_whitelist=None,
): ):
filename_locally = f"{repo_path}script/generate_config.sh" filename_locally = os.path.join(repo_path, "script/generate_config.sh")
if not filter_group: if not filter_group:
filter_group = self.odoo_version_long filter_group = self.odoo_version_long
lst_repo = self.get_repo_info( lst_repo_origin = self.get_repo_info(
repo_path=repo_path, filter_group=filter_group repo_path=repo_path, filter_group=filter_group
) )
if lst_whitelist:
lst_repo = []
for repo in lst_repo_origin:
if (
repo.get("path") in lst_whitelist
or repo.get("path") in lst_add_repo
):
lst_repo.append(repo)
else:
lst_repo = lst_repo_origin
lst_result = [] lst_result = []
if not lst_repo: if not lst_repo:
print( print(
@ -444,7 +457,9 @@ class GitTool:
continue continue
# groups = repo.get("group") # groups = repo.get("group")
# Use variable instead of hardcoded path # Use variable instead of hardcoded path
if update_repo.startswith(f"{filter_group}/addons"): if update_repo.startswith(
os.path.join(self.odoo_version_long, "addons")
):
lst_path = update_repo.split("/", 1) lst_path = update_repo.split("/", 1)
update_repo = f"${{EL_HOME_ODOO_PROJECT}}/" + lst_path[1] update_repo = f"${{EL_HOME_ODOO_PROJECT}}/" + lst_path[1]
# str_repo = ( # str_repo = (
@ -715,7 +730,7 @@ class GitTool:
print(str_xml_text + "\n") print(str_xml_text + "\n")
def generate_git_modules( def generate_git_modules(
self, lst_repo: List[Struct], repo_path: str = "./" self, lst_repo: List[Struct], repo_path: str = "."
): ):
lst_modules = [] lst_modules = []
for repo in lst_repo: for repo in lst_repo:
@ -727,10 +742,10 @@ class GitTool:
) )
# create file # create file
with open(f"{repo_path}.gitmodules", mode="w") as file: with open(os.path.join(repo_path, ".gitmodules"), mode="w") as file:
file.writelines(lst_modules) file.writelines(lst_modules)
def get_source_repo_addons(self, repo_path="./", add_repo_root=False): def get_source_repo_addons(self, repo_path=".", add_repo_root=False):
""" """
Read file CST_FILE_SOURCE_REPO_ADDONS and return structure of data Read file CST_FILE_SOURCE_REPO_ADDONS and return structure of data
:param repo_path: path to find file CST_FILE_SOURCE_REPO_ADDONS :param repo_path: path to find file CST_FILE_SOURCE_REPO_ADDONS
@ -745,7 +760,7 @@ class GitTool:
"name": name of the submodule "name": name of the submodule
}] }]
""" """
file_name = f"{repo_path}{CST_FILE_SOURCE_REPO_ADDONS}" file_name = os.path.join(repo_path, CST_FILE_SOURCE_REPO_ADDONS)
lst_result = [] lst_result = []
if add_repo_root: if add_repo_root:
# TODO what to do if origin not exist? # TODO what to do if origin not exist?
@ -800,7 +815,7 @@ class GitTool:
lst_result.append(repo_info) lst_result.append(repo_info)
return lst_result return lst_result
def get_manifest_file(self, repo_path: str = "./"): def get_manifest_file(self, repo_path: str = "."):
""" """
Find .repo and return default manifest file. Find .repo and return default manifest file.
:param repo_path: path to search .repo :param repo_path: path to search .repo
@ -811,7 +826,7 @@ class GitTool:
) )
if os.path.exists(file): if os.path.exists(file):
return file return file
file = f"{repo_path}/.repo/manifest.xml" file = os.path.join(repo_path, ".repo/manifest.xml")
if not os.path.exists(file): if not os.path.exists(file):
return "" return ""
with open(file) as xml: with open(file) as xml:
@ -824,8 +839,8 @@ class GitTool:
def get_matching_repo( def get_matching_repo(
self, self,
actual_repo="./", actual_repo=".",
repo_compare_to="./", repo_compare_to=".",
force_normalize_compare=False, force_normalize_compare=False,
sync_with_submodule=False, sync_with_submodule=False,
): ):

View file

@ -12,7 +12,7 @@ EL_USER=${USER}
#-------------------------------------------------- #--------------------------------------------------
# Install PostgreSQL Server # Install PostgreSQL Server
#-------------------------------------------------- #--------------------------------------------------
echo "\n---- Install PostgreSQL Server ----" echo "\n---- Install PostgreSQL Server ----"
if which psql >/dev/null 2>&1; then if which psql >/dev/null 2>&1; then
echo "postgresql is already installed, skipping" echo "postgresql is already installed, skipping"
else else
@ -21,39 +21,39 @@ else
brew services start postgresql@15 brew services start postgresql@15
fi fi
echo "\n---- Creating the ERPLibre PostgreSQL User ----" echo "\n---- Creating the ERPLibre PostgreSQL User ----"
sudo su - postgres -c "createuser -s ${EL_USER}" 2> /dev/null || true sudo su - postgres -c "createuser -s ${EL_USER}" 2>/dev/null || true
sudo su - postgres -c "CREATE EXTENSION postgis;\nCREATE EXTENSION postgis_topology;" 2> /dev/null || true sudo su - postgres -c "CREATE EXTENSION postgis;\nCREATE EXTENSION postgis_topology;" 2>/dev/null || true
#-------------------------------------------------- #--------------------------------------------------
# Install Dependencies # Install Dependencies
#-------------------------------------------------- #--------------------------------------------------
echo "\n--- Installing Python 3 + pip3 --" echo "\n--- Installing Python 3 + pip3 --"
#TODO is python@3.7 line here still usefull?? Should we get rid of it? brew install git wget parallel mariadb
brew install git python@3.7 wget parallel mariadb
brew link git brew link git
brew link wget brew link wget
echo "\n--- Installing extra --" echo "\n--- Installing extra --"
brew install shfmt brew install shfmt
brew install parallel brew install parallel
brew install swig brew install swig
brew install portaudio brew install portaudio
brew install xmlsec1 brew install xmlsec1
echo "\n---- Installing nodeJS NPM and rtlcss for LTR support ----" echo "\n---- Installing nodeJS NPM and rtlcss for LTR support ----"
brew install nodejs npm openssl brew install nodejs npm openssl
sudo npm install -g rtlcss sudo npm install -g rtlcss
sudo npm install -g less sudo npm install -g less
npm install npm install
echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >>~/.zshrc
#-------------------------------------------------- #--------------------------------------------------
# Install Wkhtmltopdf if needed # Install Wkhtmltopdf if needed
#-------------------------------------------------- #--------------------------------------------------
echo "\n---- Installing Wkhtmltopdf if needed ----" echo "\n---- Installing Wkhtmltopdf if needed ----"
if [ ! -f "wkhtmltox-0.12.6-2.macos-cocoa.pkg" ]; then if [ ! -f "wkhtmltox-0.12.6-2.macos-cocoa.pkg" ]; then
sudo wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-2/wkhtmltox-0.12.6-2.macos-cocoa.pkg sudo wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-2/wkhtmltox-0.12.6-2.macos-cocoa.pkg
sudo sudo installer -pkg wkhtmltox-0.12.6-2.macos-cocoa.pkg -target / sudo sudo installer -pkg wkhtmltox-0.12.6-2.macos-cocoa.pkg -target /
else echo "Wkhtmltopdf already installed" else
echo "Wkhtmltopdf already installed"
fi fi

View file

@ -14,10 +14,16 @@ else
MANIFEST_TARGET="$EL_MANIFEST_DEV" MANIFEST_TARGET="$EL_MANIFEST_DEV"
fi fi
if command -v nproc >/dev/null 2>&1; then
JOBS="$(nproc --all)"
else
JOBS="$(sysctl -n hw.ncpu)"
fi
# Generate local manifest # 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/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 init -u git://127.0.0.1:9418/ -b $(git rev-parse --verify HEAD) -m ${MANIFEST_TARGET} "$@"
.venv.erplibre/bin/repo sync -c -j $(nproc --all) -v -m ${MANIFEST_TARGET} .venv.erplibre/bin/repo sync -c -j "$JOBS" -v -m ${MANIFEST_TARGET}
kill ${DAEMON_PID} kill ${DAEMON_PID}

View file

@ -15,7 +15,13 @@ else
MANIFEST_TARGET="$EL_MANIFEST_DEV" MANIFEST_TARGET="$EL_MANIFEST_DEV"
fi fi
if command -v nproc >/dev/null 2>&1; then
JOBS="$(nproc --all)"
else
JOBS="$(sysctl -n hw.ncpu)"
fi
.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 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 $(nproc --all) -v -m ${MANIFEST_TARGET} .venv.erplibre/bin/repo sync -c -j "$JOBS" -v -m ${MANIFEST_TARGET}
kill ${DAEMON_PID} kill ${DAEMON_PID}

View file

@ -14,10 +14,16 @@ else
MANIFEST_TARGET="$EL_MANIFEST_DEV" MANIFEST_TARGET="$EL_MANIFEST_DEV"
fi fi
if command -v nproc >/dev/null 2>&1; then
JOBS="$(nproc --all)"
else
JOBS="$(sysctl -n hw.ncpu)"
fi
# Generate local manifest # Generate local manifest
.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_mobile .venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_mobile
.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 init -u git://127.0.0.1:9418/ -b $(git rev-parse --verify HEAD) -m ${MANIFEST_TARGET} "$@"
.venv.erplibre/bin/repo sync -c -j $(nproc --all) -v -m ${MANIFEST_TARGET} .venv.erplibre/bin/repo sync -c -j "$JOBS" -v -m ${MANIFEST_TARGET}
kill ${DAEMON_PID} kill ${DAEMON_PID}

View file

@ -15,7 +15,13 @@ else
MANIFEST_TARGET="$EL_MANIFEST_DEV" MANIFEST_TARGET="$EL_MANIFEST_DEV"
fi fi
if command -v nproc >/dev/null 2>&1; then
JOBS="$(nproc --all)"
else
JOBS="$(sysctl -n hw.ncpu)"
fi
.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 init -u git://127.0.0.1:9418/ -b $(git rev-parse --verify HEAD) -m ${MANIFEST_TARGET}
.venv.erplibre/bin/repo sync -c -j $(nproc --all) -v -m ${MANIFEST_TARGET} .venv.erplibre/bin/repo sync -c -j "$JOBS" -v -m ${MANIFEST_TARGET}
kill ${DAEMON_PID} kill ${DAEMON_PID}

View file

@ -5,6 +5,12 @@
#EL_MANIFEST_PROD="./default.xml" #EL_MANIFEST_PROD="./default.xml"
#EL_MANIFEST_DEV="./manifest/default.dev.xml" #EL_MANIFEST_DEV="./manifest/default.dev.xml"
if command -v nproc >/dev/null 2>&1; then
JOBS="$(nproc --all)"
else
JOBS="$(sysctl -n hw.ncpu)"
fi
# Update git-repo # Update git-repo
.venv.erplibre/bin/repo init -u https://github.com/ERPLibre/ERPLibre -b $(git rev-parse --verify HEAD) .venv.erplibre/bin/repo init -u https://github.com/ERPLibre/ERPLibre -b $(git rev-parse --verify HEAD)
.venv.erplibre/bin/repo sync -v -j $(nproc --all) .venv.erplibre/bin/repo sync -v -j "$JOBS"

View file

@ -3,7 +3,7 @@
Run this script when doing database migration. Example : Run this script when doing database migration. Example :
```bash ```bash
source ./.venv.odoo15.0_python3.8.20/bin/activate && cat ./script/migration/fix_migration_odoo140_to_odoo150.py | ./odoo15.0/odoo/odoo-bin shell -d DATABASE source ./.venv.odoo15.0_python3.8.20/bin/activate && cat ./script/odoo/migration/fix_migration_odoo140_to_odoo150.py | ./odoo15.0/odoo/odoo-bin shell -d DATABASE
``` ```
Check [uninstall_module_list_odoo140_to_odoo150.txt](uninstall_module_list_odoo140_to_odoo150.txt) Check [uninstall_module_list_odoo140_to_odoo150.txt](uninstall_module_list_odoo140_to_odoo150.txt)

View file

@ -13,6 +13,7 @@ import sys
import tempfile import tempfile
import time import time
import tkinter as tk import tkinter as tk
from pathlib import Path
from tkinter import filedialog from tkinter import filedialog
from pykeepass import PyKeePass from pykeepass import PyKeePass
@ -226,10 +227,18 @@ class SeleniumLib(object):
self.config.firefox_binary_path self.config.firefox_binary_path
) )
elif not self.config.use_network: elif not self.config.use_network:
status_location = subprocess.check_output( # test snap firefox for Ubuntu first
["which", "firefox"], text=True snap_firefox_path = (
).strip() "/snap/firefox/current/usr/lib/firefox/firefox"
firefox_options.binary_location = status_location )
firefox_path_exist = Path(snap_firefox_path).exists()
if firefox_path_exist:
firefox_options.binary_location = snap_firefox_path
else:
status_location = subprocess.check_output(
["which", "firefox"], text=True
).strip()
firefox_options.binary_location = status_location
firefox_profile = webdriver.FirefoxProfile() firefox_profile = webdriver.FirefoxProfile()
firefox_profile.set_preference( firefox_profile.set_preference(

View file

@ -54,6 +54,8 @@ try:
# import rich # import rich
import todo_upgrade import todo_upgrade
from pykeepass import PyKeePass from pykeepass import PyKeePass
from script.execute import execute
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
humanize = None humanize = None
ENABLE_CRASH = True ENABLE_CRASH = True
@ -81,34 +83,9 @@ class TODO:
def __init__(self): def __init__(self):
self.dir_path = None self.dir_path = None
self.kdbx = None self.kdbx = None
self.init()
self.file_path = None self.file_path = None
self.config_file = config_file.ConfigFile() self.config_file = config_file.ConfigFile()
self.execute = execute.Execute()
def init(self):
# Get command
self.cmd_source_erplibre = ""
self.cmd_source_default = ""
exec_path_gnome_terminal = shutil.which("gnome-terminal")
if exec_path_gnome_terminal:
self.cmd_source_erplibre = (
f"gnome-terminal -- bash -c 'source"
f" ./{cst_venv_erplibre}/bin/activate;%s'"
)
self.cmd_source_default = "gnome-terminal -- bash -c '" f"%s'"
else:
exec_path_tell = shutil.which("osascript")
if exec_path_tell:
self.cmd_source_erplibre = (
"osascript -e 'tell application \"Terminal\"'"
)
self.cmd_source_erplibre += " -e 'tell application \"System Events\" to keystroke \"t\" using {command down}' -e 'delay 0.1' -e 'do script \""
self.cmd_source_erplibre += f"cd {os.getcwd()}; source ./{cst_venv_erplibre}/bin/activate; %s\" in front window'"
self.cmd_source_erplibre += " -e 'end tell'"
else:
self.cmd_source_erplibre = (
f"source ./{cst_venv_erplibre}/bin/activate;%s"
)
def run(self): def run(self):
with open(self.config_file.get_logo_ascii_file_path()) as my_file: with open(self.config_file.get_logo_ascii_file_path()) as my_file:
@ -150,7 +127,7 @@ class TODO:
# f" ./{cst_venv_erplibre}/bin/activate;make todo'" # f" ./{cst_venv_erplibre}/bin/activate;make todo'"
# ) # )
cmd = "make todo" cmd = "make todo"
self.executer_commande_live(cmd, source_erplibre=True) self.execute.exec_command_live(cmd, source_erplibre=True)
# elif status == "3" or status == "install": # elif status == "3" or status == "install":
# print("install") # print("install")
else: else:
@ -229,6 +206,7 @@ class TODO:
[5] Doc - Recherche de documentation [5] Doc - Recherche de documentation
[6] Database - Outils sur les bases de données [6] Database - Outils sur les bases de données
[7] Process - Outils sur les exécutions [7] Process - Outils sur les exécutions
[8] Config - Traitement du fichier de configuration
[0] Retour [0] Retour
""" """
while True: while True:
@ -264,6 +242,10 @@ class TODO:
status = self.prompt_execute_process() status = self.prompt_execute_process()
if status is not False: if status is not False:
return return
elif status == "8":
status = self.prompt_execute_config()
if status is not False:
return
else: else:
print("Commande non trouvée 🤖!") print("Commande non trouvée 🤖!")
@ -280,7 +262,7 @@ class TODO:
) )
if first_installation_input == "y": if first_installation_input == "y":
cmd = "./script/version/update_env_version.py --install" cmd = "./script/version/update_env_version.py --install"
self.executer_commande_live(cmd, source_erplibre=True) self.execute.exec_command_live(cmd, source_erplibre=True)
print("Wait after OS installation before continue.") print("Wait after OS installation before continue.")
# First detect pycharm, need to be open before installation and close to increase speed # First detect pycharm, need to be open before installation and close to increase speed
@ -312,7 +294,7 @@ class TODO:
pycharm_bin = "pycharm" if has_pycharm else "pycharm-community" pycharm_bin = "pycharm" if has_pycharm else "pycharm-community"
cmd = f"cd {os.getcwd()} && {pycharm_bin} ./" cmd = f"cd {os.getcwd()} && {pycharm_bin} ./"
self.executer_commande_live( self.execute.exec_command_live(
cmd, cmd,
source_erplibre=False, source_erplibre=False,
single_source_erplibre=False, single_source_erplibre=False,
@ -432,7 +414,7 @@ class TODO:
makefile_cmd = dct_instance.get("makefile_cmd") makefile_cmd = dct_instance.get("makefile_cmd")
if makefile_cmd and not ignore_makefile: if makefile_cmd and not ignore_makefile:
status = self.executer_commande_live( status = self.execute.exec_command_live(
f"make {makefile_cmd}", f"make {makefile_cmd}",
source_erplibre=False, source_erplibre=False,
single_source_erplibre=True, single_source_erplibre=True,
@ -459,10 +441,10 @@ class TODO:
if callback: if callback:
callback(dct_instance) callback(dct_instance)
def fill_help_info(self, lst_instance): def fill_help_info(self, lst_choice):
help_info = "Commande :\n" help_info = "Commande :\n"
help_end = "[0] Retour\n" help_end = "[0] Retour\n"
for i, dct_instance in enumerate(lst_instance): for i, dct_instance in enumerate(lst_choice):
help_info += ( help_info += (
f"[{i + 1}] " + dct_instance["prompt_description"] + "\n" f"[{i + 1}] " + dct_instance["prompt_description"] + "\n"
) )
@ -473,16 +455,16 @@ class TODO:
# TODO proposer le déploiement à distance # TODO proposer le déploiement à distance
# TODO proposer l'exécution de docker # TODO proposer l'exécution de docker
# TODO proposer la création de docker # TODO proposer la création de docker
lst_instance = self.config_file.get_config("instance") lst_choice = self.config_file.get_config("instance")
init_len = len(lst_instance) init_len = len(lst_choice)
if os.path.exists(MOBILE_HOME_PATH): if os.path.exists(MOBILE_HOME_PATH):
dct_upgrade_odoo_database = { dct_upgrade_odoo_database = {
"prompt_description": "Mobile - Compile and run software", "prompt_description": "Mobile - Compile and run software",
"callback": self.callback_make_mobile_home, "callback": self.callback_make_mobile_home,
} }
lst_instance.append(dct_upgrade_odoo_database) lst_choice.append(dct_upgrade_odoo_database)
help_info = self.fill_help_info(lst_instance) help_info = self.fill_help_info(lst_choice)
while True: while True:
status = click.prompt(help_info) status = click.prompt(help_info)
@ -498,15 +480,15 @@ class TODO:
status = click.confirm( status = click.confirm(
"Voulez-vous une nouvelle instance?" "Voulez-vous une nouvelle instance?"
) )
dct_instance = lst_instance[int_cmd - 1] dct_instance = lst_choice[int_cmd - 1]
self.execute_from_configuration( self.execute_from_configuration(
dct_instance, dct_instance,
exec_run_db=True, exec_run_db=True,
ignore_makefile=not bool(status), ignore_makefile=not bool(status),
) )
elif int_cmd <= len(lst_instance): elif int_cmd <= len(lst_choice):
# Execute dynamic instance # Execute dynamic instance
dct_instance = lst_instance[int_cmd - 1] dct_instance = lst_choice[int_cmd - 1]
self.execute_from_configuration( self.execute_from_configuration(
dct_instance, dct_instance,
) )
@ -516,8 +498,8 @@ class TODO:
print("Commande non trouvée 🤖!") print("Commande non trouvée 🤖!")
def prompt_execute_fonction(self): def prompt_execute_fonction(self):
lst_instance = self.config_file.get_config("function") lst_choice = self.config_file.get_config("function")
help_info = self.fill_help_info(lst_instance) help_info = self.fill_help_info(lst_choice)
while True: while True:
status = click.prompt(help_info) status = click.prompt(help_info)
@ -528,9 +510,9 @@ class TODO:
cmd_no_found = True cmd_no_found = True
try: try:
int_cmd = int(status) int_cmd = int(status)
if 0 < int_cmd <= len(lst_instance): if 0 < int_cmd <= len(lst_choice):
cmd_no_found = False cmd_no_found = False
dct_instance = lst_instance[int_cmd - 1] dct_instance = lst_choice[int_cmd - 1]
self.execute_from_configuration(dct_instance) self.execute_from_configuration(dct_instance)
except ValueError: except ValueError:
pass pass
@ -538,7 +520,7 @@ class TODO:
print("Commande non trouvée 🤖!") print("Commande non trouvée 🤖!")
def prompt_execute_update(self): def prompt_execute_update(self):
# self.executer_commande_live(f"make {makefile_cmd}") # self.execute.exec_command_live(f"make {makefile_cmd}")
print("🤖 Mise à jour du développement") print("🤖 Mise à jour du développement")
# TODO détecter les modules en modification pour faire la mise à jour en cours # TODO détecter les modules en modification pour faire la mise à jour en cours
# TODO demander sur quel BD faire la mise à jour # TODO demander sur quel BD faire la mise à jour
@ -548,34 +530,34 @@ class TODO:
# TODO faire la mise à jour de ERPLibre # TODO faire la mise à jour de ERPLibre
# TODO faire l'upgrade d'un odoo vers un autre # TODO faire l'upgrade d'un odoo vers un autre
lst_instance = self.config_file.get_config("update_from_makefile") lst_choice = self.config_file.get_config("update_from_makefile")
dct_upgrade_odoo_database = { dct_upgrade_odoo_database = {
"prompt_description": "Upgrade Odoo - Migration Database", "prompt_description": "Upgrade Odoo - Migration Database",
} }
lst_instance.append(dct_upgrade_odoo_database) lst_choice.append(dct_upgrade_odoo_database)
dct_upgrade_poetry = { dct_upgrade_poetry = {
"prompt_description": "Upgrade Poetry - Dependency of Odoo", "prompt_description": "Upgrade Poetry - Dependency of Odoo",
} }
lst_instance.append(dct_upgrade_poetry) lst_choice.append(dct_upgrade_poetry)
help_info = self.fill_help_info(lst_instance) help_info = self.fill_help_info(lst_choice)
while True: while True:
status = click.prompt(help_info) status = click.prompt(help_info)
print() print()
if status == "0": if status == "0":
return False return False
elif status == str(len(lst_instance) - 1): elif status == str(len(lst_choice) - 1):
upgrade = todo_upgrade.TodoUpgrade(self) upgrade = todo_upgrade.TodoUpgrade(self)
upgrade.execute_odoo_upgrade() upgrade.execute_odoo_upgrade()
elif status == str(len(lst_instance)): elif status == str(len(lst_choice)):
self.upgrade_poetry() self.upgrade_poetry()
else: else:
cmd_no_found = True cmd_no_found = True
try: try:
int_cmd = int(status) - 1 int_cmd = int(status) - 1
if 0 < int_cmd <= len(lst_instance): if 0 < int_cmd <= len(lst_choice):
cmd_no_found = False cmd_no_found = False
dct_instance = lst_instance[int_cmd - 1] dct_instance = lst_choice[int_cmd - 1]
self.execute_from_configuration(dct_instance) self.execute_from_configuration(dct_instance)
except ValueError: except ValueError:
pass pass
@ -596,27 +578,36 @@ class TODO:
# [0] Retour # [0] Retour
# """ # """
lst_instance = self.config_file.get_config("code_from_makefile") lst_choice = self.config_file.get_config("code_from_makefile")
dct_upgrade_odoo_database = {
"prompt_description": "Open SHELL",
}
lst_choice.append(dct_upgrade_odoo_database)
dct_upgrade_odoo_database = { dct_upgrade_odoo_database = {
"prompt_description": "Upgrade Module", "prompt_description": "Upgrade Module",
} }
lst_instance.append(dct_upgrade_odoo_database) lst_choice.append(dct_upgrade_odoo_database)
help_info = self.fill_help_info(lst_instance)
help_info = self.fill_help_info(lst_choice)
while True: while True:
status = click.prompt(help_info) status = click.prompt(help_info)
print() print()
if status == "0": if status == "0":
return False return False
elif status == str(len(lst_instance)): elif status == str(len(lst_choice)):
self.upgrade_module() self.upgrade_module()
elif status == str(len(lst_choice) - 1):
self.open_shell_on_database()
else: else:
cmd_no_found = True cmd_no_found = True
try: try:
int_cmd = int(status) int_cmd = int(status)
if 0 < int_cmd <= len(lst_instance): if 0 < int_cmd <= len(lst_choice):
cmd_no_found = False cmd_no_found = False
dct_instance = lst_instance[int_cmd - 1] dct_instance = lst_choice[int_cmd - 1]
self.execute_from_configuration(dct_instance) self.execute_from_configuration(dct_instance)
except ValueError: except ValueError:
pass pass
@ -625,13 +616,13 @@ class TODO:
def prompt_execute_doc(self): def prompt_execute_doc(self):
print("🤖 Vous cherchez de la documentation?") print("🤖 Vous cherchez de la documentation?")
lst_instance = [ lst_choice = [
{"prompt_description": "Migration module coverage"}, {"prompt_description": "Migration module coverage"},
{"prompt_description": "What change between version"}, {"prompt_description": "What change between version"},
{"prompt_description": "OCA guidelines"}, {"prompt_description": "OCA guidelines"},
{"prompt_description": "OCA migration Odoo 19 milestone"}, {"prompt_description": "OCA migration Odoo 19 milestone"},
] ]
help_info = self.fill_help_info(lst_instance) help_info = self.fill_help_info(lst_choice)
while True: while True:
status = click.prompt(help_info) status = click.prompt(help_info)
@ -674,13 +665,13 @@ class TODO:
def prompt_execute_database(self): def prompt_execute_database(self):
print("🤖 Faites des modifications sur les bases de données!") print("🤖 Faites des modifications sur les bases de données!")
lst_instance = [ lst_choice = [
{ {
"prompt_description": "Download database to create backup (.zip)" "prompt_description": "Download database to create backup (.zip)"
}, },
{"prompt_description": "Restore from backup (.zip)"}, {"prompt_description": "Restore from backup (.zip)"},
] ]
help_info = self.fill_help_info(lst_instance) help_info = self.fill_help_info(lst_choice)
while True: while True:
status = click.prompt(help_info) status = click.prompt(help_info)
@ -696,10 +687,10 @@ class TODO:
def prompt_execute_process(self): def prompt_execute_process(self):
print("🤖 Manipuler les processus d'exécution!") print("🤖 Manipuler les processus d'exécution!")
lst_instance = [ lst_choice = [
{"prompt_description": "Kill process from actual port"}, {"prompt_description": "Kill process from actual port"},
] ]
help_info = self.fill_help_info(lst_instance) help_info = self.fill_help_info(lst_choice)
while True: while True:
status = click.prompt(help_info) status = click.prompt(help_info)
@ -711,6 +702,122 @@ class TODO:
else: else:
print("Commande non trouvée 🤖!") print("Commande non trouvée 🤖!")
def prompt_execute_config(self):
print("🤖 Manipuler la configuration ERPLibre et Odoo!")
lst_choice = [
{"prompt_description": "Generate all configuration"},
{"prompt_description": "Generate from pre-configuration"},
{"prompt_description": "Generate from backup file"},
{"prompt_description": "Generate from database"},
]
help_info = self.fill_help_info(lst_choice)
while True:
status = click.prompt(help_info)
print()
if status == "0":
return False
elif status == "1":
self.generate_config()
elif status == "2":
self.generate_config_from_preconfiguration()
elif status == "3":
self.generate_config_from_backup()
elif status == "4":
self.generate_config_from_database()
else:
print("Commande non trouvée 🤖!")
def generate_config(self, add_arg=None):
# Repeating to get all item before get group
cmd = (
f"./script/git/git_repo_update_group.py;"
f"./script/generate_config.sh"
)
if add_arg:
cmd += (
f";./script/git/git_repo_update_group.py {add_arg};"
f"./script/generate_config.sh"
)
self.execute.exec_command_live(
cmd,
source_erplibre=False,
single_source_erplibre=True,
)
def generate_config_from_preconfiguration(self):
lst_choice = [
{"prompt_description": "base"},
{"prompt_description": "base + code_generator"},
{"prompt_description": "base + image_db"},
{"prompt_description": "all"},
# {"prompt_description": "base + migration"},
]
help_info = self.fill_help_info(lst_choice)
while True:
status = click.prompt(help_info)
print()
if status == "0":
return False
elif status == "1":
group = "base"
str_group = f"--group {group}"
self.generate_config(add_arg=str_group)
elif status == "2":
group = "base,code_generator"
str_group = f"--group {group}"
self.generate_config(add_arg=str_group)
elif status == "3":
group = "base,image_db"
str_group = f"--group {group}"
self.generate_config(add_arg=str_group)
elif status == "4":
self.generate_config()
# elif status == "5":
# group = "base,migration"
# str_group = f"--group {group}"
# self.generate_config(add_arg=str_group)
else:
print("Commande non trouvée 🤖!")
def generate_config_from_backup(self):
file_name = self.open_file_image_db()
add_arg = f"--from_backup_name {file_name} --add_repo odoo18.0/addons/MathBenTech_development"
self.generate_config(add_arg=add_arg)
def generate_config_from_database(self):
database_name = self.select_database()
str_arg = f"--database {database_name}"
self.generate_config(add_arg=str_arg)
return False
def select_database(self):
cmd_server = f"./odoo_bin.sh db --list"
status, lst_database = self.execute.exec_command_live(
cmd_server,
return_status_and_output=True,
source_erplibre=False,
single_source_erplibre=True,
)
lst_choice = [{"prompt_description": a.strip()} for a in lst_database]
help_info = self.fill_help_info(lst_choice)
lst_str_choice = [str(a) for a in range(len(lst_choice) + 1) if a]
while True:
status = click.prompt(help_info)
print()
if status == "0":
return False
elif status in lst_str_choice:
database_name = lst_database[int(status) - 1].strip()
print(database_name)
return database_name
else:
print("Commande non trouvée 🤖!")
def get_odoo_version(self): def get_odoo_version(self):
with open(VERSION_DATA_FILE) as txt: with open(VERSION_DATA_FILE) as txt:
data_version = json.load(txt) data_version = json.load(txt)
@ -776,13 +883,13 @@ class TODO:
# f'parallel ::: "./run.sh -d {bd}" "sleep' # f'parallel ::: "./run.sh -d {bd}" "sleep'
# f' 3;./script/selenium/web_login.py{extra_cmd_web_login}"' # f' 3;./script/selenium/web_login.py{extra_cmd_web_login}"'
# ) # )
# self.executer_commande_live(cmd) # self.execute.exec_command_live(cmd)
cmd_server = f"./run.sh -d {bd};bash" cmd_server = f"./run.sh -d {bd};bash"
self.executer_commande_live(cmd_server) self.execute.exec_command_live(cmd_server)
cmd_client = ( cmd_client = (
f"sleep 3;./script/selenium/web_login.py{extra_cmd_web_login};bash" f"sleep 3;./script/selenium/web_login.py{extra_cmd_web_login};bash"
) )
self.executer_commande_live(cmd_client) self.execute.exec_command_live(cmd_client)
def prompt_execute_selenium(self, command=None, extra_cmd_web_login=""): def prompt_execute_selenium(self, command=None, extra_cmd_web_login=""):
lst_cmd = [] lst_cmd = []
@ -798,132 +905,12 @@ class TODO:
lst_cmd.append(cmd + extra_cmd_web_login) lst_cmd.append(cmd + extra_cmd_web_login)
if len(lst_cmd) == 1: if len(lst_cmd) == 1:
self.executer_commande_live(lst_cmd[0]) self.execute.exec_command_live(lst_cmd[0])
elif len(lst_cmd) > 1: elif len(lst_cmd) > 1:
new_cmd = "parallel ::: " new_cmd = "parallel ::: "
for i, cmd in enumerate(lst_cmd): for i, cmd in enumerate(lst_cmd):
new_cmd += f' "sleep {1 * i};{cmd}"' new_cmd += f' "sleep {1 * i};{cmd}"'
self.executer_commande_live(new_cmd) self.execute.exec_command_live(new_cmd)
def executer_commande_live(
self,
commande,
source_erplibre=True,
quiet=False,
single_source_erplibre=False,
new_window=False,
single_source_odoo=False,
source_odoo="",
new_env=None,
return_status_and_command=False,
return_status_and_output=False,
return_status_and_output_and_command=False,
):
"""
Exécute une commande et affiche la sortie en direct.
Args:
commande (str): La commande à exécuter (sous forme de chaîne de caractères).
"""
my_env = os.environ.copy()
if new_env:
my_env.update(new_env)
process_start_time = time.time()
return_status = None
if source_erplibre:
# commande = f"source ./{cst_venv_erplibre}/bin/activate && " + commande
# cmd = (
# f"gnome-terminal --tab -- bash -c 'source"
# f" ./{cst_venv_erplibre}/bin/activate;{commande}'"
# )
commande = self.cmd_source_erplibre % commande
# os.system(f"./script/terminal/open_terminal.sh {commande}")
elif single_source_erplibre:
commande = (
f"source ./{cst_venv_erplibre}/bin/activate && %s" % commande
)
elif single_source_odoo:
if not source_odoo and os.path.exists("./.erplibre-version"):
with open("./.erplibre-version") as f:
source_odoo = f.read()
if not source_odoo:
_logger.error(
f"You cannot execute Odoo command if no version is installed. Command : {commande}"
)
return -1
commande = (
f"source ./.venv.{source_odoo}/bin/activate && {commande}"
)
elif new_window and self.cmd_source_default:
commande = self.cmd_source_default % commande
print("🏠 ⬇ Execute command :")
print(commande)
lst_output = []
try:
process = subprocess.Popen(
commande,
shell=True,
executable="/bin/bash",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1, # Désactive la mise en tampon pour la sortie en direct
universal_newlines=True, # Pour traiter les sauts de lignes correctement
env=my_env,
)
while True:
ligne = process.stdout.readline()
if not ligne:
break
print(ligne, end="")
if (
return_status_and_output
or return_status_and_output_and_command
):
lst_output.append(ligne)
process.wait() # Attendre la fin du process
return_status = process.returncode
if process.returncode != 0 and not quiet:
print(
"La commande a retourné un code d'erreur :"
f" {process.returncode}"
)
except FileNotFoundError:
if "password" in commande:
print(
f"Erreur : La commande '{commande.split(' ')[0]}'[...] n'a"
" pas été trouvée."
)
else:
print(
f"Erreur : La commande '{commande}' n'a pas été trouvée."
)
except Exception as e:
print(f"Une erreur s'est produite : {e}")
process_end_time = time.time()
duration_sec = process_end_time - process_start_time
if humanize:
duration_delta = datetime.timedelta(seconds=duration_sec)
humain_time = humanize.precisedelta(duration_delta)
print(f"🏠 ⬆ Executed ({humain_time}) :")
else:
print(f"🏠 ⬆ Executed ({duration_sec:.2f} sec.) :")
print(commande)
print()
if return_status_and_output_and_command:
return return_status, commande, lst_output
if return_status_and_command:
return return_status, commande
if return_status_and_output:
return return_status, lst_output
return return_status
def crash_diagnostic(self, e): def crash_diagnostic(self, e):
# TODO show message at start if os.path.exists(file_error_path) # TODO show message at start if os.path.exists(file_error_path)
@ -948,7 +935,7 @@ class TODO:
time.sleep(0.5) time.sleep(0.5)
cmd = "./script/todo/source_todo.sh" cmd = "./script/todo/source_todo.sh"
# self.restart_script(e) # self.restart_script(e)
self.executer_commande_live(cmd, source_erplibre=True) self.execute.exec_command_live(cmd, source_erplibre=True)
sys.exit(1) sys.exit(1)
if os.path.exists(cst_venv_erplibre): if os.path.exists(cst_venv_erplibre):
print("Import error : ") print("Import error : ")
@ -980,19 +967,30 @@ class TODO:
from pykeepass import PyKeePass from pykeepass import PyKeePass
except ImportError: except ImportError:
print("Rerun and exit") print("Rerun and exit")
self.executer_commande_live(cmd, source_erplibre=True) self.execute.exec_command_live(cmd, source_erplibre=True)
sys.exit(1) sys.exit(1)
print("No error") print("No error")
else: else:
self.prompt_install() self.prompt_install()
def open_shell_on_database(self):
database = self.select_database()
cmd_server = f"./odoo_bin.sh shell -d {database}"
status, lst_database = self.execute.exec_command_live(
cmd_server,
return_status_and_output=True,
source_erplibre=False,
single_source_erplibre=True,
new_window=True,
)
def upgrade_module(self): def upgrade_module(self):
upgrade = todo_upgrade.TodoUpgrade(self) upgrade = todo_upgrade.TodoUpgrade(self)
upgrade.execute_module_upgrade() upgrade.execute_module_upgrade()
def upgrade_poetry(self): def upgrade_poetry(self):
# Only show the version to the user # Only show the version to the user
status = self.executer_commande_live( status = self.execute.exec_command_live(
f"make version", f"make version",
source_erplibre=False, source_erplibre=False,
) )
@ -1001,7 +999,7 @@ class TODO:
"💬 Would you like to fetch all your git repositories, you need it (y/Y) : " "💬 Would you like to fetch all your git repositories, you need it (y/Y) : "
) )
if git_repo_update_input.strip().lower() == "y": if git_repo_update_input.strip().lower() == "y":
status = self.executer_commande_live( status = self.execute.exec_command_live(
f"./script/manifest/update_manifest_local_dev.sh", f"./script/manifest/update_manifest_local_dev.sh",
source_erplibre=False, source_erplibre=False,
) )
@ -1022,7 +1020,7 @@ class TODO:
except Exception as e: except Exception as e:
pass pass
status = self.executer_commande_live( status = self.execute.exec_command_live(
f"pip install -r requirement/erplibre_require-ments-poetry.txt && " f"pip install -r requirement/erplibre_require-ments-poetry.txt && "
f"./script/poetry/poetry_update.py -f", f"./script/poetry/poetry_update.py -f",
source_erplibre=False, source_erplibre=False,
@ -1042,20 +1040,13 @@ class TODO:
if status == "1": if status == "1":
file_name = status file_name = status
else: else:
self.dir_path = "" file_name = self.open_file_image_db
file_browser = todo_file_browser.FileBrowser(
path_image_db, self.on_dir_selected
)
file_browser.run_main_frame()
file_name = os.path.basename(self.dir_path)
print(file_name)
database_name = input("💬 Database name : ") database_name = input("💬 Database name : ")
if not database_name: if not database_name:
_logger.error("Missing database name") _logger.error("Missing database name")
return return
status, lst_output = self.executer_commande_live( status, lst_output = self.execute.exec_command_live(
f"python3 ./script/database/db_restore.py -d {database_name} --ignore_cache --image {file_name}", f"python3 ./script/database/db_restore.py -d {database_name} --ignore_cache --image {file_name}",
return_status_and_output=True, return_status_and_output=True,
single_source_erplibre=True, single_source_erplibre=True,
@ -1067,7 +1058,7 @@ class TODO:
.lower() .lower()
) )
if status == "y": if status == "y":
status, lst_output = self.executer_commande_live( status, lst_output = self.execute.exec_command_live(
f"./script/addons/update_prod_to_dev.sh {database_name}", f"./script/addons/update_prod_to_dev.sh {database_name}",
return_status_and_output=True, return_status_and_output=True,
single_source_erplibre=True, single_source_erplibre=True,
@ -1079,19 +1070,32 @@ class TODO:
.lower() .lower()
) )
if status == "y": if status == "y":
status, lst_output = self.executer_commande_live( status, lst_output = self.execute.exec_command_live(
f"./script/addons/update_addons_all.sh {database_name}", f"./script/addons/update_addons_all.sh {database_name}",
return_status_and_output=True, return_status_and_output=True,
single_source_erplibre=True, single_source_erplibre=True,
source_erplibre=False, source_erplibre=False,
) )
def open_file_image_db(self):
self.dir_path = ""
path_image_db = os.path.join(os.getcwd(), "image_db")
# self.dir_path is over-write into on_dir_selected
file_browser = todo_file_browser.FileBrowser(
path_image_db, self.on_dir_selected
)
file_browser.run_main_frame()
file_name = os.path.basename(self.dir_path)
print(file_name)
return file_name
def process_kill_from_port(self): def process_kill_from_port(self):
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
cfg.read("./config.conf") cfg.read("./config.conf")
http_port = cfg.getint("options", "http_port") http_port = cfg.getint("options", "http_port")
status = self.executer_commande_live( status = self.execute.exec_command_live(
f"./script/process/kill_process_by_port.py {http_port} --kill-tree --nb_parent 2", f"./script/process/kill_process_by_port.py {http_port} --kill-tree --nb_parent 2",
source_erplibre=False, source_erplibre=False,
) )
@ -1099,7 +1103,7 @@ class TODO:
def download_database_backup_cli(self, show_remote_list=True): def download_database_backup_cli(self, show_remote_list=True):
database_domain = input("Domain Odoo (ex. https://mondomain.com) : ") database_domain = input("Domain Odoo (ex. https://mondomain.com) : ")
if show_remote_list: if show_remote_list:
status, lst_output = self.executer_commande_live( status, lst_output = self.execute.exec_command_live(
f"python3 ./script/database/list_remote.py --raw --odoo-url {database_domain}", f"python3 ./script/database/list_remote.py --raw --odoo-url {database_domain}",
return_status_and_output=True, return_status_and_output=True,
single_source_erplibre=True, single_source_erplibre=True,
@ -1134,7 +1138,7 @@ class TODO:
my_env["DATABASE_NAME"] = database_name my_env["DATABASE_NAME"] = database_name
my_env["OUTPUT_FILE_PATH"] = output_path my_env["OUTPUT_FILE_PATH"] = output_path
my_env["ODOO_URL"] = database_domain my_env["ODOO_URL"] = database_domain
status, cmd_executed = self.executer_commande_live( status, cmd_executed = self.execute.exec_command_live(
cmd, cmd,
source_erplibre=False, source_erplibre=False,
return_status_and_command=True, return_status_and_command=True,
@ -1265,7 +1269,7 @@ class TODO:
# Rename with script bash # Rename with script bash
cmd_client = f'cd {MOBILE_HOME_PATH} && npx cap init "{project_name}" "{package_name}" && ./rename_android.sh "{project_name}" "{package_name}" && npx cap sync android' cmd_client = f'cd {MOBILE_HOME_PATH} && npx cap init "{project_name}" "{package_name}" && ./rename_android.sh "{project_name}" "{package_name}" && npx cap sync android'
self.executer_commande_live(cmd_client, source_erplibre=False) self.execute.exec_command_live(cmd_client, source_erplibre=False)
# dotenv_mobile = dotenv.dotenv_values(dotenv_file) # dotenv_mobile = dotenv.dotenv_values(dotenv_file)
# dotenv_mobile["VITE_TITLE"] = project_name # dotenv_mobile["VITE_TITLE"] = project_name
@ -1293,7 +1297,7 @@ class TODO:
) )
if do_change_picture_menu: if do_change_picture_menu:
status = self.executer_commande_live( status = self.execute.exec_command_live(
f"cd {MOBILE_HOME_PATH} && npx cap open android;bash", f"cd {MOBILE_HOME_PATH} && npx cap open android;bash",
source_erplibre=False, source_erplibre=False,
new_window=True, new_window=True,
@ -1305,11 +1309,11 @@ class TODO:
"Did you finish to update image with Android-Studio ? Press to continue ..." "Did you finish to update image with Android-Studio ? Press to continue ..."
) )
cmd_client = "cp ./mobile/erplibre_home_mobile/android/app/src/main/ic_launcher-playstore.png ./mobile/erplibre_home_mobile/src/assets/company_logo.png" cmd_client = "cp ./mobile/erplibre_home_mobile/android/app/src/main/ic_launcher-playstore.png ./mobile/erplibre_home_mobile/src/assets/company_logo.png"
self.executer_commande_live(cmd_client, source_erplibre=False) self.execute.exec_command_live(cmd_client, source_erplibre=False)
cmd_client = "cp ./mobile/erplibre_home_mobile/android/app/src/main/ic_launcher-playstore.png ./mobile/erplibre_home_mobile/src/assets/imgs/logo.png" cmd_client = "cp ./mobile/erplibre_home_mobile/android/app/src/main/ic_launcher-playstore.png ./mobile/erplibre_home_mobile/src/assets/imgs/logo.png"
self.executer_commande_live(cmd_client, source_erplibre=False) self.execute.exec_command_live(cmd_client, source_erplibre=False)
status = self.executer_commande_live( status = self.execute.exec_command_live(
"./mobile/compile_and_run.sh", source_erplibre=False "./mobile/compile_and_run.sh", source_erplibre=False
) )

View file

@ -19,6 +19,7 @@ new_path = os.path.normpath(
) )
sys.path.append(new_path) sys.path.append(new_path)
from script.execute import execute
from script.git.git_tool import GitTool from script.git.git_tool import GitTool
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -52,6 +53,7 @@ class TodoUpgrade:
self.lst_command_executed = [] self.lst_command_executed = []
self.dct_module_per_version = {} self.dct_module_per_version = {}
self.dct_module_per_dct_version_path = {} self.dct_module_per_dct_version_path = {}
self.execute = execute.Execute()
def write_config(self): def write_config(self):
if "date_create" not in self.dct_progression.keys(): if "date_create" not in self.dct_progression.keys():
@ -1857,7 +1859,7 @@ class TodoUpgrade:
get_output = True get_output = True
output = None output = None
if get_output: if get_output:
status, cmd_executed, output = self.todo.executer_commande_live( status, cmd_executed, output = self.execute.exec_command_live(
cmd, cmd,
source_erplibre=False, source_erplibre=False,
single_source_odoo=single_source_odoo, single_source_odoo=single_source_odoo,
@ -1866,7 +1868,7 @@ class TodoUpgrade:
quiet=quiet, quiet=quiet,
) )
else: else:
status, cmd_executed = self.todo.executer_commande_live( status, cmd_executed = self.execute.exec_command_live(
cmd, cmd,
source_erplibre=False, source_erplibre=False,
single_source_odoo=single_source_odoo, single_source_odoo=single_source_odoo,