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
62 changes: 1 addition & 61 deletions cli/bash/commands/basectl/subcommands/setup_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import_base_lib str/lib_str.sh

source "$BASE_HOME/cli/bash/commands/basectl/subcommands/setup_check_results.sh"
source "$BASE_HOME/cli/bash/commands/basectl/subcommands/setup_diagnostics_fallback.sh"
source "$BASE_HOME/cli/bash/commands/basectl/subcommands/setup_doctor_visual.sh"
source "$BASE_HOME/lib/base/base_cli_runtime.sh"
source "$BASE_HOME/cli/bash/commands/basectl/subcommands/setup_linux_debian.sh"
source "$BASE_HOME/cli/bash/commands/basectl/subcommands/setup_macos_homebrew.sh"
Expand Down Expand Up @@ -851,67 +852,6 @@ setup_recovery_project_venv() {
printf "Run 'basectl setup %s --recreate-venv' to back up and recreate the project virtual environment.\n" "$project"
}

setup_doctor_visual_status_enabled() {
[[ "${BASE_SETUP_DOCTOR_NO_COLOR:-false}" != true ]] || return 1
[[ -z "${NO_COLOR:-}" ]] || return 1
[[ -n "${TERM:-}" && "${TERM:-}" != dumb ]] || return 1
[[ -t 1 ]]
}

setup_doctor_status_visual_parts() {
local status="$1"
local label color padding

case "$status" in
ok)
label="✓ ok"
color=$'\033[0;32m'
padding=" "
;;
warn)
label="! warn"
color=$'\033[0;33m'
padding=" "
;;
error)
label="✗ error"
color=$'\033[0;31m'
padding=""
;;
*)
label="$status"
color=""
padding=""
;;
esac

printf '%s\t%s\t%s\n' "$label" "$color" "$padding"
}

setup_print_doctor_finding() {
local status="$1"
local finding_id="$2"
local name="$3"
local message="$4"
local fix="${5:-}"
local color fix_indent label padding reset status_prefix

if setup_doctor_visual_status_enabled; then
IFS=$'\t' read -r label color padding <<<"$(setup_doctor_status_visual_parts "$status")"
reset=$'\033[0m'
status_prefix="${label}${padding} "
printf '%b%s%b%s %-9s %-26s %s\n' "$color" "$label" "$reset" "$padding" "$finding_id" "$name" "$message"
fix_indent=${#status_prefix}
else
status_prefix="$(printf '%-5s ' "$status")"
printf '%s%-9s %-26s %s\n' "$status_prefix" "$finding_id" "$name" "$message"
fix_indent=${#status_prefix}
fi
if [[ -n "$fix" ]]; then
printf '%*sFix: %s\n' "$fix_indent" '' "$fix"
fi
}

setup_print_project_check_json_with_venv() {
local precheck_json="$1"
local ok="$2"
Expand Down
66 changes: 66 additions & 0 deletions cli/bash/commands/basectl/subcommands/setup_doctor_visual.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash

[[ -n "${_base_setup_doctor_visual_sourced:-}" ]] && return 0
_base_setup_doctor_visual_sourced=1
readonly _base_setup_doctor_visual_sourced

setup_doctor_visual_status_enabled() {
[[ "${BASE_SETUP_DOCTOR_NO_COLOR:-false}" != true ]] || return 1
[[ -z "${NO_COLOR:-}" ]] || return 1
[[ -n "${TERM:-}" && "${TERM:-}" != dumb ]] || return 1
[[ -t 1 ]]
}

setup_doctor_status_visual_parts() {
local status="$1"
local label color padding

case "$status" in
ok)
label="✓ ok"
color=$'\033[0;32m'
padding=" "
;;
warn)
label="! warn"
color=$'\033[0;33m'
padding=" "
;;
error)
label="✗ error"
color=$'\033[0;31m'
padding=""
;;
*)
label="$status"
color=""
padding=""
;;
esac

