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
18 changes: 9 additions & 9 deletions cli/bash/bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This directory holds the user-facing Bash entrypoints.

- `bash-wrapper`
The shared dispatcher used to launch Bash commands.
- `<command>` symlinks
- `<command>.sh` symlinks
Each command symlink points to `bash-wrapper`. The wrapper uses the invoked filename to decide which command to run.
- `tests/`
Wrapper-specific BATS coverage for `bash-wrapper`.
Expand All @@ -16,16 +16,16 @@ This directory holds the user-facing Bash entrypoints.
The wrapper supports two invocation styles:

```bash
bash-wrapper <command> [args...]
<command> [args...]
bash-wrapper <command>.sh [args...]
<command>.sh [args...]
```

Behavior:

- When invoked as `bash-wrapper`, the first argument is treated as the command name.
- When invoked through a symlink, the symlink name is treated as the command name.
- Commands are resolved under `../commands/<name>/main.sh`.
- As a compatibility fallback, `../commands/<name>/<name>.sh` is also supported.
- Bash entrypoint symlinks are expected to end in `.sh`.
- When invoked through a symlink, the wrapper strips the `.sh` suffix and uses the remaining name as the command name.
- Commands are resolved under `../commands/<name>/<name>.sh`.

## What the Wrapper Provides

Expand Down Expand Up @@ -61,14 +61,14 @@ That keeps interactive shells and wrapper-launched commands on the same environm
Direct dispatch:

```bash
cli/bash/bin/bash-wrapper my-command --flag value
cli/bash/bin/bash-wrapper my-command.sh --flag value
```

Symlink dispatch:

```bash
ln -s bash-wrapper cli/bash/bin/my-command
cli/bash/bin/my-command --flag value
ln -s bash-wrapper cli/bash/bin/my-command.sh
cli/bash/bin/my-command.sh --flag value
```

## Tests
Expand Down
24 changes: 17 additions & 7 deletions cli/bash/bin/bash-wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ resolve_script_path() {
printf '%s/%s\n' "$link_dir" "$(basename "$source_path")"
}

normalize_command_name() {
local command_name="${1:-}"

if [[ "$command_name" == *.sh && "$command_name" != ".sh" ]]; then
command_name="${command_name%.sh}"
fi

printf '%s\n' "$command_name"
}

