Skip to content

Commit 9a9854e

Browse files
authored
Merge pull request #5 from codeforester/hpr/add-setup-script
Standardize Bash CLI entrypoints around .sh commands and per-command scripts
2 parents 9b171d7 + 8a2c828 commit 9a9854e

9 files changed

Lines changed: 747 additions & 53 deletions

File tree

cli/bash/bin/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This directory holds the user-facing Bash entrypoints.
66

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

1818
```bash
19-
bash-wrapper <command> [args...]
20-
<command> [args...]
19+
bash-wrapper <command>.sh [args...]
20+
<command>.sh [args...]
2121
```
2222

2323
Behavior:
2424

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

3030
## What the Wrapper Provides
3131

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

6363
```bash
64-
cli/bash/bin/bash-wrapper my-command --flag value
64+
cli/bash/bin/bash-wrapper my-command.sh --flag value
6565
```
6666

6767
Symlink dispatch:
6868

6969
```bash
70-
ln -s bash-wrapper cli/bash/bin/my-command
71-
cli/bash/bin/my-command --flag value
70+
ln -s bash-wrapper cli/bash/bin/my-command.sh
71+
cli/bash/bin/my-command.sh --flag value
7272
```
7373

7474
## Tests

cli/bash/bin/bash-wrapper

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ resolve_script_path() {
2929
printf '%s/%s\n' "$link_dir" "$(basename "$source_path")"
3030
}
3131

