Skip to content
Open
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
50 changes: 46 additions & 4 deletions script/install_github_packages
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,18 @@ install_bin() {
name="$(basename "${repo}")"
fi

local install_location="$HOME/.local/bin"
local target_path="${install_location}/${name}"

# Check if already installed
if [[ -f "${target_path}" ]]; then
echo "${name} already installed at ${target_path}, skipping"
return 0
fi

local url
url="$(get_latest_release_url "${repo}" "${match}")"

local install_location="$HOME/.local/bin"
mkdir -p "${install_location}"
echo -n "Downloading ${name} ... "
wget --quiet -O "${install_location}/${name}" "${url}"
Expand Down Expand Up @@ -179,11 +187,18 @@ install_tarball() {
name="$(basename "${repo}")"
fi

local install_location="$HOME/.local/bin"
local target_path="${install_location}/${name}"

# Check if already installed
if [[ -f "${target_path}" ]]; then
echo "${name} already installed at ${target_path}, skipping"
return 0
fi

local url
url="$(get_latest_release_url "${repo}" "${match}")"

local install_location="$HOME/.local/bin"

local download_dir
download_dir="$(mktemp --directory "${name}"XXX)"

Expand Down Expand Up @@ -217,10 +232,18 @@ install_zip() {
local name
name="$(basename "${repo}")"

local install_location="$HOME/.local/bin"
local target_path="${install_location}/${name}"

# Check if already installed
if [[ -f "${target_path}" ]]; then
echo "${name} already installed at ${target_path}, skipping"
return 0
fi

local url
url="$(get_latest_release_url "${repo}" "${match}")"

local install_location="$HOME/.local/bin"
mkdir -p "${install_location}"

local download_dir
Expand Down Expand Up @@ -322,6 +345,13 @@ install_lua_language_server() {
local name="lua-language-server"

local install_location="$HOME/.local/share/lua-language-server"

# Check if already installed
if [[ -x "${install_location}/bin/lua-language-server" ]]; then
echo "${name} already installed at ${install_location}, skipping"
return 0
fi

mkdir -p "${install_location}"

local download_dir
Expand Down Expand Up @@ -349,6 +379,12 @@ install_neovim_stable() {
local download_dir
local install_prefix="$HOME/.local"

# Check if already installed
if [[ -f "${vim_path}" ]]; then
echo "neovim already installed at ${vim_path}, skipping"
return 0
fi

echo "Installing neovim stable ..."
url="$(get_latest_release_url "neovim/neovim" "nvim-linux-{ARCH}.tar.gz")"

Expand Down Expand Up @@ -406,6 +442,12 @@ install_yazi() {

local bin_location="$HOME/.local/bin"
local completion_location="$HOME/.local/completion.d/"

# Check if already installed
if [[ -f "${bin_location}/yazi" ]] && [[ -f "${bin_location}/ya" ]]; then
echo "yazi already installed at ${bin_location}, skipping"
return 0
fi

local download_dir
download_dir="$(mktemp --directory yaziXXX)"
Expand Down
50 changes: 38 additions & 12 deletions script/install_node_packages
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,23 @@ install_node_packages() {
exit 1
fi

echo "Installing node LTS …"

# Use --arch arm64 when running on aarch64, otherwise fnm doesn't
# auto-detect (happens on Docker on MacOS)
if [[ "$(uname -m)" == "aarch64" ]]; then
fnm install --lts --arch arm64
# Check if node LTS is already installed
if fnm list | grep -qE "\(lts/|lts-latest"; then
echo "Node LTS already installed, skipping"
else
fnm install --lts
fi
echo "Installing node LTS …"

echo "Setting node version to LTS …"
fnm default lts-latest
# Use --arch arm64 when running on aarch64, otherwise fnm doesn't
# auto-detect (happens on Docker on MacOS)
if [[ "$(uname -m)" == "aarch64" ]]; then
fnm install --lts --arch arm64
else
fnm install --lts
fi

echo "Setting node version to LTS …"
fnm default lts-latest
fi

# Make sure to pick up necessary env vars
eval "$(fnm env)"
Expand All @@ -71,8 +76,29 @@ install_node_packages() {
# Corepack may need to download pnpm the first time
corepack prepare pnpm@latest --activate

echo "Installing global node packages …"
pnpm add --global "${PACKAGES[@]}"
# Check if packages are already installed
local missing_packages=()
# Get a flat list of globally installed packages once to be efficient
# Redirect stderr to avoid errors if no packages are installed, and use `|| true` to prevent script exit
local installed_list
installed_list="$(pnpm list --global --depth=0 2>/dev/null || true)"

for package in "${PACKAGES[@]}"; do
# Extract package name, removing only the version specifier (keep scope)
local package_name_no_version="${package%%@*}"

# Check for an exact match at the beginning of a line, followed by a space
if ! echo "${installed_list}" | grep -q -E "^${package_name_no_version} "; then
missing_packages+=("${package}")
fi
done
Comment on lines 80 to 94

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current logic for checking installed node packages is inefficient and can be unreliable.

  1. Inefficiency: pnpm list --global is executed inside the for loop, which is inefficient for a large number of packages.
  2. Incorrect Package Name: Stripping the scope from packages (e.g., @foo/bar becomes bar) with local package_name="${package#*/}" is incorrect and can lead to name collisions.
  3. False Positives: The grep command can match substrings, leading to false positives. For example, checking for rome could incorrectly match an installed package named chrome.

A more robust approach is to fetch the list of packages once, not strip the scope, and use a more precise grep to match the full package name at the beginning of a line.

  local missing_packages=()
  # Get a flat list of globally installed packages once to be efficient.
  # Redirect stderr to avoid errors if no packages are installed, and use `|| true` to prevent script exit.
  local installed_list
  installed_list="$(pnpm list --global --depth=0 2>/dev/null || true)"

  for package in "${PACKAGES[@]}"; do
    # Extract package name, removing only the version specifier.
    local package_name_no_version="${package%%@*}"

    # Check for an exact match at the beginning of a line, followed by a space.
    if ! echo "${installed_list}" | grep -q -E "^${package_name_no_version} "; then
      missing_packages+=("${package}")
    fi
  done


if [[ ${#missing_packages[@]} -gt 0 ]]; then
echo "Installing missing global node packages …"
pnpm add --global "${missing_packages[@]}"
else
echo "All node packages already installed, skipping"
fi
echo "Node packages installed!"
}

Expand Down
36 changes: 31 additions & 5 deletions script/stow
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,42 @@ main() {
local dotfiles_root
dotfiles_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
local stowed_files_path="${dotfiles_root}/stowed-files"
local target_dir="${HOME}"

# Check if stow command is available
if ! command -v stow >/dev/null 2>&1; then
>&2 echo "Error: stow command not found. Please install GNU Stow first."
return 1
fi

# Validate stowed-files directory
if [[ ! -d "${stowed_files_path}" ]]; then
>&2 echo "Warning: stowed-files directory not found at ${stowed_files_path}, skipping stow"
>&2 echo "Error: stowed-files directory not found at ${stowed_files_path}"
return 1
fi

# Validate target directory
if [[ ! -d "${target_dir}" ]]; then
>&2 echo "Error: target directory not found at ${target_dir}"
return 1
fi

# Get list of packages to stow (basenames only)
local packages=()
while IFS= read -r -d '' package; do
packages+=("${package}")
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The find command returns full paths (e.g., /path/to/stowed-files/package1), but stow expects package names (basenames) relative to the --dir argument. This will cause stow to fail because it won't be able to find packages with full paths.

Fix by extracting the basename:

while IFS= read -r -d '' package; do
  packages+=("$(basename "${package}")")
done < <(find "${stowed_files_path}" -mindepth 1 -maxdepth 1 -type d -print0)
Suggested change
packages+=("${package}")
packages+=("$(basename "${package}")")

Copilot uses AI. Check for mistakes.
done < <(find "${stowed_files_path}" -mindepth 1 -maxdepth 1 -type d -printf '%f\0')

if [[ ${#packages[@]} -eq 0 ]]; then
>&2 echo "Warning: no packages found in ${stowed_files_path}, nothing to stow"
return 0
fi

# Purposefully allow word splitting on $@ to pass through flags
# shellcheck disable=SC2046,SC2068
stow --dir="${stowed_files_path}/" --target="$HOME" \
$(ls "${stowed_files_path}") ${@}
echo "Stowing packages: ${packages[*]} to ${target_dir}"

# Use array to safely pass package names and additional arguments
stow --dir="${stowed_files_path}/" --target="${target_dir}" \
"${packages[@]}" "${@}"
}

main "${@}"
Loading