list_commands() {
local commands_dir="$1"
local command_dir command_name found=0
Expand All @@ -38,8 +48,8 @@ list_commands() {
while IFS= read -r command_dir; do
[[ -d "$command_dir" ]] || continue
command_name="$(basename "$command_dir")"
if [[ -f "$command_dir/main.sh" || -f "$command_dir/${command_name}.sh" ]]; then
printf ' %s\n' "$command_name"
if [[ -f "$command_dir/${command_name}.sh" ]]; then
printf ' %s.sh\n' "$command_name"
found=1
fi
done < <(find "$commands_dir" -mindepth 1 -maxdepth 1 -type d | sort)
Expand All @@ -58,9 +68,9 @@ Usage:

Behavior:
- When invoked as 'bash-wrapper', the first argument is treated as the command name.
- Bash entrypoint symlinks are expected to end in '.sh'; the wrapper maps '<name>.sh' to command '<name>'.
- When invoked through a symlink, the symlink name is treated as the command name.
- Commands are resolved under cli/bash/commands/<name>/main.sh.
- As a compatibility fallback, cli/bash/commands/<name>/<name>.sh is also accepted.
- Commands are resolved under cli/bash/commands/<name>/<name>.sh.

Available commands:
EOF
Expand Down Expand Up @@ -101,6 +111,8 @@ main() {
command_name="$invoked_as"
fi

command_name="$(normalize_command_name "$command_name")"

case "$command_name" in
""|.|..|*/* )
die "Invalid command name '$command_name'."
Expand All @@ -119,9 +131,7 @@ main() {
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"
elif [[ -f "$command_dir/${command_name}.sh" ]]; then
if [[ -f "$command_dir/${command_name}.sh" ]]; then
command_script="$command_dir/${command_name}.sh"
else
die "Command '$command_name' was not found under '$command_dir'."
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions cli/bash/bin/test_cmd.sh
78 changes: 42 additions & 36 deletions cli/bash/bin/tests/bash-wrapper.bats
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ create_bare_wrapper_layout() {
create_wrapper_layout() {
local layout_root="$1"
local command_name="$2"
local command_script_name="${3:-main.sh}"
local command_script_name="${3:-$command_name.sh}"

create_bare_wrapper_layout "$layout_root"
mkdir -p "$layout_root/commands/$command_name"
Expand All @@ -42,7 +42,7 @@ EOF
chmod +x "$layout_root/commands/$command_name/$command_script_name"
}

@test "bash-wrapper dispatches directly to commands/<name>/main.sh" {
@test "bash-wrapper dispatches directly to commands/<name>/<name>.sh" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"
local expected_repo_root expected_bash_root expected_bin_dir expected_env_script expected_script_path
Expand All @@ -54,9 +54,9 @@ EOF
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"
expected_script_path="$(cd "$layout/commands/demo" && pwd -P)/demo.sh"

run "$layout/bin/bash-wrapper" demo --debug-wrapper alpha beta
run "$layout/bin/bash-wrapper" demo.sh --debug-wrapper alpha beta

[ "$status" -eq 0 ]
[[ "$output" == *"script_dir=$expected_command_dir"* ]]
Expand All @@ -71,18 +71,18 @@ EOF
[[ "$output" == *"argv=alpha beta"* ]]
}

@test "symlink name selects the command" {
@test "symlink name with .sh suffix selects the command" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"
local expected_script_path
local expected_command_dir

create_wrapper_layout "$layout" greet
ln -s bash-wrapper "$layout/bin/greet"
ln -s bash-wrapper "$layout/bin/greet.sh"
expected_command_dir="$(cd "$layout/commands/greet" && pwd -P)"
expected_script_path="$(cd "$layout/commands/greet" && pwd -P)/main.sh"
expected_script_path="$(cd "$layout/commands/greet" && pwd -P)/greet.sh"

run "$layout/bin/greet" hello world
run "$layout/bin/greet.sh" hello world

[ "$status" -eq 0 ]
[[ "$output" == *"script_dir=$expected_command_dir"* ]]
Expand All @@ -91,25 +91,6 @@ EOF
[[ "$output" == *"argv=hello world"* ]]
}

@test "wrapper supports the fallback commands/<name>/<name>.sh layout" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"
local expected_script_path
local expected_command_dir

create_wrapper_layout "$layout" legacy "legacy.sh"
expected_command_dir="$(cd "$layout/commands/legacy" && pwd -P)"
expected_script_path="$(cd "$layout/commands/legacy" && pwd -P)/legacy.sh"

run "$layout/bin/bash-wrapper" legacy arg1

[ "$status" -eq 0 ]
[[ "$output" == *"script_dir=$expected_command_dir"* ]]
[[ "$output" == *"command=legacy"* ]]
[[ "$output" == *"script=$expected_script_path"* ]]
[[ "$output" == *"argv=arg1"* ]]
}

@test "wrapper prints usage when no command is provided" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"
Expand Down Expand Up @@ -149,13 +130,20 @@ EOF
echo "legacy"
EOF
chmod +x "$layout/commands/legacy/legacy.sh"
mkdir -p "$layout/commands/main-only"
cat > "$layout/commands/main-only/main.sh" <<'EOF'
#!/usr/bin/env bash
echo "main-only"
EOF
chmod +x "$layout/commands/main-only/main.sh"

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

[ "$status" -eq 0 ]
[[ "$output" == *" alpha"* ]]
[[ "$output" == *" legacy"* ]]
[[ "$output" == *" alpha.sh"* ]]
[[ "$output" == *" legacy.sh"* ]]
[[ "$output" != *"empty-dir"* ]]
[[ "$output" != *"main-only"* ]]
[[ "$output" != *"readme-only"* ]]
}

Expand Down Expand Up @@ -189,12 +177,30 @@ EOF

create_bare_wrapper_layout "$layout"

run "$layout/bin/bash-wrapper" missing
run "$layout/bin/bash-wrapper" missing.sh

[ "$status" -eq 1 ]
[[ "$output" == *"Command 'missing' was not found"* ]]
}

@test "wrapper rejects main.sh-only command directories" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"

create_bare_wrapper_layout "$layout"
mkdir -p "$layout/commands/legacy"
cat > "$layout/commands/legacy/main.sh" <<'EOF'
#!/usr/bin/env bash
echo "legacy"
EOF
chmod +x "$layout/commands/legacy/main.sh"

run "$layout/bin/bash-wrapper" legacy.sh

[ "$status" -eq 1 ]
[[ "$output" == *"Command 'legacy' was not found"* ]]
}

@test "wrapper errors when the stdlib is missing" {
local repo_root="$BATS_TEST_TMPDIR/repo"
local layout="$repo_root/cli/bash"
Expand Down Expand Up @@ -227,14 +233,14 @@ EOF

create_bare_wrapper_layout "$layout"
mkdir -p "$layout/commands/stdlib-demo"
cat > "$layout/commands/stdlib-demo/main.sh" <<'EOF'
cat > "$layout/commands/stdlib-demo/stdlib-demo.sh" <<'EOF'
#!/usr/bin/env bash
set_log_level DEBUG
run echo "wrapped output"
safe_touch "$BATS_TEST_TMPDIR/stdout.txt"
printf 'touched=%s\n' "$BATS_TEST_TMPDIR/stdout.txt"
EOF
chmod +x "$layout/commands/stdlib-demo/main.sh"
chmod +x "$layout/commands/stdlib-demo/stdlib-demo.sh"

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

Expand All @@ -250,14 +256,14 @@ EOF

create_bare_wrapper_layout "$layout"
mkdir -p "$layout/commands/flags"
cat > "$layout/commands/flags/main.sh" <<'EOF'
cat > "$layout/commands/flags/flags.sh" <<'EOF'
#!/usr/bin/env bash
printf 'orig=%s\n' "${__SCRIPT_ARGS__[*]}"
printf 'argv=%s\n' "$*"
printf 'log_debug=%s\n' "${LOG_DEBUG:-}"
printf 'log_utc=%s\n' "${LOG_UTC:-}"
EOF
chmod +x "$layout/commands/flags/main.sh"
chmod +x "$layout/commands/flags/flags.sh"

run "$layout/bin/bash-wrapper" flags --verbose-wrapper --utc-wrapper --color one two

Expand All @@ -273,10 +279,10 @@ EOF
local layout="$repo_root/cli/bash"

create_bare_wrapper_layout "$layout"
ln -s bash-wrapper "$layout/bin/orphan"
ln -s bash-wrapper "$layout/bin/orphan.sh"
mkdir -p "$layout/commands/orphan"

run "$layout/bin/orphan"
run "$layout/bin/orphan.sh"

[ "$status" -eq 1 ]
[[ "$output" == *"Command 'orphan' was not found"* ]]
Expand Down
80 changes: 80 additions & 0 deletions cli/bash/commands/setup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# `setup`

Bootstrap the local Banyan Labs CLI environment on macOS.

## What It Does

The command is intentionally small and idempotent.

## Commands

- `install`
Installs Homebrew, Xcode Command Line Tools, Python, and creates `$HOME/.banyan_venv`.
- `update-profile`
Reserved for future shell profile updates. This subcommand is not implemented yet.

## Install Behavior

The `install` command performs these steps:

1. install Homebrew if it is not already installed
2. install Xcode Command Line Tools if they are not already installed
3. install Python via Homebrew if it is not already installed
4. create `$HOME/.banyan_venv` if it does not already exist

## What It Does Not Do Yet

- update shell profiles such as `~/.bashrc` or `~/.zshrc`
- uninstall previously installed tools
- manage application-specific Python packages inside the virtual environment

## Usage

Via the wrapper:

```bash
cli/bash/bin/bash-wrapper setup.sh install
```

Via the symlinked entrypoint:

```bash
cli/bash/bin/setup.sh install
```

Help:

```bash
cli/bash/bin/setup.sh --help
```

Dry run:

```bash
cli/bash/bin/setup.sh install --dry-run
```

Verbose/debug logging:

```bash
cli/bash/bin/setup.sh -v install
```

## Configuration

The command supports a few environment-variable overrides, mainly for automation and tests:

- `BANYAN_SETUP_VENV_DIR`
- `BANYAN_SETUP_PYTHON_FORMULA`
- `BANYAN_SETUP_PYTHON_BIN`
- `BANYAN_SETUP_BREW_BIN`
- `BANYAN_SETUP_HOMEBREW_INSTALLER_SCRIPT`
- `BANYAN_SETUP_XCODE_COMMAND_LINE_TOOLS_DIR`

## Tests

Run the command test suite with:

```bash
bats cli/bash/commands/setup/tests/setup.bats
```
Loading
Loading