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
12 changes: 11 additions & 1 deletion cli/bash/bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,31 @@ Behavior:

Before sourcing the command script, `bash-wrapper`:

- sources `../../env/banyanenv.sh` to initialize the shared CLI environment
- resolves the repository, CLI, and Bash root directories
- exports wrapper metadata:
- `BANYAN_REPO_ROOT`
- `BANYAN_CLI_ROOT`
- `BANYAN_BASH_ROOT`
- `BANYAN_BASH_BIN_DIR`
- `BANYAN_CLI_ENV_SCRIPT`
- `BANYAN_BASH_COMMAND_NAME`
- `BANYAN_BASH_COMMAND_DIR`
- `BANYAN_BASH_COMMAND_SCRIPT`
- preloads `../lib/std/lib_std.sh`

That means command scripts can use the stdlib helpers without sourcing `lib_std.sh` themselves.
That means command scripts inherit both the shared environment and the stdlib helpers without having to source them directly.

The wrapper also sets `BANYAN_BASH_BOOTSTRAP_SOURCE` before loading the stdlib so stdlib path detection still treats the command script as the real caller.

`banyanenv.sh` is also meant to be sourced from a user's shell startup file:

```bash
source /path/to/banyanlabs/cli/env/banyanenv.sh
```

That keeps interactive shells and wrapper-launched commands on the same environment contract.

## Examples

Direct dispatch:
Expand Down
14 changes: 13 additions & 1 deletion cli/bash/bin/bash-wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ EOF
}

main() {
local invoked_as wrapper_path bin_dir bash_root cli_root repo_root commands_dir stdlib_path
local invoked_as wrapper_path bin_dir bash_root cli_root repo_root commands_dir stdlib_path env_script
local command_name command_dir command_script

invoked_as="$(basename "$0")"
Expand Down Expand Up @@ -107,6 +107,17 @@ main() {
;;
esac

env_script="$cli_root/env/banyanenv.sh"
[[ -f "$env_script" ]] || die "Required environment bootstrap '$env_script' was not found."
source "$env_script" || die "Failed to initialize Banyan CLI environment from '$env_script'."

bash_root="${BANYAN_BASH_ROOT:-$bash_root}"
cli_root="${BANYAN_CLI_ROOT:-$cli_root}"
repo_root="${BANYAN_REPO_ROOT:-$repo_root}"
bin_dir="${BANYAN_BASH_BIN_DIR:-$bin_dir}"
commands_dir="${BANYAN_BASH_COMMANDS_DIR:-$commands_dir}"
env_script="${BANYAN_CLI_ENV_SCRIPT:-$env_script}"

command_dir="$commands_dir/$command_name"
if [[ -f "$command_dir/main.sh" ]]; then
command_script="$command_dir/main.sh"
Expand All @@ -120,6 +131,7 @@ main() {
export BANYAN_CLI_ROOT="$cli_root"
export BANYAN_BASH_ROOT="$bash_root"
export BANYAN_BASH_BIN_DIR="$bin_dir"
export BANYAN_CLI_ENV_SCRIPT="$env_script"
export BANYAN_BASH_COMMAND_NAME="$command_name"
export BANYAN_BASH_COMMAND_DIR="$command_dir"
export BANYAN_BASH_COMMAND_SCRIPT="$command_script"
Expand Down
32 changes: 30 additions & 2 deletions cli/bash/bin/tests/bash-wrapper.bats
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ load ../../tests/test_helper.bash

create_bare_wrapper_layout() {
local layout_root="$1"
local cli_root

mkdir -p "$layout_root/bin" "$layout_root/commands" "$layout_root/lib/std"
cli_root="$(dirname "$layout_root")"

mkdir -p "$layout_root/bin" "$layout_root/commands" "$layout_root/lib/std" "$cli_root/env" "$cli_root/python"
cp "$BANYAN_REPO_ROOT/cli/env/banyanenv.sh" "$cli_root/env/banyanenv.sh"
cp "$BANYAN_BASH_DIR/bin/bash-wrapper" "$layout_root/bin/bash-wrapper"
cp "$BANYAN_BASH_DIR/lib/std/lib_std.sh" "$layout_root/lib/std/lib_std.sh"
chmod +x "$layout_root/bin/bash-wrapper"
Expand All @@ -26,7 +30,13 @@ printf 'orig_args=%s\n' "${__SCRIPT_ARGS__[*]:-}"
printf 'command=%s\n' "${BANYAN_BASH_COMMAND_NAME:-}"
printf 'repo=%s\n' "${BANYAN_REPO_ROOT:-}"
printf 'bash_root=%s\n' "${BANYAN_BASH_ROOT:-}"
printf 'bin_dir=%s\n' "${BANYAN_BASH_BIN_DIR:-}"
printf 'env_script=%s\n' "${BANYAN_CLI_ENV_SCRIPT:-}"
printf 'script=%s\n' "${BANYAN_BASH_COMMAND_SCRIPT:-}"
case ":$PATH:" in
*":${BANYAN_BASH_BIN_DIR:-__missing__}:"*) printf 'path_has_bin=yes\n' ;;
*) printf 'path_has_bin=no\n' ;;
esac
printf 'argv=%s\n' "$*"
EOF
chmod +x "$layout_root/commands/$command_name/$command_script_name"
Expand All @@ -35,12 +45,14 @@ EOF
@test "bash-wrapper dispatches directly to commands/<name>/main.sh" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"
local expected_repo_root expected_bash_root expected_script_path
local expected_repo_root expected_bash_root expected_bin_dir expected_env_script expected_script_path
local expected_command_dir

create_wrapper_layout "$layout" demo
expected_repo_root="$(cd "$repo_root" && pwd -P)"
expected_bash_root="$(cd "$layout" && pwd -P)"
expected_bin_dir="$(cd "$layout/bin" && pwd -P)"
expected_env_script="$(cd "$repo_root/cli/env" && pwd -P)/banyanenv.sh"
expected_command_dir="$(cd "$layout/commands/demo" && pwd -P)"
expected_script_path="$(cd "$layout/commands/demo" && pwd -P)/main.sh"

Expand All @@ -52,7 +64,10 @@ EOF
[[ "$output" == *"command=demo"* ]]
[[ "$output" == *"repo=$expected_repo_root"* ]]
[[ "$output" == *"bash_root=$expected_bash_root"* ]]
[[ "$output" == *"bin_dir=$expected_bin_dir"* ]]
[[ "$output" == *"env_script=$expected_env_script"* ]]
[[ "$output" == *"script=$expected_script_path"* ]]
[[ "$output" == *"path_has_bin=yes"* ]]
[[ "$output" == *"argv=alpha beta"* ]]
}

Expand Down Expand Up @@ -193,6 +208,19 @@ EOF
[[ "$output" == *"Required stdlib"* ]]
}

@test "wrapper errors when banyanenv is missing" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"

create_wrapper_layout "$layout" demo
rm -f "$repo_root/cli/env/banyanenv.sh"

run "$layout/bin/bash-wrapper" demo

[ "$status" -eq 1 ]
[[ "$output" == *"Required environment bootstrap"* ]]
}

@test "wrapper preloads stdlib so commands can call stdlib helpers without sourcing it" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"
Expand Down
47 changes: 47 additions & 0 deletions cli/env/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# `cli/env`

This directory holds the shared CLI environment bootstrap.

## Purpose

`banyanenv.sh` defines the common shell environment used by:

- `cli/bash/bin/bash-wrapper`
- interactive shells that source it from `~/.bashrc` or `~/.zshrc`
- future Bash and Python CLIs that want a single, shared environment contract

## Usage

Source it from a shell startup file or from another script:

```bash
source /path/to/banyanlabs/cli/env/banyanenv.sh
```

It must be sourced rather than executed.

## What It Exports

- `BANYAN_REPO_ROOT`
- `BANYAN_CLI_ROOT`
- `BANYAN_CLI_ENV_DIR`
- `BANYAN_CLI_ENV_SCRIPT`
- `BANYAN_BASH_ROOT`
- `BANYAN_BASH_BIN_DIR`
- `BANYAN_BASH_LIB_DIR`
- `BANYAN_BASH_COMMANDS_DIR`
- `BANYAN_PYTHON_ROOT`

