From 6f2d1db42aed5518318392b3388a553a1de281d0 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Tue, 24 Feb 2026 12:32:23 +0700 Subject: [PATCH 1/7] chore: replace pnpm references with npm Co-Authored-By: Claude Sonnet 4.6 --- README.md | 2 +- docs/codebase-summary.md | 2 +- docs/project-overview-pdr.md | 8 ++++---- docs/system-architecture.md | 8 ++++---- tests/test_install_tools.py | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e830d08..472ac6d 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ versions = ["16.0", "17.0", "18.0"] [tools] uv = ["odoo-venv", "odoo-addons-path", "pre-commit"] npm = ["prettier", "eslint"] -system_packages = ["git", "postgresql", "pnpm"] +system_packages = ["git", "postgresql"] [[tools.script]] url = "https://astral.sh/uv/install.sh" diff --git a/docs/codebase-summary.md b/docs/codebase-summary.md index 3955101..c9707f0 100644 --- a/docs/codebase-summary.md +++ b/docs/codebase-summary.md @@ -52,7 +52,7 @@ Technical overview of the `trobz_local` codebase structure, implementation patte 1. **PostgreSQL Repository Setup**: Idempotent APT repo configuration with GPG verification (Debian/Ubuntu) 2. **Scripts**: Download via wget/curl, execute with /bin/sh 3. **System Packages**: OS-aware (apt-get, pacman, brew) with platform defaults -4. **NPM Packages**: Global via pnpm install -g +4. **NPM Packages**: Global via npm install -g 5. **UV Tools**: Global via uv tool install **Key Functions**: diff --git a/docs/project-overview-pdr.md b/docs/project-overview-pdr.md index c81387e..64b636c 100644 --- a/docs/project-overview-pdr.md +++ b/docs/project-overview-pdr.md @@ -67,7 +67,7 @@ Five-stage installation pipeline: 1. **PostgreSQL Repository** (Debian/Ubuntu only): Setup PGDG APT repository with GPG verification (idempotent) 2. **Shell Scripts**: Download and execute scripts (e.g., uv installer) 3. **System Packages**: OS-aware installation via apt/pacman/brew (runs after PostgreSQL repo setup on Debian/Ubuntu) -4. **NPM Packages**: Global packages via pnpm +4. **NPM Packages**: Global packages via npm 5. **UV Tools**: Python tools via uv tool install ### 4. Virtual Environment Management (`create-venvs`) @@ -138,7 +138,7 @@ versions = ["16.0", "17.0", "18.0"] [tools] uv = ["odoo-venv", "odoo-addons-path", "pre-commit"] npm = ["prettier", "eslint"] -system_packages = ["git", "postgresql", "pnpm"] +system_packages = ["git", "postgresql"] [[tools.script]] url = "https://astral.sh/uv/install.sh" @@ -166,7 +166,7 @@ UV tools to install globally via `uv tool install` - **Examples**: `["odoo-venv", "pre-commit", "black[d]>=24.0"]` #### `tools.npm` (Optional) -NPM packages to install globally via pnpm +NPM packages to install globally via npm - **Type**: List of strings - **Pattern**: `^(@[a-z0-9-~][a-z0-9-._~]*/)?[a-z0-9-~][a-z0-9-._~]*$` - **Supports**: Scoped (@org/package) and unscoped packages @@ -310,7 +310,7 @@ Install tools from five sources in order: PostgreSQL repo, scripts, system packa 1. **PostgreSQL Repository** (Debian/Ubuntu): Setup PGDG APT repository with GPG verification 2. **Scripts**: Download and execute via `wget` or `curl`, then `sh` 3. **System Packages**: OS-aware installation (apt/pacman/brew) - runs after PostgreSQL repo on Debian/Ubuntu -4. **NPM Packages**: Global installation via `pnpm install -g` +4. **NPM Packages**: Global installation via `npm install -g` 5. **UV Tools**: Global installation via `uv tool install` **Behavior**: diff --git a/docs/system-architecture.md b/docs/system-architecture.md index a524f61..2713bcc 100644 --- a/docs/system-architecture.md +++ b/docs/system-architecture.md @@ -186,8 +186,8 @@ install-tools command │ └─ Return success/failure boolean │ │ 4. install_npm_packages() - │ ├─ Check if pnpm exists - │ └─ Parallel: run_tasks() with pnpm install -g + │ ├─ Check if npm exists + │ └─ Parallel: run_tasks() with npm install -g │ │ 5. install_uv_tools() │ └─ Parallel: run_tasks() with uv tool install @@ -354,9 +354,9 @@ install_tools request │ └─ Execute with sudo │ ├─ NPM Packages - │ ├─ Check: which pnpm + │ ├─ Check: which npm │ ├─ For each package: - │ │ └─ pnpm install -g {package} + │ │ └─ npm install -g {package} │ └─ Parallel via run_tasks() │ └─ UV Tools diff --git a/tests/test_install_tools.py b/tests/test_install_tools.py index 93367de..07c2029 100644 --- a/tests/test_install_tools.py +++ b/tests/test_install_tools.py @@ -171,7 +171,7 @@ def test_install_system_packages_arch(mock_subprocess, mock_get_config, mock_whi "uv": [], "npm": [], "script": [], - "system_packages": ["pnpm"], + "system_packages": ["git"], } } @@ -182,7 +182,7 @@ def test_install_system_packages_arch(mock_subprocess, mock_get_config, mock_whi mock_subprocess.assert_called_once() call_args = mock_subprocess.call_args[0][0] assert "pacman" in call_args - assert "pnpm" in call_args + assert "git" in call_args @patch("trobz_local.installers.get_os_info") From 491f7d2bd3b5a47fb63c1b7f24fec27b2573f62f Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Tue, 24 Feb 2026 12:36:48 +0700 Subject: [PATCH 2/7] fix: prevent bootstrap.sh from running as root Co-Authored-By: Claude Sonnet 4.6 --- bootstrap.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bootstrap.sh b/bootstrap.sh index a45acf5..05a9ff7 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -9,6 +9,13 @@ export PATH="$HOME/.local/bin:$PATH" echo "=== Bootstrap trobz_local ===" +# Check not running as root +if [ "$(id -u)" -eq 0 ]; then + echo "Error: Do not run this script as root." + echo "Please run as a regular user with sudo access." + exit 1 +fi + # Check if user can sudo check_sudo() { if ! sudo -v &>/dev/null; then From 2f4648c103184f702e9c8b5d8bad67f41b887b5a Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Tue, 24 Feb 2026 12:38:49 +0700 Subject: [PATCH 3/7] docs: remove uv script entry from README example config uv is installed via bootstrap.sh, not via tools.script Co-Authored-By: Claude Sonnet 4.6 --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 472ac6d..157bea9 100644 --- a/README.md +++ b/README.md @@ -89,10 +89,6 @@ uv = ["odoo-venv", "odoo-addons-path", "pre-commit"] npm = ["prettier", "eslint"] system_packages = ["git", "postgresql"] -[[tools.script]] -url = "https://astral.sh/uv/install.sh" -name = "uv installer" - [repos] odoo = ["odoo", "enterprise"] oca = ["server-tools", "server-ux", "web"] From 1af9e5f0d493824f7cdb83adf4a85c0dcceb8d83 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Tue, 24 Feb 2026 12:39:37 +0700 Subject: [PATCH 4/7] docs: remove uv script entry from project-overview-pdr example config uv is installed via bootstrap.sh, not via tools.script Co-Authored-By: Claude Sonnet 4.6 --- docs/project-overview-pdr.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/project-overview-pdr.md b/docs/project-overview-pdr.md index 64b636c..9432841 100644 --- a/docs/project-overview-pdr.md +++ b/docs/project-overview-pdr.md @@ -140,10 +140,6 @@ uv = ["odoo-venv", "odoo-addons-path", "pre-commit"] npm = ["prettier", "eslint"] system_packages = ["git", "postgresql"] -[[tools.script]] -url = "https://astral.sh/uv/install.sh" -name = "uv installer" - [repos] odoo = ["odoo", "enterprise"] oca = ["server-tools", "server-ux", "web"] @@ -181,8 +177,8 @@ Shell scripts to download and execute - **Example**: ```toml [[tools.script]] - url = "https://astral.sh/uv/install.sh" - name = "uv installer" + url = "https://example.com/install.sh" + name = "my installer" ``` #### `tools.system_packages` (Optional) From 86dbd18aba985a4200037d51ba4205d57fe85690 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Tue, 24 Feb 2026 12:42:22 +0700 Subject: [PATCH 5/7] refactor: extract config example to assets/odoo_dev.toml Co-Authored-By: Claude Sonnet 4.6 --- trobz_local/assets/odoo_dev.toml | 33 +++++++++++++++++++++++++++++ trobz_local/utils.py | 36 ++------------------------------ 2 files changed, 35 insertions(+), 34 deletions(-) create mode 100644 trobz_local/assets/odoo_dev.toml diff --git a/trobz_local/assets/odoo_dev.toml b/trobz_local/assets/odoo_dev.toml new file mode 100644 index 0000000..d63807e --- /dev/null +++ b/trobz_local/assets/odoo_dev.toml @@ -0,0 +1,33 @@ +versions = ["14.0", "15.0", "16.0", "17.0", "18.0"] + +[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/utils.py b/trobz_local/utils.py index 4603cd4..3e251fa 100644 --- a/trobz_local/utils.py +++ b/trobz_local/utils.py @@ -2,6 +2,7 @@ import platform import re import shutil +from importlib.resources import files from pathlib import Path import git @@ -162,40 +163,7 @@ def get_uv_path(): def show_config_instructions(): - content = """versions = ["14.0", "15.0", "16.0", "17.0", "18.0"] - -[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", -] -""" + 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:") From c9d95efbc888e59e9367216813311b1c6e302d65 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Wed, 25 Feb 2026 10:54:12 +0700 Subject: [PATCH 6/7] feat: add -y/--yes flag for non-interactive mode Co-Authored-By: Claude Sonnet 4.6 --- trobz_local/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/trobz_local/main.py b/trobz_local/main.py index fbf31d0..22a77cb 100644 --- a/trobz_local/main.py +++ b/trobz_local/main.py @@ -47,12 +47,18 @@ def main( help="Enable newcomer mode with confirmations and help.", envvar="NEWCOMER_MODE", ), + yes: bool = typer.Option( + False, + "--yes", + "-y", + help="Skip all confirmations (non-interactive mode).", + ), ): """ Hi, I'm a CLI to help you setup and manage your local environment for Odoo development. """ ctx.ensure_object(dict) - ctx.obj["newcomer"] = newcomer + ctx.obj["newcomer"] = newcomer and not yes if ctx.invoked_subcommand is None: _run_init(ctx) From 04bb2a4ea2a8eb3c8d0d09bc5c7e46137d38b675 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Wed, 25 Feb 2026 11:44:48 +0700 Subject: [PATCH 7/7] fix: always install default system packages in install-tools Default OS packages (UBUNTU_PACKAGES etc.) were silently skipped when system_packages was empty or absent in config. Two root causes fixed: - _run_installers: removed the falsy-list guard around install_system_packages - install_system_packages: removed early return that bypassed default packages Also adds --install-default-system-packages/--no-install-default-system-packages flag (default: true) to opt out of default package installation. Co-Authored-By: Claude Sonnet 4.6 --- tests/test_install_tools.py | 6 ++++-- trobz_local/installers.py | 10 ++++------ trobz_local/main.py | 21 +++++++++++---------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/test_install_tools.py b/tests/test_install_tools.py index 07c2029..d3929a5 100644 --- a/tests/test_install_tools.py +++ b/tests/test_install_tools.py @@ -24,10 +24,11 @@ def mock_typer_confirm(): @patch("trobz_local.main.setup_postgresql_repo", return_value=True) +@patch("trobz_local.main.install_system_packages", return_value=True) @patch("trobz_local.installers.shutil.which") @patch("trobz_local.main.get_config") @patch("trobz_local.installers.subprocess.run") -def test_install_uv_tools(mock_subprocess, mock_get_config, mock_which, _mock_pg): +def test_install_uv_tools(mock_subprocess, mock_get_config, mock_which, _mock_sys_pkgs, _mock_pg): mock_which.return_value = "/usr/bin/uv" mock_get_config.return_value = { "tools": { @@ -133,10 +134,11 @@ def test_install_npm_packages_npm_missing(mock_subprocess, mock_get_config, mock @patch("trobz_local.main.setup_postgresql_repo", return_value=True) +@patch("trobz_local.main.install_system_packages", return_value=True) @patch("trobz_local.installers.shutil.which") @patch("trobz_local.main.get_config") @patch("trobz_local.installers.subprocess.run") -def test_install_npm_packages_success(mock_subprocess, mock_get_config, mock_which, _mock_pg): +def test_install_npm_packages_success(mock_subprocess, mock_get_config, mock_which, _mock_sys_pkgs, _mock_pg): mock_which.return_value = "/usr/bin/npm" mock_get_config.return_value = { "tools": { diff --git a/trobz_local/installers.py b/trobz_local/installers.py index 26dd68a..cce5efe 100644 --- a/trobz_local/installers.py +++ b/trobz_local/installers.py @@ -156,10 +156,7 @@ def _run_package_install(cmd: list[str], packages: list[str]) -> bool: return True -def install_system_packages(packages: list[str], dry_run: bool = False) -> bool: - if not packages: - return True - +def install_system_packages(packages: list[str], dry_run: bool = False, install_defaults: bool = True) -> bool: os_info = get_os_info() system = os_info["system"] distro = os_info["distro"] @@ -177,8 +174,9 @@ def install_system_packages(packages: list[str], dry_run: bool = False) -> bool: cmd, default_packages = config - # Merge user packages with default packages - all_packages = list(dict.fromkeys(default_packages + packages)) + all_packages = list(dict.fromkeys((default_packages if install_defaults else []) + packages)) + if not all_packages: + return True if dry_run: typer.echo(f"\n[System packages - would be installed via {cmd[0]}]") diff --git a/trobz_local/main.py b/trobz_local/main.py index 22a77cb..41ccaef 100644 --- a/trobz_local/main.py +++ b/trobz_local/main.py @@ -287,7 +287,9 @@ def _build_install_message(tools_config: dict) -> str: return msg -def _run_installers(tools_config: dict, dry_run: bool) -> tuple[list, bool]: +def _run_installers( + tools_config: dict, dry_run: bool, install_default_system_packages: bool = True +) -> tuple[list, bool]: all_results = [] any_failed = False @@ -309,14 +311,12 @@ def _run_installers(tools_config: dict, dry_run: bool) -> tuple[list, bool]: if not dry_run: setup_postgresql_repo() - if tools_config.get("system_packages"): - success = install_system_packages(tools_config["system_packages"], dry_run) - if not success: - any_failed = True - - all_results.append( - TaskResult(name="system-packages", success=False, message="System package installation failed") - ) + success = install_system_packages(tools_config.get("system_packages", []), dry_run, install_default_system_packages) + if not success: + any_failed = True + all_results.append( + TaskResult(name="system-packages", success=False, message="System package installation failed") + ) if tools_config.get("npm"): results = install_npm_packages(tools_config["npm"], dry_run) @@ -337,6 +337,7 @@ def _run_installers(tools_config: dict, dry_run: bool) -> tuple[list, bool]: def install_tools( ctx: typer.Context, dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be installed without executing."), + install_default_system_packages: bool = typer.Option(True, help="Install default OS system packages."), ): """ Install tools using uv tool based on config. @@ -359,7 +360,7 @@ def install_tools( msg = _build_install_message(tools_config) confirm_step(ctx, msg, "install-tools") - all_results, any_failed = _run_installers(tools_config, dry_run) + all_results, any_failed = _run_installers(tools_config, dry_run, install_default_system_packages) if not dry_run: if any_failed: