[UPD] script Format

This commit is contained in:
Mathieu Benoit 2026-03-13 15:04:14 -04:00
parent bee6d50018
commit 36ae1d9beb
8 changed files with 171 additions and 227 deletions

View file

@ -96,9 +96,7 @@ def commit_by_directory():
run_git_command(f'cd "{config.path}" && git rm -r {directory}') run_git_command(f'cd "{config.path}" && git rm -r {directory}')
# Crée le commit # Crée le commit
commit_message = ( commit_message = f"[{mig_prefix_msg}] {directory}: Migration to {config.odoo_version}"
f"[{mig_prefix_msg}] {directory}: Migration to {config.odoo_version}"
)
commit_result = run_git_command( commit_result = run_git_command(
f'cd "{config.path}" && git commit -m "{commit_message}"' f'cd "{config.path}" && git commit -m "{commit_message}"'
) )

View file

@ -10,28 +10,31 @@ def get_db_list_xmlrpc(odoo_url):
Retrieves the list of Odoo databases using the XML-RPC API. Retrieves the list of Odoo databases using the XML-RPC API.
""" """
try: try:
common = xmlrpc.client.ServerProxy(f'{odoo_url}/xmlrpc/db') common = xmlrpc.client.ServerProxy(f"{odoo_url}/xmlrpc/db")
db_list = common.list() db_list = common.list()
return db_list return db_list
except xmlrpc.client.Fault as e: except xmlrpc.client.Fault as e:
print(f"XML-RPC Error: {e.faultCode} - {e.faultString}", file=sys.stderr) print(
f"XML-RPC Error: {e.faultCode} - {e.faultString}", file=sys.stderr
)
return [] return []
except Exception as e: except Exception as e:
print(f"Connection Error: {e}", file=sys.stderr) print(f"Connection Error: {e}", file=sys.stderr)
return [] return []
# --- CLI using Click --- # --- CLI using Click ---
@click.command() @click.command()
@click.option( @click.option(
'--odoo-url', "--odoo-url",
default='http://localhost:8069', default="http://localhost:8069",
help='URL of the Odoo server.', help="URL of the Odoo server.",
show_default=True show_default=True,
) )
@click.option( @click.option(
'--raw', "--raw",
is_flag=True, is_flag=True,
help='Output one database per line, without extra formatting. Useful for scripting.' help="Output one database per line, without extra formatting. Useful for scripting.",
) )
def list_databases(odoo_url, raw=False): def list_databases(odoo_url, raw=False):
""" """
@ -56,5 +59,5 @@ def list_databases(odoo_url, raw=False):
# --- Script Execution --- # --- Script Execution ---
if __name__ == '__main__': if __name__ == "__main__":
list_databases() list_databases()

View file

