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
5 changes: 5 additions & 0 deletions lib/installers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ install_mise_tools() {

selfishell_mise_trust

if MISE_GLOBAL_CONFIG_FILE="$SELFISHELL_ROOT/config/shared/mise.toml" \
"$mise_command" -q install --dry-run-code "$@" >/dev/null 2>&1; then
return 0
fi

if ! MISE_GLOBAL_CONFIG_FILE="$SELFISHELL_ROOT/config/shared/mise.toml" "$mise_command" install "$@"; then
if [[ "$requirement" == "optional" ]]; then
cli_warn "Could not install $requirement mise tools: $*"
Expand Down
9 changes: 1 addition & 8 deletions lib/package_managers/apt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ apt_install_managed_packages() {
local dry_run="$2"
shift 2
local package
local installed_packages=()
local missing_packages=()
local available_packages=()
local unavailable_packages=()
Expand All @@ -46,17 +45,11 @@ apt_install_managed_packages() {
# one that was removed but not purged ("rc" status: config files remain,
# binaries gone) -- which would silently skip reinstalling it. Checking
# the actual Status field distinguishes that from a real "ii" install.
if dpkg-query -W -f='${Status}\n' "$package" 2>/dev/null | grep -q '^install ok installed$'; then
installed_packages+=("$package")
else
if ! dpkg-query -W -f='${Status}\n' "$package" 2>/dev/null | grep -q '^install ok installed$'; then
missing_packages+=("$package")
fi
done

if ((${#installed_packages[@]} > 0)); then
printf '%sAlready installed apt packages (%d):%s %s\n' "$SELFISHELL_COLOR_CYAN" "${#installed_packages[@]}" "$SELFISHELL_COLOR_RESET" "${installed_packages[*]}"
fi

((${#missing_packages[@]} > 0)) || return 0

if ((SELFISHELL_APT_UPDATED == 0)); then
Expand Down
10 changes: 1 addition & 9 deletions lib/package_managers/homebrew.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ homebrew_install_packages() {
local manager="$2"
local dry_run="$3"
local package
local installed_packages=()
local missing_packages=()
shift 3

Expand All @@ -76,18 +75,11 @@ homebrew_install_packages() {
fi

for package in "$@"; do
if homebrew_package_installed "$manager" "$package"; then
installed_packages+=("$package")
else
if ! homebrew_package_installed "$manager" "$package"; then
missing_packages+=("$package")
fi
done

if ((${#installed_packages[@]} > 0)); then
printf '%sAlready installed Homebrew %s (%d):%s %s\n' \
"$SELFISHELL_COLOR_CYAN" "$manager" "${#installed_packages[@]}" "$SELFISHELL_COLOR_RESET" "${installed_packages[*]}"
fi

((${#missing_packages[@]} > 0)) || return 0

if [[ "$manager" == "cask" ]]; then
Expand Down
29 changes: 27 additions & 2 deletions tests/installers_test.bash
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ source "$ROOT_DIR/lib/installers.sh"

NVIM_ARGUMENTS=""
MISE_ARGUMENTS=""
MISE_CALLS=()
MISE_CONFIG=""
GIT_ARGUMENTS=""
GIT_CALLS=()
NVIM_CALLS=()
FAKE_NVIM_PATH=""
NVIM_PLUGINS_VERIFIED=0
MOCK_MISE_DRY_RUN_CODE_EXIT=1

nvim() {
NVIM_ARGUMENTS="$*"
Expand All @@ -24,12 +26,15 @@ nvim() {

mise() {
MISE_ARGUMENTS="$*"
MISE_CALLS+=("$*")
MISE_CONFIG="$MISE_GLOBAL_CONFIG_FILE"
if [[ "$1" == "which" && "$2" == "nvim" ]]; then
printf '%s\n' "$FAKE_NVIM_PATH"
elif [[ "$1" == "exec" && "$2" == "--" ]]; then
shift 2
"$@"
elif [[ "$1" == "-q" && "$2" == "install" && "${3:-}" == "--dry-run-code" ]]; then
return "${MOCK_MISE_DRY_RUN_CODE_EXIT:-1}"
fi
}

Expand All @@ -53,19 +58,39 @@ verify_neovim_plugins() {
setup_installer_test() {
setup_test_home
export SELFISHELL_ROOT="$ROOT_DIR"
MOCK_MISE_DRY_RUN_CODE_EXIT=1
}

test_installs_declared_mise_tools_with_managed_config() {
# shellcheck disable=SC2034 # Read by install_mise_tools in the sourced module.
SELFISHELL_SKIPPED_OPTIONAL_PACKAGES=()
MISE_CALLS=()
MOCK_MISE_DRY_RUN_CODE_EXIT=1
install_mise_tools required 0 node@24.18.0 python@3.13.14

[[ "$MISE_ARGUMENTS" == 'install node@24.18.0 python@3.13.14' ]] ||
fail "mise tools were not installed together"
((${#MISE_CALLS[@]} == 2)) ||
fail "Expected preflight check followed by install, got: ${MISE_CALLS[*]}"
[[ "${MISE_CALLS[0]}" == '-q install --dry-run-code node@24.18.0 python@3.13.14' ]] ||
fail "mise preflight check was not called first: ${MISE_CALLS[0]}"
[[ "${MISE_CALLS[1]}" == 'install node@24.18.0 python@3.13.14' ]] ||
fail "mise tools were not installed together: ${MISE_CALLS[1]}"
[[ "$MISE_CONFIG" == "$ROOT_DIR/config/shared/mise.toml" ]] ||
fail "mise install did not use the Selfishell config"
}

test_skips_mise_install_when_tools_are_already_installed() {
# shellcheck disable=SC2034 # Read by install_mise_tools in the sourced module.
SELFISHELL_SKIPPED_OPTIONAL_PACKAGES=()
MISE_CALLS=()
MOCK_MISE_DRY_RUN_CODE_EXIT=0
install_mise_tools required 0 node@24.18.0 python@3.13.14

((${#MISE_CALLS[@]} == 1)) ||
fail "Expected only dry-run check call, got: ${MISE_CALLS[*]}"
[[ "${MISE_CALLS[0]}" == '-q install --dry-run-code node@24.18.0 python@3.13.14' ]] ||
fail "mise dry-run check was not called as expected: ${MISE_CALLS[0]}"
}

test_provisions_declared_zinit_plugins_without_loading_them() {
local manifest
local repository
Expand Down
23 changes: 23 additions & 0 deletions tests/package_adapters_test.bash
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,29 @@ test_apt_skips_index_update_when_packages_are_installed() {
[[ -z "$MOCK_INSTALLED_PACKAGES" ]] || fail "Installed apt packages were reinstalled"
}

test_apt_suppresses_already_installed_output() {
local output
reset_package_mocks
MOCK_DPKG_PACKAGES="first second"

output="$(apt_install_managed_packages required 0 first second)"

[[ -z "$output" ]] || fail "Installed apt packages produced output: $output"
}

test_homebrew_suppresses_already_installed_output() {
local output
reset_package_mocks
MOCK_BREW_FORMULAE="installed"
MOCK_BREW_CASKS="font-one"

output="$(homebrew_install_packages required formula 0 installed)"
[[ -z "$output" ]] || fail "Installed Homebrew formulae produced output: $output"

output="$(homebrew_install_packages optional cask 0 font-one)"
[[ -z "$output" ]] || fail "Installed Homebrew casks produced output: $output"
}

test_apt_non_root_requires_sudo() {
reset_package_mocks
MOCK_AVAILABLE_PACKAGES="available"
Expand Down