# Module Anatomy The four file types inside `lib/`, the `api/` wrapper, and the patterns each follows. Read this when writing or changing a reader, a validator, an implementation function, or a public wrapper. --- ## 1. `--env-vars-reader.sh` — defaults and CLI parsing Responsibilities, in order: 1. export defaults, preserving any value already exported by the caller; 2. parse CLI flags into environment variables; 3. restore the positional arguments so later consumers still see them. ```bash #!/bin/bash export MODULE_EXAMPLE_V1_NAME="${MODULE_EXAMPLE_V1_NAME:-default-name}" export MODULE_EXAMPLE_V1_OUTPUT_DIR="${MODULE_EXAMPLE_V1_OUTPUT_DIR:-.tmp/module-example-v1}" export MODULE_EXAMPLE_V1_DRY_RUN="${MODULE_EXAMPLE_V1_DRY_RUN:-false}" ALL_ARGS=("$@") while [[ "$#" -gt 0 ]]; do case $1 in --name) export MODULE_EXAMPLE_V1_NAME="$2" shift ;; --output-dir) export MODULE_EXAMPLE_V1_OUTPUT_DIR="$2" shift ;; --dry-run) export MODULE_EXAMPLE_V1_DRY_RUN="true" ;; *) ;; esac shift done set -- "${ALL_ARGS[@]}" ``` Rules: - Use a stable uppercase environment prefix per module. - **Always use `${VAR:-default}`**, never a bare `export VAR=value`. A bare assignment silently discards a caller's override, and the caller has no way to detect it. This is what makes a reader safe to source more than once. - Prefer explicit CLI flags over positional arguments. - Defaults must be safe to run locally — a default that writes outside `.tmp/` or touches a shared system is not a safe default. - Never print secrets here. The `ALL_ARGS` save/restore matters because `shift` consumes the arguments. Without the `set --` at the end, anything sourced after the reader sees an empty argument list. --- ## 2. `--env-vars-validator.sh` — required inputs ```bash #!/bin/bash . ./.scripts/loggers/lib/--index-api.sh if [ -z "${MODULE_EXAMPLE_V1_NAME}" ]; then _loggers_error "module-example-v1" "Missing required --name / MODULE_EXAMPLE_V1_NAME" exit 1 fi ``` Rules: - Runs from `--index.sh`, after the reader and before the API index, so failures happen before any implementation function is even defined. - Use `_loggers_error` for the failure and `_loggers_info` for a usage hint. - `exit 1` is correct **here** — the validator runs during bootstrap of a top-level command, and stopping is the intent. - Validate only what every command in the module needs. A variable that only one command requires is validated inside that command's function, not globally — otherwise unrelated commands fail on input they never use. --- ## 3. `--index-api.sh` — dependency and implementation wiring ```bash #!/bin/bash . ./.scripts/loggers/lib/--index-api.sh . ./.scripts/base/lib/--index-api.sh . ./.scripts/module-example-v1/lib/-initialize-one.sh ``` Rules: - Upstream dependencies first, then this module's own implementation files. - Source other modules' `--index-api.sh`, **not** their `--index.sh` — see `references/shared-modules.md` §2. - **Every implementation file must appear here.** A `lib/-*.sh` that is never sourced defines nothing, and the failure surfaces as `command not found` for a function you can see on disk. - Safe to source repeatedly. Sourcing defines functions; it should have no other side effect. --- ## 4. `--index.sh` — runtime bootstrap ```bash #!/bin/bash # --> passed parameters are read & exported environment variables . ./.scripts/module-example-v1/lib/--env-vars-reader.sh # --> required environment variables are validated for existence . ./.scripts/module-example-v1/lib/--env-vars-validator.sh # --> available functions are imported/exported . ./.scripts/module-example-v1/lib/--index-api.sh ``` Rules: - Exactly these three sources, in exactly this order. - This is the module's **own** entrypoint, sourced by its `api/` wrappers. Other modules never source it — they take `--index-api.sh`. - A module with no required inputs may omit the validator, but keeping an empty one makes the next required variable a one-line change. --- ## 5. Implementation functions — `lib/-.sh` ```bash #!/bin/bash . ./.scripts/loggers/lib/--index-api.sh _moduleExampleV1_initializeOne() { local FUNCTION_NAME="_moduleExampleV1_initializeOne" _loggers_info "${FUNCTION_NAME}" "Initializing module-example-v1" _loggers_debug "${FUNCTION_NAME}" "Output directory: ${MODULE_EXAMPLE_V1_OUTPUT_DIR}" if [ "${MODULE_EXAMPLE_V1_DRY_RUN}" = "true" ]; then _loggers_info "${FUNCTION_NAME}" "Dry run enabled; no files will be changed." return 0 fi mkdir -p "${MODULE_EXAMPLE_V1_OUTPUT_DIR}" _loggers_info "${FUNCTION_NAME}" "Initialization complete." } ``` Rules: - Open with `local FUNCTION_NAME="__"` — every logger call takes it as the first argument, which is what makes log output traceable to a specific function. - One file, one primary function. Small private helpers may live alongside it. - Use `_loggers_*` helpers, never raw `echo`, for status and errors. - **`return` non-zero on failure; never `exit`.** The file is sourced, so `exit` terminates the caller's shell — including an interactive one. - Read configuration from the module's environment variables. Never re-parse `$@` here. - Quote every expansion unless word splitting is deliberate. - Never expose secret values in a log line. --- ## 6. `api/` wrappers ```bash #!/bin/bash . ./.scripts/module-example-v1/lib/--index.sh _moduleExampleV1_initializeOne ``` That is the whole file. Rules: - Source exactly `lib/--index.sh`, then call exactly one function. - No implementation, no flag parsing, no validation, no argument forwarding. - Arguments reach the reader automatically: sourcing happens while the wrapper's own `$@` is still in scope, so `--env-vars-reader.sh` sees them without being passed anything. - Create a wrapper only for a genuinely user-facing command. An internal helper stays in `lib/` — every file in `api/` is a public interface someone may come to depend on. --- ## 7. Diagnosing a broken module | Symptom | Cause | |---|---| | `command not found` for a function that exists on disk | Its file is missing from `lib/--index-api.sh` | | No log output at all, exit code 0 | Logger level flags never exported — see `references/shared-modules.md` §3 | | A flag is ignored | Not handled in `--env-vars-reader.sh`, or handled after the `set --` restore | | Later sources see no arguments | Reader is missing the `ALL_ARGS` save/`set --` restore | | `No such file or directory` for a `lib/` path | Run from inside the module instead of the repository root | | `.env` loaded unexpectedly | Something sourced `base/lib/--index.sh` instead of `base/lib/--index-api.sh` | | The shell exits on a handled error | `exit` used inside a sourced implementation function |