@ -151,8 +151,7 @@ Use --production-ready for /srv/git (requires root).
type=int, type=int,
default=DEFAULT_JOBS, default=DEFAULT_JOBS,
help=( help=(
"Parallel jobs for init/remote/push" "Parallel jobs for init/remote/push" f" (default: {DEFAULT_JOBS})"
f" (default: {DEFAULT_JOBS})"
), ),
) )
parser.add_argument( parser.add_argument(
@ -247,11 +246,7 @@ def get_erplibre_root_project(erplibre_root):
capture_output=True, capture_output=True,
text=True, text=True,
) )
revision = ( revision = result.stdout.strip() if result.returncode == 0 else "master"
result.stdout.strip()
if result.returncode == 0
else "master"
)
return { return {
"name": ERPLIBRE_REPO_NAME, "name": ERPLIBRE_REPO_NAME,
@ -278,19 +273,13 @@ async def _init_single_bare_repo(git_path, project, semaphore):
return "skipped" return "skipped"
_logger.info(f" Creating: {bare_path}") _logger.info(f" Creating: {bare_path}")
_, err, rc = await _run_git( _, err, rc = await _run_git("git", "init", "--bare", bare_path)
"git", "init", "--bare", bare_path
)
if rc != 0: if rc != 0:
_logger.warning( _logger.warning(f" Init failed: {bare_path}: {err.strip()}")
f" Init failed: {bare_path}: {err.strip()}"
)
return "error" return "error"
# Enable git daemon export # Enable git daemon export
export_file = os.path.join( export_file = os.path.join(bare_path, "git-daemon-export-ok")
bare_path, "git-daemon-export-ok"
)
open(export_file, "w").close() open(export_file, "w").close()
# Allow pushing from shallow clones # Allow pushing from shallow clones
@ -312,10 +301,7 @@ async def init_bare_repos(git_path, projects, jobs):
semaphore = asyncio.Semaphore(jobs) semaphore = asyncio.Semaphore(jobs)
results = await asyncio.gather( results = await asyncio.gather(
*[ *[_init_single_bare_repo(git_path, p, semaphore) for p in projects]
_init_single_bare_repo(git_path, p, semaphore)
for p in projects
]
) )
created = results.count("created") created = results.count("created")
@ -352,9 +338,7 @@ async def _add_single_remote(
): ):
"""Add or update remote for a single repo (async).""" """Add or update remote for a single repo (async)."""
async with semaphore: async with semaphore:
repo_path = os.path.join( repo_path = os.path.join(erplibre_root, project["path"])
erplibre_root, project["path"]
)
if not os.path.isdir(repo_path): if not os.path.isdir(repo_path):
_logger.warning(f" Not found: {repo_path}") _logger.warning(f" Not found: {repo_path}")
return "error" return "error"
@ -368,9 +352,7 @@ async def _add_single_remote(
) )
# Check if remote already exists # Check if remote already exists
stdout, _, _ = await _run_git( stdout, _, _ = await _run_git("git", "-C", repo_path, "remote")
"git", "-C", repo_path, "remote"
)
existing_remotes = stdout.strip().split("\n") existing_remotes = stdout.strip().split("\n")
if remote_name in existing_remotes: if remote_name in existing_remotes:
@ -403,8 +385,7 @@ async def _add_single_remote(
) )
if rc != 0: if rc != 0:
_logger.warning( _logger.warning(
f" add failed for" f" add failed for" f" {project['path']}: {err.strip()}"
f" {project['path']}: {err.strip()}"
) )
return "error" return "error"
_logger.info(f" Added: {project['path']}") _logger.info(f" Added: {project['path']}")
@ -447,10 +428,7 @@ async def add_remotes(
added = results.count("added") added = results.count("added")
updated = results.count("updated") updated = results.count("updated")
errors = results.count("error") errors = results.count("error")
print( print(f"Remotes: {added} added, {updated} updated," f" {errors} errors")
f"Remotes: {added} added, {updated} updated,"
f" {errors} errors"
)
# --- Async workers for push --- # --- Async workers for push ---
@ -458,9 +436,7 @@ async def add_remotes(
async def _is_detached_head(repo_path): async def _is_detached_head(repo_path):
"""Check if a repo is in detached HEAD state.""" """Check if a repo is in detached HEAD state."""
_, _, rc = await _run_git( _, _, rc = await _run_git("git", "-C", repo_path, "symbolic-ref", "HEAD")
"git", "-C", repo_path, "symbolic-ref", "HEAD"
)
return rc != 0 return rc != 0
@ -471,9 +447,7 @@ async def _checkout_manifest_branch(repo_path, revision):
on the exact commit. We create/checkout a local branch on the exact commit. We create/checkout a local branch
matching the manifest revision so git push works. matching the manifest revision so git push works.
""" """
branch = ( branch = revision.split("/")[-1] if "/" in revision else revision
revision.split("/")[-1] if "/" in revision else revision
)
# Check if local branch already exists # Check if local branch already exists
_, _, rc = await _run_git( _, _, rc = await _run_git(
@ -485,9 +459,7 @@ async def _checkout_manifest_branch(repo_path, revision):
f"refs/heads/{branch}", f"refs/heads/{branch}",
) )
if rc == 0: if rc == 0:
await _run_git( await _run_git("git", "-C", repo_path, "checkout", branch)
"git", "-C", repo_path, "checkout", branch
)
else: else:
await _run_git( await _run_git(
"git", "git",
@ -514,9 +486,7 @@ async def _update_bare_head(git_path, project):
revision = project.get("revision", "") revision = project.get("revision", "")
if not revision: if not revision:
return return
branch = ( branch = revision.split("/")[-1] if "/" in revision else revision
revision.split("/")[-1] if "/" in revision else revision
)
await _run_git( await _run_git(
"git", "git",
"-C", "-C",
@ -529,20 +499,10 @@ async def _update_bare_head(git_path, project):
async def _try_unshallow(repo_path, project, remote_name): async def _try_unshallow(repo_path, project, remote_name):
"""Try to unshallow a repo by fetching full history.""" """Try to unshallow a repo by fetching full history."""
shallow_file = os.path.join( shallow_file = os.path.join(repo_path, ".git", "shallow")
repo_path, ".git", "shallow" _logger.info(f" Unshallowing {project['path']}...")
) stdout, _, _ = await _run_git("git", "-C", repo_path, "remote")
_logger.info( remotes = [r for r in stdout.strip().split("\n") if r and r != remote_name]
f" Unshallowing {project['path']}..."
)
stdout, _, _ = await _run_git(
"git", "-C", repo_path, "remote"
)
remotes = [
r
for r in stdout.strip().split("\n")
if r and r != remote_name
]
manifest_remote = project.get("remote", "") manifest_remote = project.get("remote", "")
if manifest_remote in remotes: if manifest_remote in remotes:
remotes.remove(manifest_remote) remotes.remove(manifest_remote)
@ -562,9 +522,7 @@ async def _try_unshallow(repo_path, project, remote_name):
except asyncio.TimeoutError: except asyncio.TimeoutError:
continue continue
if not os.path.exists(shallow_file): if not os.path.exists(shallow_file):
_logger.info( _logger.info(f" Unshallowed via {try_remote}")
f" Unshallowed via {try_remote}"
)
return return
if os.path.exists(shallow_file): if os.path.exists(shallow_file):
_logger.warning( _logger.warning(
@ -585,30 +543,22 @@ async def _push_single_repo(
): ):
"""Push a single repo to local remote (async).""" """Push a single repo to local remote (async)."""
async with semaphore: async with semaphore:
repo_path = os.path.join( repo_path = os.path.join(erplibre_root, project["path"])
erplibre_root, project["path"]
)
if not os.path.isdir(repo_path): if not os.path.isdir(repo_path):
_logger.warning(f" Not found: {repo_path}") _logger.warning(f" Not found: {repo_path}")
return "error", False return "error", False
# Handle shallow repos # Handle shallow repos
shallow_file = os.path.join( shallow_file = os.path.join(repo_path, ".git", "shallow")
repo_path, ".git", "shallow"
)
if os.path.exists(shallow_file): if os.path.exists(shallow_file):
if unshallow: if unshallow:
await _try_unshallow( await _try_unshallow(repo_path, project, remote_name)
repo_path, project, remote_name
)
else: else:
# Enable shallow push on bare repo # Enable shallow push on bare repo
repo_name = project["name"] repo_name = project["name"]
if not repo_name.endswith(".git"): if not repo_name.endswith(".git"):
repo_name += ".git" repo_name += ".git"
bare_path = os.path.join( bare_path = os.path.join(git_path, repo_name)
git_path, repo_name
)
if os.path.isdir(bare_path): if os.path.isdir(bare_path):
await _run_git( await _run_git(
"git", "git",
@ -618,19 +568,14 @@ async def _push_single_repo(
"receive.shallowUpdate", "receive.shallowUpdate",
"true", "true",
) )
_logger.info( _logger.info(f" Shallow push for" f" {project['path']}")
f" Shallow push for"
f" {project['path']}"
)
# Handle detached HEAD: checkout manifest branch # Handle detached HEAD: checkout manifest branch
did_checkout = False did_checkout = False
if await _is_detached_head(repo_path): if await _is_detached_head(repo_path):
revision = project.get("revision", "") revision = project.get("revision", "")
if revision: if revision:
branch = await _checkout_manifest_branch( branch = await _checkout_manifest_branch(repo_path, revision)
repo_path, revision
)
_logger.info( _logger.info(
f" Checkout {branch} for" f" Checkout {branch} for"
f" {project['path']}" f" {project['path']}"
@ -663,9 +608,7 @@ async def _push_single_repo(
"push", "push",
remote_name, remote_name,
] ]
_, err, rc = await _run_git( _, err, rc = await _run_git(*cmd, timeout=120)
*cmd, timeout=120
)
if rc != 0: if rc != 0:
_logger.warning( _logger.warning(
f" Push failed for" f" Push failed for"
@ -675,14 +618,10 @@ async def _push_single_repo(
return "error", did_checkout return "error", did_checkout
else: else:
await _update_bare_head(git_path, project) await _update_bare_head(git_path, project)
_logger.info( _logger.info(f" Pushed: {project['path']}")
f" Pushed: {project['path']}"
)
return "pushed", did_checkout return "pushed", did_checkout
except asyncio.TimeoutError: except asyncio.TimeoutError:
_logger.warning( _logger.warning(f" Timeout pushing {project['path']}")
f" Timeout pushing {project['path']}"
)
return "error", did_checkout return "error", did_checkout
@ -744,8 +683,7 @@ def print_clone_commands(git_path, projects, port):
if clone_path == ".": if clone_path == ".":
clone_path = "erplibre" clone_path = "erplibre"
lines.append( lines.append(
f" git clone {base_url}/{repo_name}" f" git clone {base_url}/{repo_name}" f" {clone_path}"
f" {clone_path}"
) )
lines.sort() lines.sort()
for line in lines: for line in lines:

View file

@ -26,17 +26,10 @@ def get_pull_request_repo(
parsed_url = parse(upstream_url) parsed_url = parse(upstream_url)
status, user = gh.user.get() status, user = gh.user.get()
user_name = ( user_name = user["login"] if not organization_name else organization_name
user["login"] if not organization_name else organization_name status, lst_pull = gh.repos[user_name][parsed_url.repo].pulls.get()
)
status, lst_pull = (
gh.repos[user_name][parsed_url.repo].pulls.get()
)
if type(lst_pull) is dict: if type(lst_pull) is dict:
print( print(f"For url {upstream_url}," f" got {lst_pull.get('message')}")
f"For url {upstream_url},"
f" got {lst_pull.get('message')}"
)
return False return False
else: else:
for pull in lst_pull: for pull in lst_pull:
@ -53,26 +46,21 @@ def fork_repo(
parsed_url = parse(upstream_url) parsed_url = parse(upstream_url)
status, user = gh.user.get() status, user = gh.user.get()
user_name = ( user_name = user["login"] if not organization_name else organization_name
user["login"] if not organization_name else organization_name status, forked_repo = gh.repos[user_name][parsed_url.repo].get()
)
status, forked_repo = (
gh.repos[user_name][parsed_url.repo].get()
)
if status == 404: if status == 404:
status, upstream_repo = ( status, upstream_repo = gh.repos[parsed_url.owner][
gh.repos[parsed_url.owner][parsed_url.repo].get() parsed_url.repo
) ].get()
if status == 404: if status == 404:
print("Unable to find repo %s" % upstream_url) print("Unable to find repo %s" % upstream_url)
exit(1) exit(1)
args = {} args = {}
if organization_name: if organization_name:
args["organization"] = organization_name args["organization"] = organization_name
status, forked_repo = ( status, forked_repo = gh.repos[parsed_url.owner][
gh.repos[parsed_url.owner][parsed_url.repo] parsed_url.repo
.forks.post(**args) ].forks.post(**args)
)
if status == 404: if status == 404:
print( print(
f"{Fore.RED}Error{Style.RESET_ALL} when forking" f"{Fore.RED}Error{Style.RESET_ALL} when forking"
@ -82,23 +70,16 @@ def fork_repo(
else: else:
try: try:
print( print(
"Forked %s to %s" "Forked %s to %s" % (upstream_url, forked_repo["html_url"])
% (upstream_url, forked_repo["html_url"])
) )
except Exception as e: except Exception as e:
print(e) print(e)
print(forked_repo) print(forked_repo)
print(upstream_url) print(upstream_url)
elif status == 202: elif status == 202:
print( print("Forked repo %s already exists" % forked_repo["full_name"])
"Forked repo %s already exists"
% forked_repo["full_name"]
)
elif status != 200: elif status != 200:
print( print("Status not supported: %s - %s" % (status, forked_repo))
"Status not supported: %s - %s"
% (status, forked_repo)
)
exit(1) exit(1)
@ -110,9 +91,7 @@ def add_and_fetch_remote(
""" """
try: try:
working_repo = Repo(repo_info.relative_path) working_repo = Repo(repo_info.relative_path)
if repo_info.organization in [ if repo_info.organization in [a.name for a in working_repo.remotes]:
a.name for a in working_repo.remotes
]:
print( print(
f'Remote "{repo_info.organization}" already exist' f'Remote "{repo_info.organization}" already exist'
f" in {repo_info.relative_path}" f" in {repo_info.relative_path}"
@ -122,8 +101,7 @@ def add_and_fetch_remote(
print(f"New repo {repo_info.relative_path}") print(f"New repo {repo_info.relative_path}")
if not root_repo: if not root_repo:
print( print(
"Missing git repository to root for repo" "Missing git repository to root for repo" f" {repo_info.path}"
f" {repo_info.path}"
) )
return return
if branch_name: if branch_name:
@ -149,16 +127,14 @@ def add_and_fetch_remote(
# Add remote # Add remote
upstream_remote = retry( upstream_remote = retry(
wait_exponential_multiplier=1000, stop_max_delay=15000 wait_exponential_multiplier=1000, stop_max_delay=15000
)(working_repo.create_remote)( )(working_repo.create_remote)(repo_info.organization, repo_info.url_https)
repo_info.organization, repo_info.url_https
)
print( print(
'Remote "%s" created for %s' 'Remote "%s" created for %s'
% (repo_info.organization, repo_info.url_https) % (repo_info.organization, repo_info.url_https)
) )
# Fetch the remote # Fetch the remote
retry( retry(wait_exponential_multiplier=1000, stop_max_delay=15000)(
wait_exponential_multiplier=1000, stop_max_delay=15000 upstream_remote.fetch
)(upstream_remote.fetch)() )()
print('Remote "%s" fetched' % repo_info.organization) print('Remote "%s" fetched' % repo_info.organization)

View file

@ -26,7 +26,9 @@ _logger = logging.getLogger(__name__)
PROJECT_NAME = os.path.basename(os.getcwd()) PROJECT_NAME = os.path.basename(os.getcwd())
IDEA_PATH = "./.idea" IDEA_PATH = "./.idea"
DEFAULT_ODOO_BIN = "./odoo/odoo-bin" # Need this to replace static configuration DEFAULT_ODOO_BIN = (
"./odoo/odoo-bin" # Need this to replace static configuration
)
IDEA_MISC = os.path.join(IDEA_PATH, "misc.xml") IDEA_MISC = os.path.join(IDEA_PATH, "misc.xml")
IDEA_WORKSPACE = os.path.join(IDEA_PATH, "workspace.xml") IDEA_WORKSPACE = os.path.join(IDEA_PATH, "workspace.xml")
VCS_WORKSPACE = os.path.join(IDEA_PATH, "vcs.xml") VCS_WORKSPACE = os.path.join(IDEA_PATH, "vcs.xml")

View file

@ -2,7 +2,9 @@
# © 2021-2026 TechnoLibre (http://www.technolibre.ca) # © 2021-2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
installed_modules = env["ir.module.module"].search([('state', '=', 'installed')]) installed_modules = env["ir.module.module"].search(
[("state", "=", "installed")]
)
print("Installed modules:") print("Installed modules:")

View file

@ -10,18 +10,20 @@ print("Running a script in the Odoo shell!")
# ERROR: insert or update on table "discuss_channel_member" violates foreign key constraint "discuss_channel_member_channel_id_fkey" # ERROR: insert or update on table "discuss_channel_member" violates foreign key constraint "discuss_channel_member_channel_id_fkey"
# DÉTAIL : Key (channel_id)=(3) is not present in table "discuss_channel". # DÉTAIL : Key (channel_id)=(3) is not present in table "discuss_channel".
env.cr.execute(""" env.cr.execute(
"""
ALTER TABLE discuss_channel ALTER TABLE discuss_channel
DROP CONSTRAINT discuss_channel_channel_type_not_null; DROP CONSTRAINT discuss_channel_channel_type_not_null;
""") """
)
env.cr.commit() env.cr.commit()
#env.cr.execute("SELECT to_regclass('public.discuss_channel');") # env.cr.execute("SELECT to_regclass('public.discuss_channel');")
#print("table:", env.cr.fetchone()) # print("table:", env.cr.fetchone())
#print( # print(
# "model discuss.channel:", # "model discuss.channel:",
# env["ir.model"].search([("model", "=", "discuss.channel")]), # env["ir.model"].search([("model", "=", "discuss.channel")]),
#) # )
print("End fix migration with update mail postgresql 17 to postgresql 18.") print("End fix migration with update mail postgresql 17 to postgresql 18.")

View file

@ -393,7 +393,9 @@ class TODO:
odoo_version_input = "" odoo_version_input = ""
while odoo_version_input not in install_commands: while odoo_version_input not in install_commands:
if odoo_version_input: if odoo_version_input:
print(f"{t('Error, cannot understand value')} '{odoo_version_input}'") print(
f"{t('Error, cannot understand value')} '{odoo_version_input}'"
)
str_input_dyn_odoo_version = ( str_input_dyn_odoo_version = (
f"💬 {t('Choose a version:')}\n\t" f"💬 {t('Choose a version:')}\n\t"
+ "\n\t".join([a[1] for a in install_commands.values()]) + "\n\t".join([a[1] for a in install_commands.values()])
@ -417,7 +419,9 @@ class TODO:
cmd_intern, shell=True, executable="/bin/bash", check=True cmd_intern, shell=True, executable="/bin/bash", check=True
) )
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"{t('The Bash script failed with return code')} {e.returncode}.") print(
f"{t('The Bash script failed with return code')} {e.returncode}."
)
print("Wait after installation and open projects by terminal.") print("Wait after installation and open projects by terminal.")
print("make open_terminal") print("make open_terminal")
self.restart_script(str(e)) self.restart_script(str(e))
@ -464,9 +468,7 @@ class TODO:
bash_command = instance.get("bash_command") bash_command = instance.get("bash_command")
if bash_command: if bash_command:
print(f"{t('Will execute:')} {bash_command}") print(f"{t('Will execute:')} {bash_command}")
self.execute.exec_command_live( self.execute.exec_command_live(bash_command, source_erplibre=False)
bash_command, source_erplibre=False
)
command = instance.get("Command:") command = instance.get("Command:")
if command: if command:
@ -525,7 +527,9 @@ class TODO:
int_cmd = int(status) int_cmd = int(status)
if 1 < int_cmd <= init_len: if 1 < int_cmd <= init_len:
cmd_no_found = False cmd_no_found = False
status = click.confirm(t("Do you want a new instance?")) status = click.confirm(
t("Do you want a new instance?")
)
instance = choices[int_cmd - 1] instance = choices[int_cmd - 1]
self.execute_from_configuration( self.execute_from_configuration(
instance, instance,
@ -612,15 +616,9 @@ class TODO:
print(t("Command not found !")) print(t("Command not found !"))
def prompt_execute_deploy(self): def prompt_execute_deploy(self):
print( print(f"🤖 {t('Deploy ERPLibre to a local directory!')}")
f"🤖 {t('Deploy ERPLibre to a local directory!')}"
)
choices = [ choices = [
{ {"prompt_description": t("Clone ERPLibre locally (git clone)")},
"prompt_description": t(
"Clone ERPLibre locally (git clone)"
)
},
] ]
help_info = self.fill_help_info(choices) help_info = self.fill_help_info(choices)
@ -637,16 +635,12 @@ class TODO:
def _deploy_clone_erplibre(self): def _deploy_clone_erplibre(self):
default_path = os.path.expanduser("~/erplibre") default_path = os.path.expanduser("~/erplibre")
target_path = ( target_path = (
input( input(t("Target directory path (default: ~/erplibre): ")).strip()
t("Target directory path (default: ~/erplibre): ")
).strip()
or default_path or default_path
) )
target_path = os.path.expanduser(target_path) target_path = os.path.expanduser(target_path)
if os.path.exists(target_path): if os.path.exists(target_path):
print( print(f"{t('Directory already exists: ')}{target_path}")
f"{t('Directory already exists: ')}{target_path}"
)
return return
print(t("Cloning ERPLibre...")) print(t("Cloning ERPLibre..."))
cmd = ( cmd = (
@ -656,13 +650,8 @@ class TODO:
) )
print(f"{t('Will execute:')} {cmd}") print(f"{t('Will execute:')} {cmd}")
try: try:
self.execute.exec_command_live( self.execute.exec_command_live(cmd, source_erplibre=False)
cmd, source_erplibre=False print(f"{t('ERPLibre cloned successfully to: ')}" f"{target_path}")
)
print(
f"{t('ERPLibre cloned successfully to: ')}"
f"{target_path}"
)
except Exception as e: except Exception as e:
print(f"{t('Error cloning ERPLibre: ')}{e}") print(f"{t('Error cloning ERPLibre: ')}{e}")
@ -762,18 +751,19 @@ class TODO:
def _git_add_remote(self): def _git_add_remote(self):
remote_name = ( remote_name = (
input(t("Remote name (default: localhost): ")).strip() or "localhost" input(t("Remote name (default: localhost): ")).strip()
or "localhost"
) )
remote_url = input(t("Repository address (e.g.: git://192.168.1.100/my-repo.git): ")).strip() remote_url = input(
t("Repository address (e.g.: git://192.168.1.100/my-repo.git): ")
).strip()
if not remote_url: if not remote_url:
print(t("Repository address is required!")) print(t("Repository address is required!"))
return return
cmd = f"git remote add {remote_name} {remote_url}" cmd = f"git remote add {remote_name} {remote_url}"
print(f"{t('Will execute:')} {cmd}") print(f"{t('Will execute:')} {cmd}")
try: try:
self.execute.exec_command_live( self.execute.exec_command_live(cmd, source_erplibre=False)
cmd, source_erplibre=False
)
print(t("Remote added successfully!")) print(t("Remote added successfully!"))
except Exception as e: except Exception as e:
print(f"{t('Error adding remote: ')}{e}") print(f"{t('Error adding remote: ')}{e}")
@ -781,8 +771,16 @@ class TODO:
def prompt_execute_git_local_server(self): def prompt_execute_git_local_server(self):
print(f"🤖 {t('Manage local git repository server!')}") print(f"🤖 {t('Manage local git repository server!')}")
choices = [ choices = [
{"prompt_description": t("Deploy a local git server (~/.git-server)")}, {
{"prompt_description": t("Deploy a production git server (/srv/git, root required)")}, "prompt_description": t(
"Deploy a local git server (~/.git-server)"
)
},
{
"prompt_description": t(
"Deploy a production git server (/srv/git, root required)"
)
},
] ]
help_info = self.fill_help_info(choices) help_info = self.fill_help_info(choices)
@ -806,7 +804,11 @@ class TODO:
) )
print(f"🤖 {mode}") print(f"🤖 {mode}")
choices = [ choices = [
{"prompt_description": t("Run all (init + remote + push + serve)")}, {
"prompt_description": t(
"Run all (init + remote + push + serve)"
)
},
{"prompt_description": t("Init - Create bare repos")}, {"prompt_description": t("Init - Create bare repos")},
{"prompt_description": t("Remote - Add local remotes")}, {"prompt_description": t("Remote - Add local remotes")},
{"prompt_description": t("Push - Push to local server")}, {"prompt_description": t("Push - Push to local server")},
@ -863,8 +865,16 @@ class TODO:
print(f"🤖 {t('AI assistant tools for development!')}") print(f"🤖 {t('AI assistant tools for development!')}")
choices = [ choices = [
{"prompt_description": t("Configure Claude Code configurations")}, {"prompt_description": t("Configure Claude Code configurations")},
{"prompt_description": t("Add an automation with Claude in todo.py")}, {
{"prompt_description": t("RTK - CLI proxy to reduce LLM token consumption")}, "prompt_description": t(
"Add an automation with Claude in todo.py"
)
},
{
"prompt_description": t(
"RTK - CLI proxy to reduce LLM token consumption"
)
},
] ]
help_info = self.fill_help_info(choices) help_info = self.fill_help_info(choices)
@ -891,11 +901,7 @@ class TODO:
"Todo Add Command - Add a command to todo.py menu" "Todo Add Command - Add a command to todo.py menu"
) )
}, },
{ {"prompt_description": t("Show installed custom commands")},
"prompt_description": t(
"Show installed custom commands"
)
},
] ]
help_info = self.fill_help_info(choices) help_info = self.fill_help_info(choices)
@ -926,9 +932,7 @@ class TODO:
print(t("No custom commands found in ~/.claude/commands/")) print(t("No custom commands found in ~/.claude/commands/"))
return return
files = sorted( files = sorted(
f f for f in os.listdir(commands_dir) if f.endswith(".md")
for f in os.listdir(commands_dir)
if f.endswith(".md")
) )
if not files: if not files:
print(t("No custom commands found in ~/.claude/commands/")) print(t("No custom commands found in ~/.claude/commands/"))
@ -944,10 +948,7 @@ class TODO:
name = f[:-3] # remove .md name = f[:-3] # remove .md
print(f" /{name:<30} {date_str}") print(f" /{name:<30} {date_str}")
print("-" * 50) print("-" * 50)
print( print(f"{t('Total:')}" f" {len(files)}")
f"{t('Total:')}"
f" {len(files)}"
)
def _setup_claude_command( def _setup_claude_command(
self, command_name, template_filename, personalize=False self, command_name, template_filename, personalize=False
@ -996,14 +997,10 @@ class TODO:
print(f"{t('Error creating file: ')}{e}") print(f"{t('Error creating file: ')}{e}")
def _claude_add_automation(self): def _claude_add_automation(self):
description = input( description = input(t("Description of the command to add: ")).strip()
t("Description of the command to add: ")
).strip()
if not description: if not description:
return return
command = input( command = input(t("Bash command to execute: ")).strip()
t("Bash command to execute: ")
).strip()
if not command: if not command:
return return
section = ( section = (
@ -1013,9 +1010,7 @@ class TODO:
or "git" or "git"
) )
section_key = f"{section}_from_makefile" section_key = f"{section}_from_makefile"
config_path = os.path.join( config_path = os.path.join(os.path.dirname(__file__), "todo.json")
os.path.dirname(__file__), "todo.json"
)
try: try:
with open(config_path) as f: with open(config_path) as f:
config = json.load(f) config = json.load(f)
@ -1086,7 +1081,11 @@ class TODO:
def prompt_execute_database(self): def prompt_execute_database(self):
print(f"🤖 {t('Make changes to databases!')}") print(f"🤖 {t('Make changes to databases!')}")
choices = [ choices = [
{"prompt_description": t("Download database to create backup (.zip)")}, {
"prompt_description": t(
"Download database to create backup (.zip)"
)
},
{"prompt_description": t("Restore from backup (.zip)")}, {"prompt_description": t("Restore from backup (.zip)")},
{"prompt_description": t("Create backup (.zip)")}, {"prompt_description": t("Create backup (.zip)")},
] ]
@ -1134,7 +1133,9 @@ class TODO:
print(t("Git daemon process killed.")) print(t("Git daemon process killed."))
def prompt_execute_rtk(self): def prompt_execute_rtk(self):
print(f"🤖 {t('Manage RTK (Rust Token Killer) for token optimization!')}") print(
f"🤖 {t('Manage RTK (Rust Token Killer) for token optimization!')}"
)
choices = [ choices = [
{"prompt_description": t("Install RTK")}, {"prompt_description": t("Install RTK")},
{"prompt_description": t("Check RTK version")}, {"prompt_description": t("Check RTK version")},
@ -1170,7 +1171,11 @@ class TODO:
choices = [ choices = [
{"prompt_description": t("curl - Automatic install script")}, {"prompt_description": t("curl - Automatic install script")},
{"prompt_description": t("brew - Homebrew (macOS/Linux)")}, {"prompt_description": t("brew - Homebrew (macOS/Linux)")},
{"prompt_description": t("cargo - Build from source (Rust required)")}, {
"prompt_description": t(
"cargo - Build from source (Rust required)"
)
},
] ]
help_info = self.fill_help_info(choices) help_info = self.fill_help_info(choices)
status = click.prompt(help_info) status = click.prompt(help_info)
@ -1323,7 +1328,11 @@ class TODO:
def prompt_execute_security(self): def prompt_execute_security(self):
print(f"🤖 {t('Dependency security audit!')}") print(f"🤖 {t('Dependency security audit!')}")
choices = [ choices = [
{"prompt_description": t("pip-audit - Check vulnerabilities on Python environments")}, {
"prompt_description": t(
"pip-audit - Check vulnerabilities on Python environments"
)
},
] ]
help_info = self.fill_help_info(choices) help_info = self.fill_help_info(choices)
@ -1368,12 +1377,16 @@ class TODO:
return return
# Database name # Database name
db_name = input(t("Temporary database name (default: test_todo_tmp): ")).strip() db_name = input(
t("Temporary database name (default: test_todo_tmp): ")
).strip()
if not db_name: if not db_name:
db_name = "test_todo_tmp" db_name = "test_todo_tmp"
# Extra modules # Extra modules
extra_modules = input(t("Extra modules to install (comma-separated, empty for none): ")).strip() extra_modules = input(
t("Extra modules to install (comma-separated, empty for none): ")
).strip()
# Log level # Log level
log_level = input(t("Log level (default: test): ")).strip() log_level = input(t("Log level (default: test): ")).strip()
@ -1395,9 +1408,7 @@ class TODO:
) )
# Step 2: Install modules # Step 2: Install modules
print( print(f"\n--- {t('Installing modules')}: {modules_to_install} ---")
f"\n--- {t('Installing modules')}: {modules_to_install} ---"
)
cmd_install = ( cmd_install = (
f"./script/addons/install_addons.sh" f"./script/addons/install_addons.sh"
f" {db_name} {modules_to_install}" f" {db_name} {modules_to_install}"
@ -1433,12 +1444,16 @@ class TODO:
# Step 4: Cleanup # Step 4: Cleanup
lang = get_lang() lang = get_lang()
keep_input = input(t("Keep the temporary database? (y/N): ")).strip().lower() keep_input = (
input(t("Keep the temporary database? (y/N): ")).strip().lower()
)
keep = keep_input in (("o", "oui") if lang == "fr" else ("y", "yes")) keep = keep_input in (("o", "oui") if lang == "fr" else ("y", "yes"))
if keep: if keep:
print(f"{t('Database kept')}: {db_name}") print(f"{t('Database kept')}: {db_name}")
else: else:
print(f"\n--- {t('Cleaning up temporary database')} '{db_name}' ---") print(
f"\n--- {t('Cleaning up temporary database')} '{db_name}' ---"
)
cmd_drop = f"./odoo_bin.sh db --drop --database {db_name}" cmd_drop = f"./odoo_bin.sh db --drop --database {db_name}"
self.execute.exec_command_live( self.execute.exec_command_live(
cmd_drop, cmd_drop,
@ -1460,7 +1475,9 @@ class TODO:
if status_code == 0: if status_code == 0:
print(f"\n{t('All unit tests passed')}") print(f"\n{t('All unit tests passed')}")
else: else:
print(f"\n{t('Some unit tests failed, exit code')}: {status_code}") print(
f"\n{t('Some unit tests failed, exit code')}: {status_code}"
)
def execute_pip_audit(self): def execute_pip_audit(self):
versions, installed_versions, odoo_installed_version = ( versions, installed_versions, odoo_installed_version = (
@ -1495,7 +1512,11 @@ class TODO:
} }
if not environments: if not environments:
print(t("No installed environment found. Install an Odoo version first.")) print(
t(
"No installed environment found. Install an Odoo version first."
)
)
return return
# Show selection menu # Show selection menu
@ -1508,7 +1529,9 @@ class TODO:
env_input = "" env_input = ""
while env_input not in environments and env_input != "0": while env_input not in environments and env_input != "0":
if env_input: if env_input:
print(f"{t('Error, cannot understand value')}" f" '{env_input}'") print(
f"{t('Error, cannot understand value')}" f" '{env_input}'"
)
env_input = input(str_input).strip() env_input = input(str_input).strip()
if env_input == "0": if env_input == "0":