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
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,12 @@ to install them and `basectl check --dev` or `basectl doctor --dev` to verify
them.

On macOS, `basectl setup` sends a best-effort notification when setup completes
or fails. Notifications are skipped during `--dry-run` and never change the
setup exit status. Use `basectl setup --no-notify` or
`BASE_SETUP_NOTIFY=false` to disable them.
or fails after running for at least 30 seconds. Notifications are skipped during
`--dry-run` and never change the setup exit status. Use `basectl setup --notify`
to force a notification for quick runs, `basectl setup --no-notify` or
`BASE_SETUP_NOTIFY=false` to disable notifications, and
`BASE_SETUP_NOTIFY_MIN_SECONDS` to tune the default threshold. When `--notify`
is requested on macOS, Base warns if `osascript` is not available.

## Quick Start

Expand Down
3 changes: 3 additions & 0 deletions cli/bash/commands/basectl/subcommands/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Options:
--dev Install manifest-declared developer prerequisites.
--dry-run Log what would happen without making changes.
--manifest <path> Use a specific base_manifest.yaml path.
--notify Force a best-effort macOS notification when setup ends.
--no-notify Disable the default best-effort macOS completion notification.
--recreate-venv Back up and recreate the project virtual environment.
-v Enable DEBUG logging for this subcommand.
Expand Down Expand Up @@ -100,6 +101,8 @@ base_setup_subcommand_main() {
done

BASE_SETUP_PROJECT_NAME="$project_name"
BASE_SETUP_START_TIME="$(date +%s)"
export BASE_SETUP_START_TIME
export BASE_SETUP_PROJECT_NAME
log_debug "Running 'basectl setup' (DRY_RUN=$(setup_is_dry_run && printf true || printf false))."
if setup_notifications_enabled; then
Expand Down
29 changes: 28 additions & 1 deletion cli/bash/commands/basectl/subcommands/setup_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,17 @@ setup_enable_recreate_venv() {

setup_enable_notifications() {
export BASE_SETUP_NOTIFY=true
export BASE_SETUP_NOTIFY_FORCE=true
}

setup_disable_notifications() {
export BASE_SETUP_NOTIFY=false
}

setup_notify_min_seconds() {
printf '%s\n' "${BASE_SETUP_NOTIFY_MIN_SECONDS:-30}"
}

setup_recreate_venv_enabled() {
[[ "${BASE_SETUP_RECREATE_VENV:-false}" == true ]]
}
Expand All @@ -64,6 +69,10 @@ setup_notifications_enabled() {
[[ "${BASE_SETUP_NOTIFY:-true}" == true ]]
}

setup_notifications_forced() {
[[ "${BASE_SETUP_NOTIFY_FORCE:-false}" == true ]]
}

setup_virtualenv_exists() {
local venv_dir

Expand Down Expand Up @@ -153,12 +162,30 @@ setup_recovery_project_layer() {

setup_notify_completion() {
local exit_code="$1"
local elapsed_seconds=0
local message title
local min_seconds

setup_notifications_enabled || return 0
setup_is_dry_run && return 0
[[ "$OSTYPE" == darwin* ]] || return 0
command -v osascript >/dev/null 2>&1 || return 0
if ! command -v osascript >/dev/null 2>&1; then
if setup_notifications_forced; then
log_warn "Setup notification was requested, but 'osascript' is not available on this Mac."
fi
return 0
fi

min_seconds="$(setup_notify_min_seconds)"
if ! [[ "$min_seconds" =~ ^[0-9]+$ ]]; then
min_seconds=30
fi
if [[ -n "${BASE_SETUP_START_TIME:-}" && "$BASE_SETUP_START_TIME" =~ ^[0-9]+$ ]]; then
elapsed_seconds=$(($(date +%s) - BASE_SETUP_START_TIME))
fi
if ! setup_notifications_forced && ((elapsed_seconds < min_seconds)); then
return 0
fi

if ((exit_code == 0)); then
title="Base setup complete"
Expand Down
57 changes: 54 additions & 3 deletions cli/bash/commands/basectl/tests/setup.bats
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ EOF
[[ "$output" == *"Usage:"* ]]
[[ "$output" == *"basectl setup [options]"* ]]
[[ "$output" == *"--dev"* ]]
[[ "$output" == *"--notify"* ]]
[[ "$output" == *"--no-notify"* ]]
[[ "$output" == *"--recreate-venv"* ]]
[[ "$output" == *"Prepare the local Base CLI environment on macOS."* ]]
Expand Down Expand Up @@ -547,6 +548,7 @@ EOF
installer="$(create_homebrew_installer_stub)"

run_base_command \
BASE_SETUP_NOTIFY_MIN_SECONDS=999999 \
BASE_SETUP_ALLOW_NONINTERACTIVE_XCODE_INSTALL=true \
BASE_SETUP_HOMEBREW_INSTALLER_SCRIPT="$installer" \
setup
Expand All @@ -571,7 +573,7 @@ EOF
[ -f "$venv_dir/pyvenv.cfg" ]
}

@test "basectl setup sends a best-effort success notification by default" {
@test "basectl setup skips notifications for quick successful runs" {
local installer

create_xcode_stubs
Expand All @@ -583,12 +585,61 @@ EOF
BASE_SETUP_HOMEBREW_INSTALLER_SCRIPT="$installer" \
setup

[ "$status" -eq 0 ]
[ ! -f "$TEST_STATE_DIR/osascript-args" ]
}

@test "basectl setup sends a best-effort success notification after the threshold" {
local installer

create_xcode_stubs
create_osascript_stub
installer="$(create_homebrew_installer_stub)"

run_base_command \
BASE_SETUP_NOTIFY_MIN_SECONDS=0 \
BASE_SETUP_ALLOW_NONINTERACTIVE_XCODE_INSTALL=true \
BASE_SETUP_HOMEBREW_INSTALLER_SCRIPT="$installer" \
setup

[ "$status" -eq 0 ]
[ -f "$TEST_STATE_DIR/osascript-args" ]
[[ "$(cat "$TEST_STATE_DIR/osascript-args")" == *"Base setup complete"* ]]
[[ "$(cat "$TEST_STATE_DIR/osascript-args")" == *"Base CLI setup completed successfully."* ]]
}

@test "basectl setup --notify forces a notification for quick runs" {
local installer

create_xcode_stubs
create_osascript_stub
installer="$(create_homebrew_installer_stub)"

run_base_command \
BASE_SETUP_ALLOW_NONINTERACTIVE_XCODE_INSTALL=true \
BASE_SETUP_HOMEBREW_INSTALLER_SCRIPT="$installer" \
setup --notify

[ "$status" -eq 0 ]
[ -f "$TEST_STATE_DIR/osascript-args" ]
[[ "$(cat "$TEST_STATE_DIR/osascript-args")" == *"Base setup complete"* ]]
[[ "$(cat "$TEST_STATE_DIR/osascript-args")" == *"Base CLI setup completed successfully."* ]]
}

@test "basectl setup --notify warns when osascript is unavailable on macOS" {
run env \
HOME="$TEST_HOME" \
PATH="$TEST_MOCKBIN:$TEST_BASH_BIN_DIR:/bin:/usr/sbin:/sbin" \
OSTYPE=darwin24 \
BASE_HOME="$BASE_REPO_ROOT" \
BASE_SETUP_NOTIFY=true \
BASE_SETUP_NOTIFY_FORCE=true \
bash -c 'source "$BASE_HOME/lib/bash/std/lib_std.sh"; source "$BASE_HOME/cli/bash/commands/basectl/subcommands/setup_common.sh"; setup_notify_completion 0'

[ "$status" -eq 0 ]
[[ "$output" == *"Setup notification was requested, but 'osascript' is not available on this Mac."* ]]
}

@test "basectl setup forwards project setup arguments through the Base venv" {
local base_venv_dir="$TEST_HOME/.base.d/base/.venv"
local demo_venv_dir="$TEST_HOME/.base.d/demo/.venv"
Expand Down Expand Up @@ -673,13 +724,13 @@ EOF
[[ "$output" == *"Run 'xcode-select --install' in an interactive terminal, complete the installer, then rerun 'basectl setup'."* ]]
}

@test "basectl setup sends a best-effort failure notification by default" {
@test "basectl setup sends a best-effort failure notification after the threshold" {
create_brew_stub
create_xcode_stubs
create_osascript_stub
touch "$TEST_STATE_DIR/python-installed"

run_base_command setup
run_base_command BASE_SETUP_NOTIFY_MIN_SECONDS=0 setup

[ "$status" -eq 1 ]
[ -f "$TEST_STATE_DIR/osascript-args" ]
Expand Down
Loading