32+
normalize_command_name() {
33+
local command_name="${1:-}"
34+
35+
if [[ "$command_name" == *.sh && "$command_name" != ".sh" ]]; then
36+
command_name="${command_name%.sh}"
37+
fi
38+
39+
printf '%s\n' "$command_name"
40+
}
41+
3242
list_commands() {
3343
local commands_dir="$1"
3444
local command_dir command_name found=0
@@ -38,8 +48,8 @@ list_commands() {
3848
while IFS= read -r command_dir; do
3949
[[ -d "$command_dir" ]] || continue
4050
command_name="$(basename "$command_dir")"
41-
if [[ -f "$command_dir/main.sh" || -f "$command_dir/${command_name}.sh" ]]; then
42-
printf ' %s\n' "$command_name"
51+
if [[ -f "$command_dir/${command_name}.sh" ]]; then
52+
printf ' %s.sh\n' "$command_name"
4353
found=1
4454
fi
4555
done < <(find "$commands_dir" -mindepth 1 -maxdepth 1 -type d | sort)
@@ -58,9 +68,9 @@ Usage:
5868
5969
Behavior:
6070
- When invoked as 'bash-wrapper', the first argument is treated as the command name.
71+
- Bash entrypoint symlinks are expected to end in '.sh'; the wrapper maps '<name>.sh' to command '<name>'.
6172
- When invoked through a symlink, the symlink name is treated as the command name.
62-
- Commands are resolved under cli/bash/commands/<name>/main.sh.
63-
- As a compatibility fallback, cli/bash/commands/<name>/<name>.sh is also accepted.
73+
- Commands are resolved under cli/bash/commands/<name>/<name>.sh.
6474
6575
Available commands:
6676
EOF
@@ -101,6 +111,8 @@ main() {
101111
command_name="$invoked_as"
102112
fi
103113

114+
command_name="$(normalize_command_name "$command_name")"
115+
104116
case "$command_name" in
105117
""|.|..|*/* )
106118
die "Invalid command name '$command_name'."
@@ -119,9 +131,7 @@ main() {
119131
env_script="${BANYAN_CLI_ENV_SCRIPT:-$env_script}"
120132

121133
command_dir="$commands_dir/$command_name"
122-
if [[ -f "$command_dir/main.sh" ]]; then
123-
command_script="$command_dir/main.sh"
124-
elif [[ -f "$command_dir/${command_name}.sh" ]]; then
134+
if [[ -f "$command_dir/${command_name}.sh" ]]; then
125135
command_script="$command_dir/${command_name}.sh"
126136
else
127137
die "Command '$command_name' was not found under '$command_dir'."

cli/bash/bin/test_cmd.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bash-wrapper

cli/bash/bin/tests/bash-wrapper.bats

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ create_bare_wrapper_layout() {
1818
create_wrapper_layout() {
1919
local layout_root="$1"
2020
local command_name="$2"
21-
local command_script_name="${3:-main.sh}"
21+
local command_script_name="${3:-$command_name.sh}"
2222

2323
create_bare_wrapper_layout "$layout_root"
2424
mkdir -p "$layout_root/commands/$command_name"
@@ -42,7 +42,7 @@ EOF
4242
chmod +x "$layout_root/commands/$command_name/$command_script_name"
4343
}
4444

45-
@test "bash-wrapper dispatches directly to commands/<name>/main.sh" {
45+
@test "bash-wrapper dispatches directly to commands/<name>/<name>.sh" {
4646
local repo_root="$BATS_TEST_TMPDIR/repo"
4747
local layout="$repo_root/cli/bash"
4848
local expected_repo_root expected_bash_root expected_bin_dir expected_env_script expected_script_path
@@ -54,9 +54,9 @@ EOF
5454
expected_bin_dir="$(cd "$layout/bin" && pwd -P)"
5555
expected_env_script="$(cd "$repo_root/cli/env" && pwd -P)/banyanenv.sh"
5656
expected_command_dir="$(cd "$layout/commands/demo" && pwd -P)"
57-
expected_script_path="$(cd "$layout/commands/demo" && pwd -P)/main.sh"
57+
expected_script_path="$(cd "$layout/commands/demo" && pwd -P)/demo.sh"
5858

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

6161
[ "$status" -eq 0 ]
6262
[[ "$output" == *"script_dir=$expected_command_dir"* ]]
@@ -71,18 +71,18 @@ EOF
7171
[[ "$output" == *"argv=alpha beta"* ]]
7272
}
7373

74-
@test "symlink name selects the command" {
74+
@test "symlink name with .sh suffix selects the command" {
7575
local repo_root="$BATS_TEST_TMPDIR/repo"
7676
local layout="$repo_root/cli/bash"
7777
local expected_script_path
7878
local expected_command_dir
7979

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

85-
run "$layout/bin/greet" hello world
85+
run "$layout/bin/greet.sh" hello world
8686

8787
[ "$status" -eq 0 ]
8888
[[ "$output" == *"script_dir=$expected_command_dir"* ]]
@@ -91,25 +91,6 @@ EOF
9191
[[ "$output" == *"argv=hello world"* ]]
9292
}
9393

94-
@test "wrapper supports the fallback commands/<name>/<name>.sh layout" {
95-
local repo_root="$BATS_TEST_TMPDIR/repo"
96-
local layout="$repo_root/cli/bash"
97-
local expected_script_path
98-
local expected_command_dir
99-
100-
create_wrapper_layout "$layout" legacy "legacy.sh"
101-
expected_command_dir="$(cd "$layout/commands/legacy" && pwd -P)"
102-
expected_script_path="$(cd "$layout/commands/legacy" && pwd -P)/legacy.sh"
103-
104-
run "$layout/bin/bash-wrapper" legacy arg1
105-
106-
[ "$status" -eq 0 ]
107-
[[ "$output" == *"script_dir=$expected_command_dir"* ]]
108-
[[ "$output" == *"command=legacy"* ]]
109-
[[ "$output" == *"script=$expected_script_path"* ]]
110-
[[ "$output" == *"argv=arg1"* ]]
111-
}
112-
11394
@test "wrapper prints usage when no command is provided" {
11495
local repo_root="$BATS_TEST_TMPDIR/repo"
11596
local layout="$repo_root/cli/bash"
@@ -149,13 +130,20 @@ EOF
149130
echo "legacy"
150131
EOF
151132
chmod +x "$layout/commands/legacy/legacy.sh"
133+
mkdir -p "$layout/commands/main-only"
134+
cat > "$layout/commands/main-only/main.sh" <<'EOF'
135+
#!/usr/bin/env bash
136+
echo "main-only"
137+
EOF
138+
chmod +x "$layout/commands/main-only/main.sh"
152139

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

155142
[ "$status" -eq 0 ]
156-
[[ "$output" == *" alpha"* ]]
157-
[[ "$output" == *" legacy"* ]]
143+
[[ "$output" == *" alpha.sh"* ]]
144+
[[ "$output" == *" legacy.sh"* ]]
158145
[[ "$output" != *"empty-dir"* ]]
146+
[[ "$output" != *"main-only"* ]]
159147
[[ "$output" != *"readme-only"* ]]
160148
}
161149

@@ -189,12 +177,30 @@ EOF
189177

190178
create_bare_wrapper_layout "$layout"
191179

192-
run "$layout/bin/bash-wrapper" missing
180+
run "$layout/bin/bash-wrapper" missing.sh
193181

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

186+
@test "wrapper rejects main.sh-only command directories" {
187+
local repo_root="$BATS_TEST_TMPDIR/repo"
188+
local layout="$repo_root/cli/bash"
189+
190+
create_bare_wrapper_layout "$layout"
191+
mkdir -p "$layout/commands/legacy"
192+
cat > "$layout/commands/legacy/main.sh" <<'EOF'
193+
#!/usr/bin/env bash
194+
echo "legacy"
195+
EOF
196+
chmod +x "$layout/commands/legacy/main.sh"
197+
198+
run "$layout/bin/bash-wrapper" legacy.sh
199+
200+
[ "$status" -eq 1 ]
201+
[[ "$output" == *"Command 'legacy' was not found"* ]]
202+
}
203+
198204
@test "wrapper errors when the stdlib is missing" {
199205
local repo_root="$BATS_TEST_TMPDIR/repo"
200206
local layout="$repo_root/cli/bash"
@@ -227,14 +233,14 @@ EOF
227233

228234
create_bare_wrapper_layout "$layout"
229235
mkdir -p "$layout/commands/stdlib-demo"
230-
cat > "$layout/commands/stdlib-demo/main.sh" <<'EOF'
236+
cat > "$layout/commands/stdlib-demo/stdlib-demo.sh" <<'EOF'
231237
#!/usr/bin/env bash
232238
set_log_level DEBUG
233239
run echo "wrapped output"
234240
safe_touch "$BATS_TEST_TMPDIR/stdout.txt"
235241
printf 'touched=%s\n' "$BATS_TEST_TMPDIR/stdout.txt"
236242
EOF
237-
chmod +x "$layout/commands/stdlib-demo/main.sh"
243+
chmod +x "$layout/commands/stdlib-demo/stdlib-demo.sh"
238244

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

@@ -250,14 +256,14 @@ EOF
250256

251257
create_bare_wrapper_layout "$layout"
252258
mkdir -p "$layout/commands/flags"
253-
cat > "$layout/commands/flags/main.sh" <<'EOF'
259+
cat > "$layout/commands/flags/flags.sh" <<'EOF'
254260
#!/usr/bin/env bash
255261
printf 'orig=%s\n' "${__SCRIPT_ARGS__[*]}"
256262
printf 'argv=%s\n' "$*"
257263
printf 'log_debug=%s\n' "${LOG_DEBUG:-}"
258264
printf 'log_utc=%s\n' "${LOG_UTC:-}"
259265
EOF
260-
chmod +x "$layout/commands/flags/main.sh"
266+
chmod +x "$layout/commands/flags/flags.sh"
261267

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

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

275281
create_bare_wrapper_layout "$layout"
276-
ln -s bash-wrapper "$layout/bin/orphan"
282+
ln -s bash-wrapper "$layout/bin/orphan.sh"
277283
mkdir -p "$layout/commands/orphan"
278284

279-
run "$layout/bin/orphan"
285+
run "$layout/bin/orphan.sh"
280286

281287
[ "$status" -eq 1 ]
282288
[[ "$output" == *"Command 'orphan' was not found"* ]]

cli/bash/commands/setup/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# `setup`
2+
3+
Bootstrap the local Banyan Labs CLI environment on macOS.
4+
5+
## What It Does
6+
7+
The command is intentionally small and idempotent.
8+
9+
## Commands
10+
11+
- `install`
12+
Installs Homebrew, Xcode Command Line Tools, Python, and creates `$HOME/.banyan_venv`.
13+
- `update-profile`
14+
Reserved for future shell profile updates. This subcommand is not implemented yet.
15+
16+
## Install Behavior
17+
18+
The `install` command performs these steps:
19+
20+
1. install Homebrew if it is not already installed
21+
2. install Xcode Command Line Tools if they are not already installed
22+
3. install Python via Homebrew if it is not already installed
23+
4. create `$HOME/.banyan_venv` if it does not already exist
24+
25+
## What It Does Not Do Yet
26+
27+
- update shell profiles such as `~/.bashrc` or `~/.zshrc`
28+
- uninstall previously installed tools
29+
- manage application-specific Python packages inside the virtual environment
30+
31+
## Usage
32+
33+
Via the wrapper:
34+
35+
```bash
36+
cli/bash/bin/bash-wrapper setup.sh install
37+
```
38+
39+
Via the symlinked entrypoint:
40+
41+
```bash
42+
cli/bash/bin/setup.sh install
43+
```
44+
45+
Help:
46+
47+
```bash
48+
cli/bash/bin/setup.sh --help
49+
```
50+
51+
Dry run:
52+
53+
```bash
54+
cli/bash/bin/setup.sh install --dry-run
55+
```
56+
57+
Verbose/debug logging:
58+
59+
```bash
60+
cli/bash/bin/setup.sh -v install
61+
```
62+
63+
## Configuration
64+
65+
The command supports a few environment-variable overrides, mainly for automation and tests:
66+
67+
- `BANYAN_SETUP_VENV_DIR`
68+
- `BANYAN_SETUP_PYTHON_FORMULA`
69+
- `BANYAN_SETUP_PYTHON_BIN`
70+
- `BANYAN_SETUP_BREW_BIN`
71+
- `BANYAN_SETUP_HOMEBREW_INSTALLER_SCRIPT`
72+
- `BANYAN_SETUP_XCODE_COMMAND_LINE_TOOLS_DIR`
73+
74+
## Tests
75+
76+
Run the command test suite with:
77+
78+
```bash
79+
bats cli/bash/commands/setup/tests/setup.bats
80+
```

0 commit comments

Comments
 (0)