diff --git a/FAQ.md b/FAQ.md index 05d4394..79c5282 100644 --- a/FAQ.md +++ b/FAQ.md @@ -159,6 +159,73 @@ that belongs to that product rather than to Base. Project installers should call Base when they need workspace primitives instead of reimplementing them. +## Writing Base Scripts + +### How do I write a Bash script that uses Base's standard library? + +Use `basectl` as the script interpreter: + +```bash +#!/usr/bin/env basectl + +main() { + local project="${1:-}" + + if [[ -z "$project" ]]; then + print_error "Project name is required." + return 2 + fi + + log_info "Checking project '$project'." + run git status --short +} +``` + +Make the script executable and run it directly: + +```bash +chmod +x ./scripts/check-project.sh +./scripts/check-project.sh base +``` + +The shebang form requires `basectl` to be on `PATH`. If Base is not on `PATH` +yet, run the script explicitly through the installed `basectl`: + +```bash +/path/to/base/bin/basectl ./scripts/check-project.sh base +``` + +In this mode, the script should define `main` and should not call `main "$@"` +itself. `basectl` receives the script path from the shebang, establishes the +Base runtime through `base_init.sh`, loads Base's Bash standard library, sources +the script, and then calls `main` with the user arguments. + +That gives the script helpers such as `log_info`, `print_error`, `fatal_error`, +`run`, `assert_command_exists`, and `import_base_lib` without sourcing +`lib_std.sh` directly. + +### When should I source lib_std.sh directly instead? + +Source `lib_std.sh` directly only for standalone Bash scripts that are not +intended to run through `basectl`: + +```bash +#!/usr/bin/env bash +source "/path/to/base/lib/bash/std/lib_std.sh" + +main() { + run echo "hello" +} + +main "$@" +``` + +Base-native scripts should prefer the `#!/usr/bin/env basectl` pattern because +it uses the same runtime bootstrap path as Base command implementations. + +For deeper details, see [Execution Model](docs/execution-model.md), [Base +Standards](STANDARDS.md), and [`lib_std.sh`](lib/bash/std/README.md). + ## More Information Useful starting points: diff --git a/docs/execution-model.md b/docs/execution-model.md index f1007c3..4b1b727 100644 --- a/docs/execution-model.md +++ b/docs/execution-model.md @@ -84,8 +84,47 @@ A script can also opt into Base with a shebang: #!/usr/bin/env basectl main() { - # script body + local project="${1:-}" + + if [[ -z "$project" ]]; then + print_error "Project name is required." + return 2 + fi + + log_info "Checking project '$project'." + run git status --short +} +``` + +In shebang mode, the script defines `main` but does not call `main "$@"` +itself. The operating system runs `basectl` with the script path as an +argument. `basectl` then: + +1. resolves `BASE_HOME` +2. treats the script path as an explicit Bash script +3. exports runtime metadata such as `BASE_BASH_COMMAND_NAME`, + `BASE_BASH_COMMAND_DIR`, and `BASE_BASH_COMMAND_SCRIPT` +4. sources `base_init.sh`, which loads Base runtime variables and the Bash + standard library +5. sources the script +6. calls `main` with the remaining user arguments + +This makes Base stdlib helpers such as `log_info`, `print_error`, +`fatal_error`, `run`, `assert_command_exists`, and `import_base_lib` available +without the script sourcing `lib_std.sh` directly. + +Standalone Bash scripts that are not intended to run through Base can still +source the standard library directly: + +```bash +#!/usr/bin/env bash +source "/path/to/base/lib/bash/std/lib_std.sh" + +main() { + run echo "hello" } + +main "$@" ``` ## Command Implementations