Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
41 changes: 40 additions & 1 deletion docs/execution-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading