From 46747199c9352e17c4639f74ff80296e5ddb8731 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Fri, 13 Mar 2026 15:23:16 +0700 Subject: [PATCH 1/5] feat: add generate-config command with odoo-minimal and oca-contributor profiles New `tlc generate-config ` command that creates config.toml from predefined profiles. oca-contributor dynamically fetches OCA repo list from trobz/odoo-addons-repos. Also removes trobz_local/assets/ in favor of root assets/ directory and updates show_config_instructions to point to the new command. Co-Authored-By: Claude Opus 4.6 (1M context) --- assets/oca_contributor.toml | 198 ------------------------------- trobz_local/assets/odoo_dev.toml | 34 ------ trobz_local/main.py | 48 ++++++++ trobz_local/utils.py | 7 +- 4 files changed, 50 insertions(+), 237 deletions(-) delete mode 100644 trobz_local/assets/odoo_dev.toml diff --git a/assets/oca_contributor.toml b/assets/oca_contributor.toml index 6b840ed..6d9031e 100644 --- a/assets/oca_contributor.toml +++ b/assets/oca_contributor.toml @@ -1,8 +1,3 @@ -# Exhaustive config to contribute to OCA -# Place this file at ~/code/config.toml (or set TLC_CODE_DIR) - -versions = ["18.0", "19.0"] - [tools] uv = [ "copier", @@ -12,196 +7,3 @@ uv = [ "odoo-addons-path" ] system_packages = ["postgresql"] - -[repos] -odoo = ["odoo"] -oca = [ - # addons repositories - "account-analytic", - "account-budgeting", - "account-closing", - "account-consolidation", - "account-financial-reporting", - "account-financial-tools", - "account-fiscal-rule", - "account-invoice-reporting", - "account-invoicing", - "account-payment", - "account-reconcile", - "agreement", - "ai", - "apps-store", - "automation", - "bank-payment", - "bank-payment-alternative", - "bank-statement-import", - "brand", - "business-requirement", - "calendar", - "cim", - "commission", - "community-data-files", - "connector", - "connector-accountedge", - "connector-cmis", - "connector-ecommerce", - "connector-infor", - "connector-interfaces", - "connector-jira", - "connector-lengow", - "connector-lims", - "connector-magento", - "connector-odoo2odoo", - "connector-prestashop", - "connector-redmine", - "connector-sage", - "connector-salesforce", - "connector-spscommerce", - "connector-telephony", - "connector-woocommerce", - "contract", - "cooperative", - "credit-control", - "crm", - "crowdfunding", - "currency", - "data-protection", - "ddmrp", - "delivery-carrier", - "department", - "dms", - "donation", - "dotnet", - "e-commerce", - "e-learning", - "edi", - "edi-ediversa", - "edi-framework", - "edi-voxel", - "event", - "field-service", - "fleet", - "geospatial", - "helpdesk", - "hr", - "hr-attendance", - "hr-expense", - "hr-holidays", - "infrastructure", - "interface-github", - "intrastat-extrastat", - "iot", - "knowledge", - "l10n-brazil", - "l10n-france", - "l10n-usa", - "mail", - "maintenance", - "management-system", - "manufacture", - "manufacture-reporting", - "margin-analysis", - "mass-mailing", - "mis-builder", - "mis-builder-contrib", - "multi-company", - "odoo-pim", - "operating-unit", - "partner-contact", - "payroll", - "pms", - "pos", - "product-attribute", - "product-configurator", - "product-kitting", - "product-pack", - "product-variant", - "program", - "project", - "project-agile", - "project-reporting", - "purchase-reporting", - "purchase-workflow", - "pwa-builder", - "queue", - "repair", - "report-print-send", - "reporting-engine", - "resource", - "rest-api", - "rest-framework", - "rma", - "role-policy", - "sale-blanket", - "sale-channel", - "sale-financial", - "sale-prebook", - "sale-promotion", - "sale-reporting", - "sale-workflow", - "search-engine", - "server-auth", - "server-backend", - "server-brand", - "server-env", - "server-tools", - "server-ux", - "shift-planning", - "shopfloor-app", - "sign", - "social", - "spreadsheet", - "stock-logistics-availability", - "stock-logistics-barcode", - "stock-logistics-interfaces", - "stock-logistics-orderpoint", - "stock-logistics-putaway", - "stock-logistics-release-channel", - "stock-logistics-reporting", - "stock-logistics-request", - "stock-logistics-reservation", - "stock-logistics-shopfloor", - "stock-logistics-tracking", - "stock-logistics-transport", - "stock-logistics-warehouse", - "stock-logistics-workflow", - "stock-weighing", - "storage", - "survey", - "timesheet", - "vertical-abbey", - "vertical-agriculture", - "vertical-association", - "vertical-community", - "vertical-construction", - "vertical-cooperative-supermarket", - "vertical-edition", - "vertical-education", - "vertical-hotel", - "vertical-isp", - "vertical-medical", - "vertical-ngo", - "vertical-realestate", - "vertical-rental", - "vertical-travel", - "wallet", - "web", - "web-api", - "web-api-contrib", - "webhook", - "webkit-tools", - "website", - "website-cms", - "website-themes", - "wms", - # exceptions, new repositories - ["oca-custom", ["18.0"]], - ["tier-validation", ["19.0"]], - # tooling - ["oca-ci", ["master"]], - ["oca-github-bot", ["master"]], - ["oca-port", ["main"]], - ["odoo-module-migrator", ["master"]], -] -camptocamp = ["odoo-cloud-platform"] -forgeflow = ["stock-rma"] diff --git a/trobz_local/assets/odoo_dev.toml b/trobz_local/assets/odoo_dev.toml deleted file mode 100644 index a31eb98..0000000 --- a/trobz_local/assets/odoo_dev.toml +++ /dev/null @@ -1,34 +0,0 @@ -versions = ["14.0", "15.0", "16.0", "17.0", "18.0"] -create_launcher = true - -[tools] -uv = [ - "odoo-venv", - "odoo-addons-path", - "pre-commit", -] - -npm = [ - "prettier", -] - -[[tools.script]] -name = "uv" -url = "https://astral.sh/uv/install.sh" - -[[tools.script]] -name = "nvm" -url = "https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh" - -system_packages = [] - -[repos] -odoo = [ - "odoo", - "enterprise", -] - -oca = [ - "server-tools", - "server-ux", -] diff --git a/trobz_local/main.py b/trobz_local/main.py index bcd2c5b..28b328f 100644 --- a/trobz_local/main.py +++ b/trobz_local/main.py @@ -1,4 +1,6 @@ import subprocess +import urllib.request +from enum import Enum from pathlib import Path from typing import Annotated @@ -603,3 +605,49 @@ def doctor(): if has_fail: raise typer.Exit(code=1) + + +ALL_REPOS_URL = "https://raw.githubusercontent.com/trobz/odoo-addons-repos/main/all_repos_all_versions.toml" + +ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets" + + +class ConfigProfile(str, Enum): + odoo_minimal = "odoo-minimal" + oca_contributor = "oca-contributor" + + +@app.command() +def generate_config( + profile: Annotated[ConfigProfile, typer.Argument(help="Configuration profile to generate.")], +): + """Generate a config.toml file from a predefined profile.""" + code_root = get_code_root() + config_path = code_root / "config.toml" + + if config_path.exists(): + typer.secho(f"Config file already exists: {config_path}", fg=typer.colors.YELLOW) + if not typer.confirm("Overwrite?", default=False): + raise typer.Abort() + + if profile == ConfigProfile.odoo_minimal: + content = (ASSETS_DIR / "odoo_minimal.toml").read_text() + else: + content = _build_oca_contributor_config() + + code_root.mkdir(parents=True, exist_ok=True) + config_path.write_text(content) + typer.secho(f"Config written to {config_path}", fg=typer.colors.GREEN) + + +def _build_oca_contributor_config() -> str: + typer.echo(f"Fetching OCA repo list from {ALL_REPOS_URL} ...") + try: + with urllib.request.urlopen(ALL_REPOS_URL, timeout=30) as resp: # noqa: S310 + remote_content = resp.read().decode() + except Exception as e: + typer.secho(f"Failed to fetch repo list: {e}", fg=typer.colors.RED) + raise typer.Exit(code=1) from e + + local_content = (ASSETS_DIR / "oca_contributor.toml").read_text() + return local_content + "\n" + remote_content diff --git a/trobz_local/utils.py b/trobz_local/utils.py index 4ef6bec..b332070 100644 --- a/trobz_local/utils.py +++ b/trobz_local/utils.py @@ -2,7 +2,6 @@ import platform import re import shutil -from importlib.resources import files from pathlib import Path import git @@ -233,11 +232,9 @@ def get_uv_path(): def show_config_instructions(): - content = files("trobz_local").joinpath("assets/odoo_dev.toml").read_text() typer.secho("Config file not found.", fg=typer.colors.YELLOW) - code_root = get_code_root() - typer.echo(f"Please create {code_root}/config.toml with content like this:") - typer.echo(content) + typer.echo("Generate one with: tlc generate-config ") + typer.echo("Available profiles: odoo-minimal, oca-contributor") def get_config(): From 93c34e54e5717ff5af94f27944fb91a1ff937a68 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Fri, 13 Mar 2026 15:32:12 +0700 Subject: [PATCH 2/5] fix: use importlib.resources for asset files so they work when installed as a package Path(__file__) relative traversal breaks when installed via uv tool install because the root assets/ dir is not included in the package. Co-Authored-By: Claude Opus 4.6 (1M context) --- trobz_local/assets/oca_contributor.toml | 9 +++++++++ trobz_local/assets/odoo_minimal.toml | 10 ++++++++++ trobz_local/main.py | 7 ++++--- 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 trobz_local/assets/oca_contributor.toml create mode 100644 trobz_local/assets/odoo_minimal.toml diff --git a/trobz_local/assets/oca_contributor.toml b/trobz_local/assets/oca_contributor.toml new file mode 100644 index 0000000..6d9031e --- /dev/null +++ b/trobz_local/assets/oca_contributor.toml @@ -0,0 +1,9 @@ +[tools] +uv = [ + "copier", + "oca-port", + "odooly", + "odoo-venv", + "odoo-addons-path" +] +system_packages = ["postgresql"] diff --git a/trobz_local/assets/odoo_minimal.toml b/trobz_local/assets/odoo_minimal.toml new file mode 100644 index 0000000..f20f74b --- /dev/null +++ b/trobz_local/assets/odoo_minimal.toml @@ -0,0 +1,10 @@ +# Minimal config to set up latest Odoo for development +# Place this file at ~/code/config.toml (or set TLC_CODE_DIR) + +versions = ["18.0"] + +[tools] +uv = ["odoo-venv", "odoo-addons-path"] + +[repos] +odoo = ["odoo"] diff --git a/trobz_local/main.py b/trobz_local/main.py index 28b328f..65ac3eb 100644 --- a/trobz_local/main.py +++ b/trobz_local/main.py @@ -1,6 +1,7 @@ import subprocess import urllib.request from enum import Enum +from importlib.resources import files from pathlib import Path from typing import Annotated @@ -609,7 +610,7 @@ def doctor(): ALL_REPOS_URL = "https://raw.githubusercontent.com/trobz/odoo-addons-repos/main/all_repos_all_versions.toml" -ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets" +_ASSETS = files("trobz_local").joinpath("assets") class ConfigProfile(str, Enum): @@ -631,7 +632,7 @@ def generate_config( raise typer.Abort() if profile == ConfigProfile.odoo_minimal: - content = (ASSETS_DIR / "odoo_minimal.toml").read_text() + content = (_ASSETS / "odoo_minimal.toml").read_text() else: content = _build_oca_contributor_config() @@ -649,5 +650,5 @@ def _build_oca_contributor_config() -> str: typer.secho(f"Failed to fetch repo list: {e}", fg=typer.colors.RED) raise typer.Exit(code=1) from e - local_content = (ASSETS_DIR / "oca_contributor.toml").read_text() + local_content = (_ASSETS / "oca_contributor.toml").read_text() return local_content + "\n" + remote_content From f48c434ca5c3084106d7cce5b1f3debd78b4b93c Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Fri, 13 Mar 2026 15:34:56 +0700 Subject: [PATCH 3/5] fix: honor --yes flag in generate-config to skip overwrite prompt Co-Authored-By: Claude Opus 4.6 (1M context) --- trobz_local/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/trobz_local/main.py b/trobz_local/main.py index 65ac3eb..afc23d6 100644 --- a/trobz_local/main.py +++ b/trobz_local/main.py @@ -620,13 +620,14 @@ class ConfigProfile(str, Enum): @app.command() def generate_config( + ctx: typer.Context, profile: Annotated[ConfigProfile, typer.Argument(help="Configuration profile to generate.")], ): """Generate a config.toml file from a predefined profile.""" code_root = get_code_root() config_path = code_root / "config.toml" - if config_path.exists(): + if config_path.exists() and ctx.obj.get("newcomer", True): typer.secho(f"Config file already exists: {config_path}", fg=typer.colors.YELLOW) if not typer.confirm("Overwrite?", default=False): raise typer.Abort() From a1a332093840c96fa2be7cb4f1c8abe3fdade2ca Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Fri, 13 Mar 2026 15:37:21 +0700 Subject: [PATCH 4/5] refactor: move generate-config before init in command registration order Co-Authored-By: Claude Opus 4.6 (1M context) --- trobz_local/main.py | 94 ++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/trobz_local/main.py b/trobz_local/main.py index afc23d6..3cb9d28 100644 --- a/trobz_local/main.py +++ b/trobz_local/main.py @@ -69,6 +69,53 @@ def main( _run_init(ctx) +ALL_REPOS_URL = "https://raw.githubusercontent.com/trobz/odoo-addons-repos/main/all_repos_all_versions.toml" + +_ASSETS = files("trobz_local").joinpath("assets") + + +class ConfigProfile(str, Enum): + odoo_minimal = "odoo-minimal" + oca_contributor = "oca-contributor" + + +@app.command() +def generate_config( + ctx: typer.Context, + profile: Annotated[ConfigProfile, typer.Argument(help="Configuration profile to generate.")], +): + """Generate a config.toml file from a predefined profile.""" + code_root = get_code_root() + config_path = code_root / "config.toml" + + if config_path.exists() and ctx.obj.get("newcomer", True): + typer.secho(f"Config file already exists: {config_path}", fg=typer.colors.YELLOW) + if not typer.confirm("Overwrite?", default=False): + raise typer.Abort() + + if profile == ConfigProfile.odoo_minimal: + content = (_ASSETS / "odoo_minimal.toml").read_text() + else: + content = _build_oca_contributor_config() + + code_root.mkdir(parents=True, exist_ok=True) + config_path.write_text(content) + typer.secho(f"Config written to {config_path}", fg=typer.colors.GREEN) + + +def _build_oca_contributor_config() -> str: + typer.echo(f"Fetching OCA repo list from {ALL_REPOS_URL} ...") + try: + with urllib.request.urlopen(ALL_REPOS_URL, timeout=30) as resp: # noqa: S310 + remote_content = resp.read().decode() + except Exception as e: + typer.secho(f"Failed to fetch repo list: {e}", fg=typer.colors.RED) + raise typer.Exit(code=1) from e + + local_content = (_ASSETS / "oca_contributor.toml").read_text() + return local_content + "\n" + remote_content + + @app.command() def init(ctx: typer.Context): _run_init(ctx) @@ -606,50 +653,3 @@ def doctor(): if has_fail: raise typer.Exit(code=1) - - -ALL_REPOS_URL = "https://raw.githubusercontent.com/trobz/odoo-addons-repos/main/all_repos_all_versions.toml" - -_ASSETS = files("trobz_local").joinpath("assets") - - -class ConfigProfile(str, Enum): - odoo_minimal = "odoo-minimal" - oca_contributor = "oca-contributor" - - -@app.command() -def generate_config( - ctx: typer.Context, - profile: Annotated[ConfigProfile, typer.Argument(help="Configuration profile to generate.")], -): - """Generate a config.toml file from a predefined profile.""" - code_root = get_code_root() - config_path = code_root / "config.toml" - - if config_path.exists() and ctx.obj.get("newcomer", True): - typer.secho(f"Config file already exists: {config_path}", fg=typer.colors.YELLOW) - if not typer.confirm("Overwrite?", default=False): - raise typer.Abort() - - if profile == ConfigProfile.odoo_minimal: - content = (_ASSETS / "odoo_minimal.toml").read_text() - else: - content = _build_oca_contributor_config() - - code_root.mkdir(parents=True, exist_ok=True) - config_path.write_text(content) - typer.secho(f"Config written to {config_path}", fg=typer.colors.GREEN) - - -def _build_oca_contributor_config() -> str: - typer.echo(f"Fetching OCA repo list from {ALL_REPOS_URL} ...") - try: - with urllib.request.urlopen(ALL_REPOS_URL, timeout=30) as resp: # noqa: S310 - remote_content = resp.read().decode() - except Exception as e: - typer.secho(f"Failed to fetch repo list: {e}", fg=typer.colors.RED) - raise typer.Exit(code=1) from e - - local_content = (_ASSETS / "oca_contributor.toml").read_text() - return local_content + "\n" + remote_content From eef0fdfc0e318dd92317c133c9bba093894d79d5 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Fri, 13 Mar 2026 15:43:07 +0700 Subject: [PATCH 5/5] fix: prepend remote content before local assets to fix TOML table scoping The remote content (versions, repos) must come before the local oca_contributor.toml which opens a [tools] table, otherwise versions ends up scoped under [tools] instead of at the top level. Co-Authored-By: Claude Opus 4.6 (1M context) --- trobz_local/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trobz_local/main.py b/trobz_local/main.py index 3cb9d28..1b2d6d6 100644 --- a/trobz_local/main.py +++ b/trobz_local/main.py @@ -113,7 +113,7 @@ def _build_oca_contributor_config() -> str: raise typer.Exit(code=1) from e local_content = (_ASSETS / "oca_contributor.toml").read_text() - return local_content + "\n" + remote_content + return remote_content + "\n" + local_content @app.command()