printf '%s\t%s\t%s\n' "$label" "$color" "$padding"
}

setup_print_doctor_finding() {
local status="$1"
local finding_id="$2"
local name="$3"
local message="$4"
local fix="${5:-}"
local color fix_indent label padding reset status_prefix

if setup_doctor_visual_status_enabled; then
IFS=$'\t' read -r label color padding <<<"$(setup_doctor_status_visual_parts "$status")"
reset=$'\033[0m'
status_prefix="${label}${padding} "
printf '%b%s%b%s %-9s %-26s %s\n' "$color" "$label" "$reset" "$padding" "$finding_id" "$name" "$message"
fix_indent=${#status_prefix}
else
status_prefix="$(printf '%-5s ' "$status")"
printf '%s%-9s %-26s %s\n' "$status_prefix" "$finding_id" "$name" "$message"
fix_indent=${#status_prefix}
fi
if [[ -n "$fix" ]]; then
printf '%*sFix: %s\n' "$fix_indent" '' "$fix"
fi
}
20 changes: 20 additions & 0 deletions cli/bash/commands/basectl/tests/setup-common.bats
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,26 @@ run_setup_common_script() {
[[ "$output" == *"guard=1"* ]]
}

@test "setup_common sources doctor visual helper idempotently" {
run_setup_common_script '
source "$BASE_HOME/cli/bash/commands/basectl/subcommands/setup_doctor_visual.sh"
source "$BASE_HOME/cli/bash/commands/basectl/subcommands/setup_doctor_visual.sh"
for helper in \
setup_doctor_visual_status_enabled \
setup_doctor_status_visual_parts \
setup_print_doctor_finding; do
declare -F "$helper" >/dev/null || {
printf "missing helper: %s\n" "$helper" >&2
exit 25
}
done
printf "guard=%s\n" "${_base_setup_doctor_visual_sourced:-}"
'

[ "$status" -eq 0 ]
[[ "$output" == *"guard=1"* ]]
}

@test "setup_common reports WSL2 host context without changing platform support" {
run_setup_common_script '
BASE_TEST_MODE=true
Expand Down
23 changes: 12 additions & 11 deletions docs/setup-common-ownership.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,21 @@ entry-point functions are the stable anchors for future edits.
| File / span | Current responsibility | Entry-point anchors | Target owner |
| --- | --- | --- | --- |
| `setup_common.sh` 1-179 | Source guard, helper sourcing, shared cached paths, run state, dry-run/debug/yes/CI toggles, Ubuntu/Debian consent prompts, notification toggles, and CI mode detection. | `setup_refresh_cached_paths()`, `setup_clear_run_state()`, `setup_require_linux_debian_system_consent()` | Keep in shared shell orchestration. |
| `setup_common.sh` 183-268 | Platform/host-env helpers, platform support messages, test-hook gates, and shared non-runtime recovery text. | `setup_current_platform()`, `setup_current_host_env()`, `setup_reject_test_hook_if_disallowed()` | Keep platform policy shared while OS-specific implementation remains in platform helpers. |
| `setup_common.sh` 183-286 | Platform/host-env helpers, platform support messages, test-hook gates, and shared non-runtime recovery text. | `setup_current_platform()`, `setup_current_host_env()`, `setup_reject_test_hook_if_disallowed()` | Keep platform policy shared while OS-specific implementation remains in platform helpers. |
| `setup_common.sh` 272-310 | Completion notification behavior. | `setup_notify_completion()` | Keep in shared shell for now. Revisit `setup_notifications.sh` when notification policy grows beyond the current macOS-only surface into a cross-platform domain. |
| `setup_common.sh` 314-410 | Shared command-path probes, executable architecture, Rosetta state, GitHub CLI version display, and runtime-chain summary rendering. | `setup_command_path()`, `setup_rosetta_translation_state()`, `setup_print_runtime_chain_summary()` | Keep shared because the summary combines platform helper data with cross-platform runtime state. |
| `setup_common.sh` 414-632 | Base Bash library status, PYTHONPATH, diagnostics JSON bridge, and first-mile text fallback for Base check metadata. | `setup_base_check_metadata()`, `setup_diagnostics_python_bin()`, `setup_run_diagnostics_json()` | Base check metadata and structured diagnostics JSON are Python-primary; keep shell fallback only for pre-runtime text diagnostics. |
| `setup_common.sh` 414-665 | Base Bash library status, PYTHONPATH, diagnostics JSON bridge, and first-mile text fallback for Base check metadata. | `setup_base_check_metadata()`, `setup_diagnostics_python_bin()`, `setup_run_diagnostics_json()` | Base check metadata and structured diagnostics JSON are Python-primary; keep shell fallback only for pre-runtime text diagnostics. |
| `setup_common.sh` 636-784 | Project manifest resolution, project route dispatch, check-result recording, user config seeding, and legacy project-venv fallback helpers. | `setup_resolve_project_manifest()`, `setup_resolve_project_route()`, `setup_record_project_check_result()` | Continue moving structured route policy to Python; keep shell dispatch thin. |
| `setup_common.sh` 790-879 | Doctor visual status and project virtualenv JSON routing for pre-venv failure handling. | `setup_print_doctor_finding()`, `setup_print_project_check_json_with_venv()`, `setup_print_project_venv_doctor_json()` | Shell owns human doctor text and routes project virtualenv JSON to Python diagnostics. |
| `setup_common.sh` 933-1026 | Project pre-venv, bootstrap, and the shared project setup entrypoint. | `setup_run_project_pre_venv_layer()`, `setup_run_project_bootstrap_layer()`, `setup_run_project_artifact_setup()` | Keep as shared shell dispatch; project policy and payload shape remain Python-owned. |
| `setup_project_artifacts.sh` 1-322 | Project artifact setup/check/doctor, uv-manager, wrapper, and remote-network dispatch. | `setup_run_project_artifact_layer()`, `setup_run_project_artifact_check()`, `setup_run_project_artifact_doctor()` | Extracted under #1891; keep public function names and route decisions unchanged. |
| `setup_common.sh` 1030-1066 | Shared probe waiting plus platform/base check dispatch. | `setup_wait_for_base_check_probes()`, `setup_collect_platform_base_check_results()`, `setup_collect_base_check_results()` | Keep dispatch shared until check JSON assembly and probe orchestration have clearer Python boundaries. |
| `setup_common.sh` 1068-1276 | Base check text rendering, project check result status handling, top-level check orchestration, and raw check-result record routing for JSON. | `setup_run_check()`, `setup_run_check_json()`, `setup_print_check_text_results()` | Python owns JSON item assembly from raw shell result records; keep human text rendering and exit orchestration in shell. |
| `setup_common.sh` 1278-1302 | Platform install dispatch and top-level setup dispatch. | `setup_run_platform_install()`, `setup_run_install()` | Keep shared dispatch in `setup_common.sh`; install bodies belong in domain helpers. |
| `setup_linux_debian.sh` 1-422 | Ubuntu/Debian recovery text, Python finder, runtime tool probes, check collector, apt prerequisites, GitHub CLI apt-repo setup, and Linux install body. | `setup_find_linux_python_bin()`, `setup_collect_linux_debian_base_check_results()`, `setup_run_linux_debian_install()` | Extracted OS/platform helper; keep future Ubuntu/Debian policy here unless it is structured data better owned by Python. |
| `setup_macos_homebrew.sh` 1-601 | macOS/Homebrew recovery text, Homebrew discovery and installer policy, Xcode command-line tools, macOS Python finder, macOS host probes, and macOS install body. | `setup_find_brew_bin()`, `setup_install_homebrew()`, `setup_collect_macos_base_check_results()`, `setup_run_macos_install()` | Extracted OS/platform helper; keep future macOS/Homebrew policy here unless it is structured data better owned by Python. |
| `setup_venv.sh` 1-444 | Base runtime virtualenv health, pyvenv inspection, recreate behavior, platform Python dispatch, Base bootstrap package checks/install, venv check probes, CI-runtime checks, and CI-runtime install body. | `setup_virtualenv_healthy_path()`, `setup_create_virtualenv()`, `setup_collect_ci_runtime_check_results()`, `setup_run_ci_runtime_install()` | Extracted Base runtime helper; keep future runtime bootstrap policy here unless structured check output moves to Python. |
| `setup_doctor_visual.sh` 1-66 | Doctor visual status selection, status glyphs, and human-readable finding rendering. | `setup_doctor_visual_status_enabled()`, `setup_doctor_status_visual_parts()`, `setup_print_doctor_finding()` | Shell owns human doctor text rendering. |
| `setup_common.sh` 855-882 | Project virtualenv JSON routing for pre-venv failure handling. | `setup_print_project_check_json_with_venv()`, `setup_print_project_venv_doctor_json()` | Shell routes project virtualenv JSON to Python diagnostics. |
| `setup_common.sh` 885-978 | Project pre-venv, bootstrap, and the shared project setup entrypoint. | `setup_run_project_pre_venv_layer()`, `setup_run_project_bootstrap_layer()`, `setup_run_project_artifact_setup()` | Keep as shared shell dispatch; project policy and payload shape remain Python-owned. |
| `setup_project_artifacts.sh` 1-338 | Project artifact setup/check/doctor, uv-manager, wrapper, and remote-network dispatch. | `setup_run_project_artifact_layer()`, `setup_run_project_artifact_check()`, `setup_run_project_artifact_doctor()` | Extracted under #1891; keep public function names and route decisions unchanged. |
| `setup_common.sh` 982-1018 | Shared probe waiting plus platform/base check dispatch. | `setup_wait_for_base_check_probes()`, `setup_collect_platform_base_check_results()`, `setup_collect_base_check_results()` | Keep dispatch shared until check JSON assembly and probe orchestration have clearer Python boundaries. |
| `setup_common.sh` 1020-1230 | Base check text rendering, project check result status handling, top-level check orchestration, and raw check-result record routing for JSON. | `setup_run_check()`, `setup_run_check_json()`, `setup_print_check_text_results()` | Python owns JSON item assembly from raw shell result records; keep human text rendering and exit orchestration in shell. |
| `setup_common.sh` 1235-1259 | Platform install dispatch and top-level setup dispatch. | `setup_run_platform_install()`, `setup_run_install()` | Keep shared dispatch in `setup_common.sh`; install bodies belong in domain helpers. |
| `setup_linux_debian.sh` 1-435 | Ubuntu/Debian recovery text, Python finder, runtime tool probes, check collector, apt prerequisites, GitHub CLI apt-repo setup, and Linux install body. | `setup_find_linux_python_bin()`, `setup_collect_linux_debian_base_check_results()`, `setup_run_linux_debian_install()` | Extracted OS/platform helper; keep future Ubuntu/Debian policy here unless it is structured data better owned by Python. |
| `setup_macos_homebrew.sh` 1-562 | macOS/Homebrew recovery text, Homebrew discovery and installer policy, Xcode command-line tools, macOS Python finder, macOS host probes, and macOS install body. | `setup_find_brew_bin()`, `setup_install_homebrew()`, `setup_collect_macos_base_check_results()`, `setup_run_macos_install()` | Extracted OS/platform helper; keep future macOS/Homebrew policy here unless it is structured data better owned by Python. |
| `setup_venv.sh` 1-530 | Base runtime virtualenv health, pyvenv inspection, recreate behavior, platform Python dispatch, Base bootstrap package checks/install, venv check probes, CI-runtime checks, and CI-runtime install body. | `setup_virtualenv_healthy_path()`, `setup_create_virtualenv()`, `setup_collect_ci_runtime_check_results()`, `setup_run_ci_runtime_install()` | Extracted Base runtime helper; keep future runtime bootstrap policy here unless structured check output moves to Python. |
| `setup_profiles.sh` 1-137 | Setup/check profile parsing, profile state, profile JSON key naming, profile CSV rendering, and `base_dev` prerequisite profile dispatch. | `setup_enable_profile_argument()`, `setup_profiles_csv()`, `setup_run_base_dev_layer()` | Extracted profile helper; keep future profile parsing and dispatch policy here unless profile state moves to Python. |

## Decomposition Strategy
Expand Down
50 changes: 50 additions & 0 deletions tests/test_setup_common_ownership_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
/ "subcommands"
/ "setup_project_artifacts.sh"
)
SETUP_DOCTOR_VISUAL_SCRIPT = (
REPO_ROOT
/ "cli"
/ "bash"
/ "commands"
/ "basectl"
/ "subcommands"
/ "setup_doctor_visual.sh"
)
DOCS_README = REPO_ROOT / "docs" / "README.md"


Expand All @@ -49,6 +58,7 @@ def setup_shell_sources() -> str:
SETUP_VENV_SCRIPT,
SETUP_PROFILES_SCRIPT,
SETUP_PROJECT_ARTIFACTS_SCRIPT,
SETUP_DOCTOR_VISUAL_SCRIPT,
)
)

