From 12eeb11ae702ba17e795068d648976432a264f97 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Mon, 28 Sep 2026 23:45:10 +0530 Subject: [PATCH 1/4] fix(cli): disambiguate help from project names --- .../commands/basectl/subcommands/activate.sh | 37 +++++++++++++++- .../commands/basectl/subcommands/uninstall.sh | 34 ++++++++++++++- cli/bash/commands/basectl/tests/activate.bats | 42 +++++++++++++++++++ .../commands/basectl/tests/uninstall.bats | 17 ++++++++ 4 files changed, 127 insertions(+), 3 deletions(-) diff --git a/cli/bash/commands/basectl/subcommands/activate.sh b/cli/bash/commands/basectl/subcommands/activate.sh index daca11c8..e214748e 100644 --- a/cli/bash/commands/basectl/subcommands/activate.sh +++ b/cli/bash/commands/basectl/subcommands/activate.sh @@ -13,6 +13,7 @@ Usage: basectl activate [options] Options: + --project Explicitly select a project, including one named "help". --workspace Workspace directory to scan. Defaults to workspace.root, then BASE_HOME's parent. --no-cd Preserve the caller's current directory in the project shell. -v Enable DEBUG logging for this subcommand. @@ -53,7 +54,7 @@ base_activate_shell_is_bash() { } base_activate_subcommand_main() { - local project="" wrapper resolve_output activate_shell venv_fix + local project="" explicit_project="" wrapper resolve_output activate_shell venv_fix local resolved_name project_root manifest_path venv_dir shell_rc route_venv_dir uses_uv_manager trust_required local preserve_cwd="${BASE_ACTIVATE_PRESERVE_CWD:-0}" local args=() @@ -68,6 +69,30 @@ base_activate_subcommand_main() { args+=(--debug) shift ;; + --project) + [[ -n "${2:-}" ]] || { + base_activate_usage_error "Option '--project' requires an argument." + return $? + } + [[ -z "$explicit_project" ]] || { + base_activate_usage_error "Option '--project' may be specified only once." + return $? + } + explicit_project="$2" + shift 2 + ;; + --project=*) + [[ -n "${1#*=}" ]] || { + base_activate_usage_error "Option '--project' requires an argument." + return $? + } + [[ -z "$explicit_project" ]] || { + base_activate_usage_error "Option '--project' may be specified only once." + return $? + } + explicit_project="${1#*=}" + shift + ;; --workspace) [[ -n "${2:-}" ]] || { base_activate_usage_error "Option '--workspace' requires an argument." @@ -89,6 +114,10 @@ base_activate_subcommand_main() { return $? ;; *) + if [[ -n "$explicit_project" ]]; then + base_activate_usage_error "The 'activate' command does not accept a positional project with --project." + return $? + fi if [[ -n "$project" ]]; then base_activate_usage_error "The 'activate' command accepts exactly one project name." return $? @@ -99,6 +128,12 @@ base_activate_subcommand_main() { esac done + [[ -z "$explicit_project" || -z "$project" ]] || { + base_activate_usage_error "The 'activate' command does not accept a positional project with --project." + return $? + } + [[ -z "$explicit_project" ]] || project="$explicit_project" + [[ -n "$project" ]] || { base_activate_usage_error "Project name is required." return $? diff --git a/cli/bash/commands/basectl/subcommands/uninstall.sh b/cli/bash/commands/basectl/subcommands/uninstall.sh index 461f0f88..b8a17ef8 100644 --- a/cli/bash/commands/basectl/subcommands/uninstall.sh +++ b/cli/bash/commands/basectl/subcommands/uninstall.sh @@ -11,6 +11,7 @@ Usage: basectl uninstall --all [options] Options: + --project Explicitly select a project, including one named "help". --all Remove all Base-managed local state and workspace settings. --workspace Workspace directory used to resolve a project name. @@ -47,7 +48,7 @@ base_uninstall_run_python() { } base_uninstall_subcommand_main() { - local all_projects=0 dry_run=0 verify=0 yes=0 debug=0 workspace="" project="" arg + local all_projects=0 dry_run=0 verify=0 yes=0 debug=0 workspace="" project="" explicit_project="" arg local -a python_args=() while (($# > 0)); do @@ -72,6 +73,29 @@ base_uninstall_subcommand_main() { -v) debug=1 ;; + --project) + shift + [[ -n "${1:-}" ]] || { + base_uninstall_usage_error "Option '--project' requires an argument." + return $? + } + [[ -z "$explicit_project" ]] || { + base_uninstall_usage_error "Option '--project' may be specified only once." + return $? + } + explicit_project="$1" + ;; + --project=*) + [[ -n "${arg#*=}" ]] || { + base_uninstall_usage_error "Option '--project' requires an argument." + return $? + } + [[ -z "$explicit_project" ]] || { + base_uninstall_usage_error "Option '--project' may be specified only once." + return $? + } + explicit_project="${arg#*=}" + ;; --workspace) shift [[ -n "${1:-}" ]] || { @@ -85,7 +109,7 @@ base_uninstall_subcommand_main() { base_uninstall_usage_error "Unknown option '$arg'." return $? fi - [[ -z "$project" ]] || { + [[ -z "$project" && -z "$explicit_project" ]] || { base_uninstall_usage_error "The 'uninstall' command accepts at most one project name." return $? } @@ -95,6 +119,12 @@ base_uninstall_subcommand_main() { shift done + [[ -z "$explicit_project" || -z "$project" ]] || { + base_uninstall_usage_error "The 'uninstall' command does not accept a positional project with --project." + return $? + } + [[ -z "$explicit_project" ]] || project="$explicit_project" + if ((all_projects && ${#project} > 0)); then base_uninstall_usage_error "Option '--all' cannot be combined with a project name." return $? diff --git a/cli/bash/commands/basectl/tests/activate.bats b/cli/bash/commands/basectl/tests/activate.bats index b609b13c..397aa313 100644 --- a/cli/bash/commands/basectl/tests/activate.bats +++ b/cli/bash/commands/basectl/tests/activate.bats @@ -508,6 +508,7 @@ EOF [[ "$output" == *"Usage:"* ]] [[ "$output" == *"basectl activate [options]"* ]] [[ "$output" == *"--no-cd"* ]] + [[ "$output" == *"--project "* ]] [[ "$output" == *"interactive Base Bash runtime shell"* ]] } @@ -532,6 +533,47 @@ EOF [ ! -f "$BASE_CACHE_DIR/base/history/runs.jsonl" ] } +@test "basectl activate targets a project named help with --project" { + local base_python="$TEST_HOME/.base.d/base/.venv/bin/python" + local workspace="$TEST_TMPDIR/workspace" + local project_root="$workspace/help" + local project_python="$project_root/.venv/bin/python" + local fake_bash="$TEST_TMPDIR/fake-bash" + + mkdir -p "$(dirname "$base_python")" "$(dirname "$project_python")" + cat > "$base_python" <<'EOF' +#!/usr/bin/env bash +source "${BASH_ENV:?}" +if [[ "${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "resolve" && "${4:-}" == "help" ]]; then + base_test_protocol_project_route help "${BASE_TEST_PROJECT_ROOT:?}" \ + "${BASE_TEST_PROJECT_ROOT:?}/base_manifest.yaml" "${BASE_TEST_PROJECT_ROOT:?}/.venv" false false + exit 0 +fi +printf 'unexpected activate resolver args: %s\n' "$*" >&2 +exit 1 +EOF + cat > "$fake_bash" <<'EOF' +#!/usr/bin/env bash +printf 'BASE_PROJECT=%s\n' "$BASE_PROJECT" +printf 'BASE_PROJECT_ROOT=%s\n' "$BASE_PROJECT_ROOT" +EOF + printf '#!/usr/bin/env bash\n' > "$project_python" + chmod +x "$base_python" "$project_python" "$fake_bash" + printf 'project:\n name: help\nartifacts: []\n' > "$project_root/base_manifest.yaml" + workspace="$(cd "$workspace" && pwd -P)" + + run env \ + HOME="$TEST_HOME" \ + PATH="/usr/bin:/bin:/usr/sbin:/sbin" \ + BASE_ACTIVATE_SHELL="$fake_bash" \ + BASE_TEST_PROJECT_ROOT="$workspace/help" \ + "$BASE_REPO_ROOT/bin/basectl" activate --project help + + [ "$status" -eq 0 ] + [[ "$output" == *"BASE_PROJECT=help"* ]] + [[ "$output" == *"BASE_PROJECT_ROOT=$workspace/help"* ]] +} + @test "basectl activate rejects non-Bash BASE_ACTIVATE_SHELL before launch" { local base_python="$TEST_HOME/.base.d/base/.venv/bin/python" local workspace="$TEST_TMPDIR/workspace" diff --git a/cli/bash/commands/basectl/tests/uninstall.bats b/cli/bash/commands/basectl/tests/uninstall.bats index 5a35f38a..c1a3beff 100644 --- a/cli/bash/commands/basectl/tests/uninstall.bats +++ b/cli/bash/commands/basectl/tests/uninstall.bats @@ -25,6 +25,23 @@ load ./basectl_helpers.bash [ "$output" = "$expected" ] } +@test "basectl uninstall targets a project named help with --project" { + local python_bin="$TEST_HOME/.base.d/base/.venv/bin/python" + + mkdir -p "$(dirname "$python_bin")" + cat > "$python_bin" <<'EOF' +#!/usr/bin/env bash +printf 'python=%s\n' "$*" +exit 0 +EOF + chmod +x "$python_bin" + + run_basectl uninstall --project help --dry-run + + [ "$status" -eq 0 ] + [[ "$output" == *"python=-m base_uninstall --dry-run help"* ]] +} + @test "basectl uninstall requires an explicit project or --all" { run_basectl uninstall From bc7b49683a5e7f34665da8f2483f094416327e7d Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:50:06 +0530 Subject: [PATCH 2/4] fix(cli): reuse project selection parsing --- cli/bash/commands/basectl/basectl.sh | 16 +++- .../commands/basectl/subcommands/activate.sh | 85 ++++--------------- .../subcommands/project_command_helpers.sh | 17 ++++ .../commands/basectl/subcommands/uninstall.sh | 81 ++++-------------- cli/bash/commands/basectl/tests/activate.bats | 3 +- .../tests/project-command-helpers.bats | 21 +++++ .../basectl/tests/runtime-dispatch.bats | 2 + 7 files changed, 90 insertions(+), 135 deletions(-) diff --git a/cli/bash/commands/basectl/basectl.sh b/cli/bash/commands/basectl/basectl.sh index 9a2fe2e4..4626f33d 100644 --- a/cli/bash/commands/basectl/basectl.sh +++ b/cli/bash/commands/basectl/basectl.sh @@ -583,7 +583,7 @@ basectl_runtime_slug() { basectl_run_bundle_project() { local command="$1" - local argument option_value=0 + local argument option_value=0 project_option=0 shift case "$command" in @@ -599,12 +599,24 @@ basectl_run_bundle_project() { for argument in "$@"; do if ((option_value)); then + if ((project_option)); then + basectl_runtime_slug "$argument" + return 0 + fi option_value=0 continue fi case "$argument" in - --manifest|--format|--repos|--profile|--environment|--config|--log-file|--project|--workspace|--path|--target|--version|--command|--status|--older-than|--keep-last|--since|--until|--last|--lines) + --project) option_value=1 + project_option=1 + ;; + --manifest|--format|--repos|--profile|--environment|--config|--log-file|--workspace|--path|--target|--version|--command|--status|--older-than|--keep-last|--since|--until|--last|--lines) + option_value=1 + ;; + --project=*) + basectl_runtime_slug "${argument#*=}" + return 0 ;; --*=*|--*|-*) ;; diff --git a/cli/bash/commands/basectl/subcommands/activate.sh b/cli/bash/commands/basectl/subcommands/activate.sh index e214748e..2358d702 100644 --- a/cli/bash/commands/basectl/subcommands/activate.sh +++ b/cli/bash/commands/basectl/subcommands/activate.sh @@ -7,6 +7,8 @@ _base_project_command_helpers_path="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" # shellcheck source=/dev/null source "$_base_project_command_helpers_path" +import_base_lib arg/lib_arg.sh + base_activate_subcommand_usage() { cat <<'EOF' Usage: @@ -30,12 +32,11 @@ base_activate_usage_error() { } base_activate_resolve_project() { - local project="$1" - local wrapper="$2" - shift 2 + local wrapper="$1" + shift env -u BASE_PROJECT_VENV_DIR \ - "$wrapper" --project base base_projects resolve "$project" "$@" --format command-protocol + "$wrapper" --project base base_projects resolve "$@" --format command-protocol } base_activate_project_venv_dir() { @@ -54,85 +55,30 @@ base_activate_shell_is_bash() { } base_activate_subcommand_main() { - local project="" explicit_project="" wrapper resolve_output activate_shell venv_fix + local project="" wrapper resolve_output activate_shell venv_fix local resolved_name project_root manifest_path venv_dir shell_rc route_venv_dir uses_uv_manager trust_required local preserve_cwd="${BASE_ACTIVATE_PRESERVE_CWD:-0}" - local args=() + local parse_args=() args=() while (($# > 0)); do case "$1" in - -h|--help|help) - base_activate_subcommand_usage - return 0 - ;; - -v) - args+=(--debug) - shift - ;; - --project) - [[ -n "${2:-}" ]] || { - base_activate_usage_error "Option '--project' requires an argument." - return $? - } - [[ -z "$explicit_project" ]] || { - base_activate_usage_error "Option '--project' may be specified only once." - return $? - } - explicit_project="$2" - shift 2 - ;; - --project=*) - [[ -n "${1#*=}" ]] || { - base_activate_usage_error "Option '--project' requires an argument." - return $? - } - [[ -z "$explicit_project" ]] || { - base_activate_usage_error "Option '--project' may be specified only once." - return $? - } - explicit_project="${1#*=}" - shift - ;; - --workspace) - [[ -n "${2:-}" ]] || { - base_activate_usage_error "Option '--workspace' requires an argument." - return $? - } - args+=(--workspace "$2") - shift 2 - ;; - --workspace=*) - args+=("$1") - shift - ;; --no-cd) preserve_cwd=1 shift ;; - -*) - base_activate_usage_error "Unknown activate option '$1'." - return $? - ;; *) - if [[ -n "$explicit_project" ]]; then - base_activate_usage_error "The 'activate' command does not accept a positional project with --project." - return $? - fi - if [[ -n "$project" ]]; then - base_activate_usage_error "The 'activate' command accepts exactly one project name." - return $? - fi - project="$1" + parse_args+=("$1") shift ;; esac done - [[ -z "$explicit_project" || -z "$project" ]] || { - base_activate_usage_error "The 'activate' command does not accept a positional project with --project." - return $? - } - [[ -z "$explicit_project" ]] || project="$explicit_project" + base_project_command_parse_args \ + activate base_activate_subcommand_usage base_activate_usage_error \ + "The 'activate' command accepts exactly one project name." "${parse_args[@]}" || return $? + [[ "$BASE_PROJECT_COMMAND_HELP_SHOWN" == 1 ]] && return 0 + project="$BASE_PROJECT_COMMAND_SELECTED_PROJECT" + args=("${BASE_PROJECT_COMMAND_ARGUMENTS[@]}") [[ -n "$project" ]] || { base_activate_usage_error "Project name is required." @@ -142,7 +88,8 @@ base_activate_subcommand_main() { wrapper="$BASE_HOME/bin/base-wrapper" [[ -x "$wrapper" ]] || base_std_fatal_error "Base Python wrapper '$wrapper' is missing or is not executable." - resolve_output="$(base_activate_resolve_project "$project" "$wrapper" "${args[@]}")" || return $? + resolve_output="$(base_activate_resolve_project "$wrapper" \ + "${BASE_PROJECT_COMMAND_SELECTION_ARGS[@]}" "${args[@]}")" || return $? base_command_protocol_decode_one project-route "$resolve_output" || { base_std_fatal_error "Unable to resolve project '$project'." } diff --git a/cli/bash/commands/basectl/subcommands/project_command_helpers.sh b/cli/bash/commands/basectl/subcommands/project_command_helpers.sh index a3db3b2b..1fd7c415 100644 --- a/cli/bash/commands/basectl/subcommands/project_command_helpers.sh +++ b/cli/bash/commands/basectl/subcommands/project_command_helpers.sh @@ -109,6 +109,8 @@ base_project_command_parse_args() { # shellcheck disable=SC2034 # The selected project is consumed by test.sh and demo.sh. BASE_PROJECT_COMMAND_PROJECT="" + # shellcheck disable=SC2034 # The selected project is consumed by activate.sh and uninstall.sh. + BASE_PROJECT_COMMAND_SELECTED_PROJECT="" # shellcheck disable=SC2034 # These shared parse outputs are consumed by test.sh and demo.sh. BASE_PROJECT_COMMAND_DRY_RUN=0 # shellcheck disable=SC2034 # The parser returns help to its caller without resolving a project. @@ -163,6 +165,19 @@ base_project_command_parse_args() { parser_args+=("--project=$2") shift 2 ;; + --project=*) + [[ -n "${1#*=}" ]] || { + "$usage_error_function" "Option '--project' requires an argument." + return $? + } + [[ -z "$explicit_project" ]] || { + "$usage_error_function" "Option '--project' may be specified only once." + return $? + } + explicit_project="${1#*=}" + parser_args+=("$1") + shift + ;; --dry-run) parser_args+=("$1") shift @@ -205,6 +220,8 @@ base_project_command_parse_args() { # shellcheck disable=SC2034 # The selected project is consumed by test.sh and demo.sh. BASE_PROJECT_COMMAND_PROJECT="$project" + # shellcheck disable=SC2034 # The selected project is consumed by activate.sh and uninstall.sh. + BASE_PROJECT_COMMAND_SELECTED_PROJECT="${explicit_project:-$project}" # shellcheck disable=SC2034 # Dry-run mode is consumed by test.sh and demo.sh. BASE_PROJECT_COMMAND_DRY_RUN="$dry_run" BASE_PROJECT_COMMAND_ARGUMENTS=("${args[@]}") diff --git a/cli/bash/commands/basectl/subcommands/uninstall.sh b/cli/bash/commands/basectl/subcommands/uninstall.sh index b8a17ef8..d1d7dc0c 100644 --- a/cli/bash/commands/basectl/subcommands/uninstall.sh +++ b/cli/bash/commands/basectl/subcommands/uninstall.sh @@ -4,6 +4,12 @@ _base_uninstall_subcommand_sourced=1 readonly _base_uninstall_subcommand_sourced +_base_project_command_helpers_path="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)/project_command_helpers.sh" +# shellcheck source=/dev/null +source "$_base_project_command_helpers_path" + +import_base_lib arg/lib_arg.sh + base_uninstall_subcommand_usage() { cat <<'EOF' Usage: @@ -48,82 +54,35 @@ base_uninstall_run_python() { } base_uninstall_subcommand_main() { - local all_projects=0 dry_run=0 verify=0 yes=0 debug=0 workspace="" project="" explicit_project="" arg - local -a python_args=() + local all_projects=0 verify=0 yes=0 project="" + local parse_args=() python_args=() while (($# > 0)); do - arg="$1" - case "$arg" in - -h|--help|help) - base_uninstall_subcommand_usage - return 0 - ;; + case "$1" in --all) all_projects=1 ;; - --dry-run) - dry_run=1 - ;; --yes) yes=1 ;; --verify) verify=1 ;; - -v) - debug=1 - ;; - --project) - shift - [[ -n "${1:-}" ]] || { - base_uninstall_usage_error "Option '--project' requires an argument." - return $? - } - [[ -z "$explicit_project" ]] || { - base_uninstall_usage_error "Option '--project' may be specified only once." - return $? - } - explicit_project="$1" - ;; - --project=*) - [[ -n "${arg#*=}" ]] || { - base_uninstall_usage_error "Option '--project' requires an argument." - return $? - } - [[ -z "$explicit_project" ]] || { - base_uninstall_usage_error "Option '--project' may be specified only once." - return $? - } - explicit_project="${arg#*=}" - ;; - --workspace) - shift - [[ -n "${1:-}" ]] || { - base_uninstall_usage_error "Option '--workspace' requires an argument." - return $? - } - workspace="$1" - ;; *) - if [[ "$arg" == -* ]]; then - base_uninstall_usage_error "Unknown option '$arg'." - return $? - fi - [[ -z "$project" && -z "$explicit_project" ]] || { - base_uninstall_usage_error "The 'uninstall' command accepts at most one project name." - return $? - } - project="$arg" + parse_args+=("$1") ;; esac shift done - [[ -z "$explicit_project" || -z "$project" ]] || { - base_uninstall_usage_error "The 'uninstall' command does not accept a positional project with --project." - return $? - } - [[ -z "$explicit_project" ]] || project="$explicit_project" + base_project_command_parse_args \ + uninstall base_uninstall_subcommand_usage base_uninstall_usage_error \ + "The 'uninstall' command accepts at most one project name." "${parse_args[@]}" || return $? + [[ "$BASE_PROJECT_COMMAND_HELP_SHOWN" == 1 ]] && return 0 + + local dry_run="$BASE_PROJECT_COMMAND_DRY_RUN" + project="$BASE_PROJECT_COMMAND_SELECTED_PROJECT" + python_args=("${BASE_PROJECT_COMMAND_ARGUMENTS[@]}") if ((all_projects && ${#project} > 0)); then base_uninstall_usage_error "Option '--all' cannot be combined with a project name." @@ -141,11 +100,7 @@ base_uninstall_subcommand_main() { base_uninstall_usage_error "Option '--verify' cannot be combined with '--dry-run' or '--yes'." return $? fi - if ((debug)); then - python_args+=(--debug) - fi ((all_projects)) && python_args+=(--all) - [[ -z "$workspace" ]] || python_args+=(--workspace "$workspace") ((dry_run)) && python_args+=(--dry-run) ((yes)) && python_args+=(--yes) ((verify)) && python_args+=(--verify) diff --git a/cli/bash/commands/basectl/tests/activate.bats b/cli/bash/commands/basectl/tests/activate.bats index 397aa313..affd999e 100644 --- a/cli/bash/commands/basectl/tests/activate.bats +++ b/cli/bash/commands/basectl/tests/activate.bats @@ -544,7 +544,8 @@ EOF cat > "$base_python" <<'EOF' #!/usr/bin/env bash source "${BASH_ENV:?}" -if [[ "${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "resolve" && "${4:-}" == "help" ]]; then +if [[ "${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "resolve" && \ + ("${4:-}" == "help" || ("${4:-}" == "--project" && "${5:-}" == "help")) ]]; then base_test_protocol_project_route help "${BASE_TEST_PROJECT_ROOT:?}" \ "${BASE_TEST_PROJECT_ROOT:?}/base_manifest.yaml" "${BASE_TEST_PROJECT_ROOT:?}/.venv" false false exit 0 diff --git a/cli/bash/commands/basectl/tests/project-command-helpers.bats b/cli/bash/commands/basectl/tests/project-command-helpers.bats index 25b77c0a..a2b1058a 100644 --- a/cli/bash/commands/basectl/tests/project-command-helpers.bats +++ b/cli/bash/commands/basectl/tests/project-command-helpers.bats @@ -96,6 +96,7 @@ manifest=$project_root/base_manifest.yaml" ] [[ "${#BASE_PROJECT_COMMAND_SELECTION_ARGS[@]}" -eq 2 ]] [[ "${BASE_PROJECT_COMMAND_SELECTION_ARGS[0]}" == --project ]] [[ "${BASE_PROJECT_COMMAND_SELECTION_ARGS[1]}" == demo ]] + [[ "$BASE_PROJECT_COMMAND_SELECTED_PROJECT" == demo ]] [[ "${#BASE_PROJECT_COMMAND_EXTRA_ARGS[@]}" -eq 2 ]] [[ "${BASE_PROJECT_COMMAND_EXTRA_ARGS[0]}" == "name with spaces" ]] [[ "${BASE_PROJECT_COMMAND_EXTRA_ARGS[1]}" == "$control_arg" ]] @@ -107,6 +108,26 @@ manifest=$project_root/base_manifest.yaml" ] [[ "$output" == *"argument boundaries preserved"* ]] } +@test "project command parser accepts equals-form explicit project selection" { + run env \ + BASE_HOME="$BASE_REPO_ROOT" \ + BASE_BASH_LIBS_DIR="${BASE_BASH_LIBS_DIR:-}" \ + bash -c ' + source "$BASE_HOME/base_init.sh" + import_base_lib arg/lib_arg.sh + source "$BASE_HOME/cli/bash/commands/basectl/subcommands/project_command_helpers.sh" + base_command_usage() { :; } + base_command_usage_error() { printf "ERROR:%s\n" "$*" >&2; return 2; } + base_project_command_parse_args activate base_command_usage base_command_usage_error \ + "The activate command accepts one project name." --project=help || exit $? + [[ "$BASE_PROJECT_COMMAND_SELECTED_PROJECT" == help ]] + [[ "${BASE_PROJECT_COMMAND_SELECTION_ARGS[0]}" == --project ]] + [[ "${BASE_PROJECT_COMMAND_SELECTION_ARGS[1]}" == help ]] + ' + + [ "$status" -eq 0 ] +} + @test "project command parser rejects duplicate explicit project options" { run env \ BASE_HOME="$BASE_REPO_ROOT" \ diff --git a/cli/bash/commands/basectl/tests/runtime-dispatch.bats b/cli/bash/commands/basectl/tests/runtime-dispatch.bats index f3271d3f..f44aef92 100644 --- a/cli/bash/commands/basectl/tests/runtime-dispatch.bats +++ b/cli/bash/commands/basectl/tests/runtime-dispatch.bats @@ -470,6 +470,7 @@ load ./basectl_helpers.bash bash -c ' source "$BASE_HOME/cli/bash/commands/basectl/basectl.sh" printf "label=%s\n" "$(basectl_run_bundle_label setup base-demo)" + printf "explicit-label=%s\n" "$(basectl_run_bundle_label activate --project help)" BASE_CACHE_DIR="$BASE_TEST_TMPDIR/cache" basectl_initialize_run_bundle setup base-demo || exit $? printf "run_id=%s\n" "$BASE_CLI_RUN_ID" printf "run_root=%s\n" "$BASE_CLI_RUN_ROOT" @@ -477,6 +478,7 @@ load ./basectl_helpers.bash [ "$status" -eq 0 ] [[ "$output" == *"label=setup__base-demo"* ]] + [[ "$output" == *"explicit-label=activate__help"* ]] [[ "$output" == *"run_root="*"__setup__base-demo" ]] run_id="$(printf '%s\n' "$output" | sed -n 's/^run_id=//p')" [[ "$run_id" != *"__"* ]] From 151e6aae4a5df28ad3ed2a634bb1efb66b32085b Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:41:10 +0530 Subject: [PATCH 3/4] fix(completions): expose activate project option --- lib/shell/completions/basectl_completion.sh | 2 +- lib/shell/completions/basectl_completion.zsh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/shell/completions/basectl_completion.sh b/lib/shell/completions/basectl_completion.sh index b9028cb5..3b579e02 100644 --- a/lib/shell/completions/basectl_completion.sh +++ b/lib/shell/completions/basectl_completion.sh @@ -620,7 +620,7 @@ _base_basectl_completion() { case "$command" in activate) _base_basectl_completion_project_or_options \ - "--workspace --no-cd -v -h --help" "$cur" "--workspace" + "--workspace --project --no-cd -v -h --help" "$cur" "--workspace --project" ;; projects) if ((COMP_CWORD == 2)); then diff --git a/lib/shell/completions/basectl_completion.zsh b/lib/shell/completions/basectl_completion.zsh index 34577012..bb211832 100644 --- a/lib/shell/completions/basectl_completion.zsh +++ b/lib/shell/completions/basectl_completion.zsh @@ -495,6 +495,7 @@ _base_basectl_completion() { case "${words[2]:-}" in activate) _arguments '--workspace[Workspace directory to scan]:path:_files' \ + '--project[Select a project explicitly]:Base project:->projects' \ '--no-cd[Preserve the caller current directory]' \ '-v[Enable DEBUG logging]' \ '(-h --help)'{-h,--help}'[Show help text]' \ From 52e80e1f067a025e9034b3d3dafd83b2eb78048b Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Wed, 30 Sep 2026 00:46:49 +0530 Subject: [PATCH 4/4] test(completions): expect activate project option --- cli/bash/commands/basectl/tests/completions.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/bash/commands/basectl/tests/completions.bats b/cli/bash/commands/basectl/tests/completions.bats index 339fb7a3..d4239708 100644 --- a/cli/bash/commands/basectl/tests/completions.bats +++ b/cli/bash/commands/basectl/tests/completions.bats @@ -287,7 +287,7 @@ EOF [ "$status" -eq 0 ] [[ "$output" == *"complete -F _base_basectl_completion basectl"* ]] [[ "$output" == *"activate_projects=base demo"* ]] - [[ "$output" == *"activate_options=--workspace --no-cd"* ]] + [[ "$output" == *"activate_options=--workspace --project --no-cd"* ]] [[ "$output" == *"check_projects=base demo"* ]] [[ "$output" == *"doctor_projects=base demo explain"* ]] [[ "$output" == *"doctor_explain_options=--format"* ]]