Ports generic-scripts-module-v1 into a routed skill: module anatomy, shared loggers/base modules, Taskfile integration, workflows, and pattern decisions. Fixes a latent defect in the loggers example that silenced all logging in the module scaffold: loggers/lib/--index-api.sh now sources --env-vars-reader.sh, and the reader preserves already-exported values so caller overrides still win. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
204 lines
6.7 KiB
Markdown
204 lines
6.7 KiB
Markdown
# 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/-<command>.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="_<module>_<function>"` — 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 |
|