Expand All @@ -61,6 +71,29 @@ def documented_setup_function_anchors(markdown: str) -> set[str]:
return set(re.findall(r"`(setup_[a-z0-9_]+)\(\)`", markdown))


def documented_setup_rows(markdown: str) -> list[tuple[str, int, int, set[str]]]:
rows: list[tuple[str, int, int, set[str]]] = []
row_pattern = re.compile(
r"^\| `(?P<path>[^`]+)` (?P<start>\d+)-(?P<end>\d+) \| "
r"(?P<responsibility>[^|]*) \| (?P<anchors>[^|]*) \| "
r"(?P<owner>[^|]*) \|$"
)
for line in markdown.splitlines():
match = row_pattern.match(line)
if match is None:
continue
anchors = set(re.findall(r"`(setup_[a-z0-9_]+)\(\)`", match.group("anchors")))
rows.append(
(
match.group("path"),
int(match.group("start")),
int(match.group("end")),
anchors,
)
)
return rows


def test_setup_common_ownership_doc_is_linked_from_docs_map() -> None:
readme = DOCS_README.read_text(encoding="utf-8")

Expand Down Expand Up @@ -105,6 +138,23 @@ def test_setup_common_ownership_doc_function_anchors_exist() -> None:
assert not missing, "documented setup_common function anchors are missing: " + ", ".join(missing)


def test_setup_common_ownership_doc_ranges_contain_function_anchors() -> None:
for path, start, end, anchors in documented_setup_rows(setup_common_doc()):
script = REPO_ROOT / "cli" / "bash" / "commands" / "basectl" / "subcommands" / path
lines = script.read_text(encoding="utf-8").splitlines()
assert end <= len(lines), f"{path} range ends at {end}, but the file has {len(lines)} lines"
function_lines = {
name: line_number
for line_number, line in enumerate(lines, start=1)
for name in re.findall(r"^([A-Za-z_][A-Za-z0-9_]*)\(\) \{", line)
}
for anchor in anchors:
assert anchor in function_lines, f"{anchor} is not defined in {path}"
assert start <= function_lines[anchor] <= end, (
f"{anchor} is at line {function_lines[anchor]} outside {path} {start}-{end}"
)


def test_setup_common_sources_linux_debian_helper() -> None:
common_source = setup_common_script()
linux_debian_source = SETUP_LINUX_DEBIAN_SCRIPT.read_text(encoding="utf-8")
Expand Down
Loading