It also prepends `cli/bash/bin` to `PATH` when that directory exists, without duplicating the entry on repeated sourcing.

## Compatibility

`banyanenv.sh` is designed to work in both Bash and zsh.

## Tests

Run the environment bootstrap test suite with:

```bash
bats cli/env/tests/banyanenv.bats
```
116 changes: 116 additions & 0 deletions cli/env/banyanenv.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env bash

#
# banyanenv.sh
# Sets up the Banyan Labs CLI shell environment.
# Source this file from bash-wrapper or from ~/.bashrc / ~/.zshrc:
# source /path/to/banyanlabs/cli/env/banyanenv.sh
# Compatible with both bash and zsh.
#

banyanenv_error() {
printf 'ERROR: %s\n' "$*" >&2
}

banyanenv_is_sourced() {
if [[ -n "${BASH_VERSION:-}" ]]; then
[[ "${BASH_SOURCE[0]}" != "$0" ]]
return
fi

if [[ -n "${ZSH_VERSION:-}" ]]; then
[[ "$(eval 'printf "%s\n" "${(%):-%x}"')" != "$0" ]]
return
fi

return 1
}

banyanenv_get_source_path() {
if [[ -n "${BASH_VERSION:-}" ]]; then
printf '%s\n' "${BASH_SOURCE[0]}"
return 0
fi

if [[ -n "${ZSH_VERSION:-}" ]]; then
eval 'printf "%s\n" "${(%):-%x}"'
return 0
fi

return 1
}

banyanenv_prepend_path() {
local dir="$1"

[[ -n "$dir" && -d "$dir" ]] || return 0

case ":${PATH:-}:" in
*":$dir:"*) ;;
*)
if [[ -n "${PATH:-}" ]]; then
PATH="$dir:$PATH"
else
PATH="$dir"
fi
export PATH
;;
esac
}

banyanenv_main() {
local source_path env_dir cli_root repo_root bash_root python_root

source_path="$(banyanenv_get_source_path)" || {
banyanenv_error "Unable to determine the path to banyanenv.sh."
return 1
}
[[ -n "$source_path" ]] || {
banyanenv_error "Unable to determine the path to banyanenv.sh."
return 1
}

env_dir="$(cd -- "$(dirname -- "$source_path")" && pwd -P)" || {
banyanenv_error "Unable to resolve cli/env root from '$source_path'."
return 1
}
cli_root="$(cd -- "$env_dir/.." && pwd -P)" || {
banyanenv_error "Unable to resolve cli root from '$env_dir'."
return 1
}
repo_root="$(cd -- "$cli_root/.." && pwd -P)" || {
banyanenv_error "Unable to resolve repository root from '$cli_root'."
return 1
}

bash_root="$cli_root/bash"
python_root="$cli_root/python"

export BANYAN_REPO_ROOT="$repo_root"
export BANYAN_CLI_ROOT="$cli_root"
export BANYAN_CLI_ENV_DIR="$env_dir"
export BANYAN_CLI_ENV_SCRIPT="$env_dir/banyanenv.sh"
export BANYAN_BASH_ROOT="$bash_root"
export BANYAN_BASH_BIN_DIR="$bash_root/bin"
export BANYAN_BASH_LIB_DIR="$bash_root/lib"
export BANYAN_BASH_COMMANDS_DIR="$bash_root/commands"
export BANYAN_PYTHON_ROOT="$python_root"

banyanenv_prepend_path "$BANYAN_BASH_BIN_DIR"

return 0
}

if ! banyanenv_is_sourced; then
banyanenv_error "banyanenv.sh must be sourced, not executed."
banyanenv_error "Use: source /path/to/banyanlabs/cli/env/banyanenv.sh"
exit 1
fi

banyanenv_main
_banyanenv_rc=$?
unset -f banyanenv_error banyanenv_is_sourced banyanenv_get_source_path banyanenv_prepend_path banyanenv_main
if [[ $_banyanenv_rc -ne 0 ]]; then
return "$_banyanenv_rc" 2>/dev/null || exit "$_banyanenv_rc"
fi
unset _banyanenv_rc
Loading
Loading