From 694f7285501374f18c9f66e15da500e9586f1b5a Mon Sep 17 00:00:00 2001 From: AntonioCS Date: Mon, 5 Jan 2026 20:43:54 +0000 Subject: [PATCH] Improve README.md documentation - Remove Windows support, recommend WSL instead - Add Common Commands section with table - Add Module System section with link to modules/README.md - Add Project Structure section with bind-hub layout - Add Debug Options section - Add Available Modules section - Streamline Installation and Upgrading Make sections - Create modules/README.md with detailed module creation guide - Create core/README.md with core system documentation - Update CLAUDE.md with README maintenance notes --- CLAUDE.md | 12 +++ README.md | 270 ++++++++++++++++++++++++++++++++++------------ core/README.md | 160 +++++++++++++++++++++++++++ modules/README.md | 155 ++++++++++++++++++++++++++ 4 files changed, 527 insertions(+), 70 deletions(-) create mode 100644 core/README.md create mode 100644 modules/README.md diff --git a/CLAUDE.md b/CLAUDE.md index 9eabf16..a585dc3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -336,6 +336,18 @@ Rules: - Bump minor version (e.g., 2.1.0 → 2.2.0) for new features/modules - Bump patch version (e.g., 2.1.0 → 2.1.1) for bug fixes +### README Files +**IMPORTANT**: Keep README files up to date when making changes: + +| File | Update When | +|------|-------------| +| `README.md` | Adding commands, changing installation, new user-facing features | +| `modules/README.md` | Changes to module structure, new module conventions | +| `core/README.md` | New core functions, changes to loading order, new utilities | +| `tests/README.md` | Changes to test framework, new assertions, test conventions | + +When adding a new module, ensure the "Available Modules" table in `README.md` is updated. + ### Trello Board **Board ID: `vBmmD6it`** - Must be set at the start of each session using `mcp__trello__set_active_board`. diff --git a/README.md b/README.md index cca8021..7a909dc 100644 --- a/README.md +++ b/README.md @@ -2,94 +2,89 @@ Streamline and manage your Makefile workflows with modular ease and project-specific customization. -*This README is a work in progress (WIP).* - ## Table of Contents - [Introduction](#introduction) - [Support](#support) - [Prerequisites](#prerequisites) - [Installation](#installation) -- [Usage](#usage) -- [Configuration](#configuration) -- [Upgrading Make](#upgrading-Make) - - [On Linux (Debian)](#on-linux-debian) - - [On Mac](#on-mac) - - [Autocompletion on Mac](#autocompletion-on-mac) +- [Upgrading Make](#upgrading-make) + - [On Linux (Debian)](#on-linux-debian) + - [On Mac](#on-mac) +- [Common Commands](#common-commands) +- [Module System](#module-system) + - [What are Modules?](#what-are-modules) + - [Module Structure](#module-structure) + - [System vs Project Modules](#system-vs-project-modules) +- [Project Structure](#project-structure) + - [The bind-hub Folder](#the-bind-hub-folder) + - [Configuration Files](#configuration-files) +- [Debug Options](#debug-options) +- [Available Modules](#available-modules) ## Introduction -MakeBind is a Makefile project manager designed to simplify and customize your Makefile workflows. It allows you to manage projects and create modular Makefiles with ease. + +MakeBind is a Makefile project manager designed to simplify and customize your Makefile workflows. It provides a plugin/module system for GNU Make, allowing you to compose reusable Makefile functionality and manage project-specific build configurations. ## Support -MakeBind is supported on the following operating systems: + +MakeBind is supported on: - Linux - macOS -- Windows (not fully tested and still a work in progress) -If you encounter any issues on any of the platforms, please open an issue. +Windows users can use [WSL (Windows Subsystem for Linux)](https://learn.microsoft.com/en-us/windows/wsl/install). + +If you encounter any issues, please [open an issue](https://github.com/AntonioCS/MakeBind/issues). ## Prerequisites + MakeBind requires the following tools to be installed on your system: - `make` version 4.4 or higher ## Installation -To install MakeBind, run the appropriate command for your operating system in your terminal in the root folder of you project: +Run this command in your project's root folder: -### Linux and macOS: ```shell curl -s -o ./Makefile https://raw.githubusercontent.com/AntonioCS/MakeBind/main/templates/Makefile.tpl.mk && make ``` -### Windows: -```powershell -powershell -Command "Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/AntonioCS/MakeBind/main/templates/Makefile.tpl.mk' -OutFile './Makefile'; & make" -``` +### What happens: +1. Downloads the Makefile template to your current directory +2. Runs `make`, which checks for MakeBind in the parent directory (`../MakeBind`) +3. If not found, downloads the latest release automatically +4. Creates a `bind-hub` folder with configuration files (`config.mk`, `project.mk`) and an `internal/` subfolder +5. Displays available targets - you're ready to use MakeBind! + +### Custom installation path -### Explanation: -- This command downloads the Makefile template and creates a Makefile in your current directory. -- `make` will then automatically execute and check for the existence of the `MakeBind` folder in the path specified by `mb_mb_default_path` (default is `../MakeBind`, meaning it will search in the parent directory). -- If the `MakeBind` folder does not exist, the latest release will be downloaded. -- The folder `bind-hub` will be created in the current directory, which contains important configuration files, including `config.mk` and `project.mk`, plus an `internal/` subfolder for auto-generated files. -- You will then see a list of available targets, and you can start using MakeBind. +By default, MakeBind looks in the parent directory. For better control, you can: +- Manually clone the repo to your preferred location +- Set the `MB_MAKEBIND_GLOBAL_PATH_ENV` environment variable -NOTE: if you want better control of where MakeBind is installed, you can either manually clone the repo or download from Github. -But you can also specify `MB_MAKEBIND_GLOBAL_PATH_ENV` and set that to the path you want. -On Linux and macOS, you can do this by running: +To set the environment variable permanently on Linux/macOS: ```shell cat <<'EOF' >> ~/.profile # MakeBind: global path env used by MakeBind tooling export MB_MAKEBIND_GLOBAL_PATH_ENV="full/path/to/MakeBind" EOF -``` +``` This will ensure that the environment variable is set for every session. -## Usage -MakeBind is designed to be simple to use and easy to configure. Here are some common commands you can use with MakeBind: -- `make` - The simplest way to run the default target which will just list all the available targets. -- `make ` - Run a specific target. - -## Configuration -As mentioned, when you run MakeBind for the first time, it will create the `config.mk` and `project.mk` files in the folder `bind-hub`. These files are used to configure MakeBind for your project. -- In `config.mk`, you can set all configuration variables that are used by MakeBind modules or your own modules. You can create a local version named `config.local.mk` to override the default values (do not commit this file to your repository). -- In `project.mk`, you can add all targets that are specific to your project. You can create a local version named `project.local.mk` to override the default values (do not commit this file to your repository). -- In the `bind-hub` folder, you can add all your custom modules in `bind-hub/modules`. - ## Upgrading Make ### On Linux (Debian) -Many Linux distributions come with `make` versions 4.2.1 or 4.3. We need to upgrade to the latest version. -The following script will install the latest version (specified in `SELECTED_MAKE_VERSION`): +Many Linux distributions ship with `make` 4.2.1 or 4.3. You'll need version 4.4+ for MakeBind. + +> **Note:** This script requires admin privileges (sudo). ```shell #!/bin/bash export SELECTED_MAKE_VERSION="4.4.1" sudo apt-get update -sudo apt-get install build-essential +sudo apt-get install build-essential cd /tmp wget "https://ftp.gnu.org/gnu/make/make-${SELECTED_MAKE_VERSION}.tar.gz" tar -xvzf "make-${SELECTED_MAKE_VERSION}.tar.gz" @@ -99,8 +94,7 @@ make sudo make install ``` -The above script will require admin privileges. -The script will: +This script will: - Update your package list - Install essential build packages for `make` compilation - Navigate to the `/tmp` directory @@ -112,44 +106,180 @@ To run this, copy and paste to a file (for example `update_make.sh`), then do: chmod +x update_make.sh && ./update_make.sh ``` -You can find all available `make` versions at [GNU's FTP site](https://ftp.gnu.org/gnu/make/). -If version `4.4.1` is no longer the latest, simply update the `SELECTED_MAKE_VERSION` variable in the script and re-run it. -Confirm that everything has been installed by doing `make --version` and checking that the version match the one specified in `SELECTED_MAKE_VERSION`. +You can find all available `make` versions at [GNU's FTP site](https://ftp.gnu.org/gnu/make/). +If version `4.4.1` is no longer the latest, update the `SELECTED_MAKE_VERSION` variable and re-run. + +Verify the installation: +```shell +make --version +``` ### On Mac -The default installed version of `make` on Mac is 3.81, which is outdated. -To upgrade your `make` version, we will use `homebrew`. -If you do not have `homebrew`, you can find the installation instructions [here](https://docs.brew.sh/Installation). -This is the package page: https://formulae.brew.sh/formula/make -If you are not using `Z shell`, replace `~/.zshrc` with the path to the appropriate configuration file for your shell. +macOS ships with `make` 3.81, which is too old. Use Homebrew to install a newer version. -To upgrade run the following script: -```bash -#!/bin/sh +> **Prerequisites:** [Homebrew](https://docs.brew.sh/Installation) must be installed. +> If you're not using zsh, replace `~/.zshrc` with your shell's config file. +```bash brew install make echo 'export PATH="$HOMEBREW_PREFIX/opt/make/libexec/gnubin:$PATH"' >> ~/.zshrc source ~/.zshrc ``` -To run this, copy and paste to a file (for example `update_make.sh`), then do: +This will: +- Install `make` via Homebrew (installed as `gmake`) +- Add the GNU make binary to your PATH so `make` uses the new version +- Reload your shell configuration + +Verify the installation: ```shell -chmod +x update_make.sh && ./update_make.sh +make --version ``` -This script will: -- Install `make` using Homebrew. -- Add the `gnubin` path to your shell configuration file: - - This command adds the new path to the top of your `~/.zshrc` file. - - Note: `$HOMEBREW_PREFIX` is an environment variable set during the installation of Homebrew. You can verify it using `echo $HOMEBREW_PREFIX`. - - This is needed to ensure that `make` is available as `brew` installs `make` as `gmake`. -- Reload your shell configuration: - -This process should upgrade `make` to a more recent version. -Run `make --version` and confirm that the version is the latest one. - #### Autocompletion on Mac If you want to enable autocompletion, you can use the extension [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions). +## Common Commands + +Here are the most commonly used commands in MakeBind: + +| Command | Description | +|---------|-------------| +| `make` | List all available targets (default) | +| `make ` | Run a specific target | +| `make mb/modules/list` | List all available modules | +| `make mb/modules/add/` | Add a module to your project | +| `make mb/modules/add//` | Add multiple modules at once | +| `make mb/modules/remove/` | Remove a module from your project | +| `make mb/help` | Get help on MakeBind | + +## Module System + +### What are Modules? + +Modules are reusable packages of Makefile functionality. Instead of writing repetitive Makefile code for common tasks like Docker management, PHP tooling, or AWS operations, you can simply add the appropriate module and get access to pre-built targets. + +For example, adding the `docker_compose` module gives you targets like: +- `dc/up` - Start containers +- `dc/down` - Stop containers +- `dc/logs` - View container logs + +### Module Structure + +Each module contains: +- `mod_info.mk` - Module metadata (name, version, dependencies) +- `.mk` - Implementation with targets +- `mod_config.mk` - (Optional) Default configuration + +For details on creating your own modules, see [modules/README.md](modules/README.md). + +### System vs Project Modules + +| Type | Location | Use Case | +|------|----------|----------| +| **System** | `modules/` (in MakeBind) | Reusable across all projects | +| **Project** | `bind-hub/modules/` | Specific to one project | + +## Project Structure + +### The bind-hub Folder + +When you initialize MakeBind, it creates a `bind-hub` folder in your project with the following structure: + +``` +bind-hub/ +├── config.mk # Project configuration (commit this) +├── config.local.mk # Local overrides (do NOT commit) +├── project.mk # Project-specific targets (commit this) +├── project.local.mk # Local target overrides (do NOT commit) +├── internal/ # Auto-generated files (do NOT edit) +│ └── modules.mk # List of enabled modules +├── configs/ # Module configuration overrides +│ └── _config.mk +└── modules/ # Project-specific custom modules + └── / +``` + +### Configuration Files + +**config.mk** - Set configuration variables used by MakeBind and modules: +```makefile +mb_project_name := my-project +mb_project_path := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..) +``` + +**config.local.mk** - Override settings for your local environment (gitignored): +```makefile +# Local overrides - do not commit +mb_debug := 1 +``` + +**project.mk** - Add targets specific to your project: +```makefile +deploy: ## Deploy the application + ./scripts/deploy.sh + +test: ## Run tests + ./scripts/test.sh +``` + +**project.local.mk** - Local target overrides (gitignored). + +**configs/_config.mk** - Override default module configuration: +```makefile +# Example: bind-hub/configs/docker_compose_config.mk +dc_file := docker/docker-compose.yml +dc_project_name := my-app +``` + +## Debug Options + +When troubleshooting issues, you can enable various debug flags: + +| Flag | Description | +|------|-------------| +| `mb_debug=1` | Enable general debugging output | +| `mb_debug_modules=1` | Debug module loading and discovery | +| `mb_debug_targets=1` | Debug target listing | +| `mb_debug_show_all_commands=1` | Show all shell commands being executed | + +Usage: +```shell +make mb_debug=1 +make mb_debug_modules=1 mb/modules/list +``` + +You can also set these in your `config.local.mk` for persistent debugging: +```makefile +mb_debug := 1 +mb_debug_show_all_commands := 1 +``` + +## Available Modules + +MakeBind comes with modules organized by category: + +| Category | Modules | Description | +|----------|---------|-------------| +| **Containers** | `docker`, `docker_compose` | Docker and Docker Compose management | +| **PHP** | `php`, `composer`, `phpunit`, `phpcs`, `phpstan`, `psalm` | PHP ecosystem tools | +| **PHP Frameworks** | `symfony`, `laravel` | Framework-specific targets | +| **Web Servers** | `nginx` | Web server management | +| **AWS** | `s3`, `sqs`, `sns`, `localstack` | AWS service integrations | +| **Infrastructure** | `terraform` | Infrastructure as Code | +| **Databases** | `postgresql` | Database management | +| **Project** | `project_builder` | Project scaffolding | + +To see all available modules with descriptions: +```shell +make mb/modules/list +``` + +To add a module: +```shell +make mb/modules/add/docker_compose +``` + +Module dependencies are automatically resolved - if a module requires another module, it will be added automatically. diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..fd83bc3 --- /dev/null +++ b/core/README.md @@ -0,0 +1,160 @@ +# MakeBind Core + +This folder contains the core functionality of MakeBind. These files are loaded automatically and provide the foundation for the module system. + +## Core Files + +| File | Description | +|------|-------------| +| `functions.mk` | Core utility functions (`mb_invoke`, `mb_shell_capture`, `mb_user_confirm`, etc.) | +| `modules_manager.mk` | Module discovery, loading, and dependency resolution | +| `targets.mk` | Target listing and help system (`mb/targets-list`, `mb/help`) | +| `util.mk` | Loads all utility files from `util/` | +| `init_project.mk` | Project initialization (creates `bind-hub` folder) | +| `deprecation.mk` | Handles deprecated function warnings | + +## Utility Files (util/) + +| File | Description | +|------|-------------| +| `variables.mk` | Common constants (`mb_true`, `mb_false`, `mb_on`, `mb_off`, `mb_empty`) | +| `colours.mk` | Terminal color output helpers | +| `os_detection.mk` | Cross-platform OS detection (`mb_os_is_linux`, `mb_os_is_mac`, `mb_os_is_windows`) | +| `cache.mk` | File-based caching system with TTL support | +| `debug.mk` | Debug output utilities | +| `git.mk` | Git utilities (`mb_staged_files`, etc.) | + +## Loading Order + +Understanding the loading order is important when extending MakeBind: + +1. `Makefile` includes `main.mk` +2. `main.mk` loads project config (`config.mk`, `config.local.mk`) +3. Core utilities loaded (`util.mk` → all util files) +4. Core functions loaded (`functions.mk`) +5. Module database built (`modules_manager.mk` → `mb_modules_build_db`) +6. Enabled modules loaded (`mb_load_modules`) +7. Project targets loaded (`project.mk`, `project.local.mk`) + +## Key Functions + +### Command Execution + +```makefile +# Execute a command with logging +$(call mb_invoke,docker compose up -d) + +# Execute and capture output +$(call mb_shell_capture,echo hello,result_var) + +# Execute with exit code capture +$(call mb_shell_capture_with_exitcode,command,output_var,exitcode_var) +``` + +### User Interaction + +```makefile +# Prompt for confirmation (y/n) +$(call mb_user_confirm,Are you sure?) + +# Print formatted messages +$(call mb_printf_info,This is an info message) +$(call mb_printf_warning,This is a warning) +$(call mb_printf_error,This is an error) +$(call mb_printf_success,This is a success message) +``` + +### File Operations + +```makefile +# Check if file/directory exists +$(call mb_exists,/path/to/file) +$(call mb_not_exists,/path/to/file) + +# Check if value is a URL +$(call mb_is_url,https://example.com) +``` + +### OS Detection + +```makefile +# Check operating system +$(if $(mb_os_is_linux),Linux-specific code) +$(if $(mb_os_is_mac),macOS-specific code) +$(if $(mb_os_is_windows),Windows-specific code) + +# Run OS-specific command +$(call mb_os_call,windows_cmd,unix_cmd) +``` + +### Caching + +```makefile +# Read from cache (sets variable if cache hit) +$(call mb_cache_read,my_cache_key,result_var) + +# Write to cache with TTL (seconds) +$(call mb_cache_write,my_cache_key,$(value),3600) +``` + +### Module Management + +```makefile +# Check if module is loaded +$(if $(call mb_module_is_loaded,docker_compose),Module is loaded) + +# Get module path +$(call mb_module_get_path,docker_compose) +``` + +## Variables + +### Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `mb_debug` | `0` | Enable debug output | +| `mb_invoke_print` | `1` | Print commands before execution | +| `mb_invoke_dry_run` | `0` | Don't execute, just print | +| `mb_invoke_silent` | `0` | Suppress all output | + +### Constants (util/variables.mk) + +| Variable | Value | Description | +|----------|-------|-------------| +| `mb_true` | `1` | Boolean true | +| `mb_false` | `0` | Boolean false | +| `mb_on` | `1` | Alias for true | +| `mb_off` | `0` | Alias for false | +| `mb_empty` | `` | Empty string | + +## Make Flags + +MakeBind sets these MAKEFLAGS: + +| Flag | Purpose | +|------|---------| +| `--always-make` | Targets always rebuild (`.PHONY` is redundant) | +| `--warn-undefined-variables` | Catch undefined variable usage | +| `--no-builtin-rules` | Clean slate, no implicit rules | +| `--no-builtin-variables` | No predefined variables | +| `--no-print-directory` | Suppress directory change messages | +| `--silent` | Quiet mode (unless `mb_debug_no_silence=1`) | + +## Shell Configuration + +```makefile +SHELL := /bin/bash +.SHELLFLAGS := -euco pipefail # -x added when mb_debug_show_all_commands=1 +.ONESHELL: # Multi-line recipes run in single shell +``` + +## Adding Core Functionality + +If you need to extend core functionality: + +1. **For project-specific functions:** Add to `bind-hub/project.mk` +2. **For reusable functions:** Create a module in `modules/` or `bind-hub/modules/` +3. **For MakeBind core changes:** Submit a PR to the repository + +Do not modify core files directly in your projects - they will be overwritten on updates. diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 0000000..b6592f1 --- /dev/null +++ b/modules/README.md @@ -0,0 +1,155 @@ +# MakeBind Modules + +This folder contains all system modules provided by MakeBind. This document explains how modules work and how to create your own. + +## Module Structure + +Every module is a folder containing at least two files: + +``` +mymodule/ +├── mod_info.mk # Required: Module metadata +├── mymodule.mk # Required: Implementation +└── mod_config.mk # Optional: Default configuration +``` + +### mod_info.mk (Required) + +Defines module metadata used by the module manager: + +```makefile +mb_module_name := mymodule +mb_module_version := 1.0.0 +mb_module_description := Brief description of what this module does +mb_module_depends := docker # Space-separated list of dependencies (optional) +mb_module_filename := mymodule.mk # Optional: defaults to .mk +``` + +| Variable | Required | Description | +|----------|----------|-------------| +| `mb_module_name` | Yes | Unique identifier for the module | +| `mb_module_version` | Yes | Semantic version (e.g., 1.0.0) | +| `mb_module_description` | Yes | Short description shown in `mb/modules/list` | +| `mb_module_depends` | No | Space-separated module dependencies | +| `mb_module_filename` | No | Custom implementation filename | + +### mod_config.mk (Optional) + +Default configuration variables for the module. Users can override these in `bind-hub/configs/_config.mk`. + +```makefile +# Default values - users can override in bind-hub/configs/mymodule_config.mk +mymodule_bin := /usr/local/bin/mytool +mymodule_config_file := config.yml +mymodule_verbose := 0 +``` + +**Naming convention:** Prefix all variables with the module name to avoid conflicts. + +### Implementation File (Required) + +The main module file containing targets and functions. Must use an include guard. + +```makefile +ifndef __MB_MODULES_MYMODULE__ +__MB_MODULES_MYMODULE__ := 1 + +# Targets use ## comments for descriptions (shown in target list) +mymodule/run: ## Run the tool + $(call mb_invoke,$(mymodule_bin) --config=$(mymodule_config_file)) + +mymodule/status: ## Show status + $(call mb_invoke,$(mymodule_bin) status) + +endif +``` + +**Key points:** +- Use include guard: `ifndef __MB_MODULES___` +- Prefix targets with module name: `mymodule/target` +- Use `## comment` for target descriptions (shown in `make`) +- Use `$(call mb_invoke,...)` for command execution + +## Creating a New Module + +### Option 1: Use the module creator + +```shell +make mb/modules/create/mymodule +``` + +This creates a skeleton module in `modules/mymodule/`. + +### Option 2: Create manually + +1. Create folder: `modules/mymodule/` (system) or `bind-hub/modules/mymodule/` (project) +2. Create `mod_info.mk` with required metadata +3. Create `mymodule.mk` with targets +4. Optionally create `mod_config.mk` for configurable defaults + +## System vs Project Modules + +| Type | Location | Use Case | +|------|----------|----------| +| System | `modules/` (in MakeBind) | Reusable across all projects | +| Project | `bind-hub/modules/` | Specific to one project | + +Project modules are loaded after system modules and can override system module targets. + +## Module Categories + +| Folder | Description | +|--------|-------------| +| `containers/` | Docker, Docker Compose | +| `php/` | PHP, Composer, PHPUnit, PHPCS, PHPStan, Psalm | +| `php/frameworks/` | Symfony, Laravel | +| `webservers/` | Nginx | +| `cloud_providers/aws/` | S3, SQS, SNS, LocalStack | +| `infrastructure/` | Terraform | +| `databases/` | PostgreSQL | +| `project_builder/` | Project scaffolding | + +## Example: docker_compose Module + +A real-world example from this repository: + +**mod_info.mk:** +```makefile +mb_module_name := docker_compose +mb_module_version := 1.0.0 +mb_module_description := Docker Compose targets for container orchestration +mb_module_depends := docker +``` + +**mod_config.mk:** +```makefile +dc_bin := docker compose +dc_file := docker-compose.yml +dc_project_name := +``` + +**docker_compose.mk:** (simplified) +```makefile +ifndef __MB_MODULES_DOCKER_COMPOSE__ +__MB_MODULES_DOCKER_COMPOSE__ := 1 + +dc/up: ## Start containers + $(call mb_invoke,$(dc_bin) -f $(dc_file) up -d) + +dc/down: ## Stop containers + $(call mb_invoke,$(dc_bin) -f $(dc_file) down) + +dc/logs: ## View container logs + $(call mb_invoke,$(dc_bin) -f $(dc_file) logs -f) + +endif +``` + +## Best Practices + +1. **Naming:** Use clear, descriptive names. Prefix all variables and targets with module name. +2. **Dependencies:** Declare all dependencies in `mod_info.mk`. They're auto-loaded. +3. **Configuration:** Put all tunables in `mod_config.mk` with sensible defaults. +4. **Documentation:** Every target should have a `## description` comment. +5. **Include guards:** Always use them to prevent double-loading. +6. **Use mb_invoke:** For consistent command execution and logging.