#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) EXT_FILE="${SCRIPT_DIR}/extensions.txt" TARGET="remote" FORCE=0 usage() { cat <<'EOF' Usage: install-vscode-extensions.sh [options] Install VS Code extensions from extensions.txt. Options: --local Install to Windows VS Code only --remote Install to WSL remote VS Code (default) --both Install to both local Windows and WSL remote --list Print installed extension IDs (remote by default) --export Write installed IDs to extensions.txt --file PATH Use a different extension list file --force Reinstall even if already present -h, --help Show this help Examples: bash install-vscode-extensions.sh bash install-vscode-extensions.sh --both bash install-vscode-extensions.sh --list bash install-vscode-extensions.sh --export EOF } while [[ $# -gt 0 ]]; do case "$1" in --local) TARGET="local" ;; --remote) TARGET="remote" ;; --both) TARGET="both" ;; --list) TARGET="list" ;; --export) TARGET="export" ;; --file) shift; EXT_FILE="${1:?missing path for --file}" ;; --force) FORCE=1 ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;; esac shift done find_code_cli() { if command -v code >/dev/null 2>&1; then command -v code return 0 fi local candidate for candidate in /mnt/c/Users/*/AppData/Local/Programs/Microsoft\ VS\ Code/bin/code; do if [[ -f "$candidate" ]]; then echo "$candidate" return 0 fi done return 1 } remote_args() { if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then printf '%s\0%s\0' --remote "wsl+${WSL_DISTRO_NAME,,}" fi } run_code() { "$CODE_CLI" "$@" } list_extensions() { local scope=$1 local -a args=(--list-extensions) if [[ "$scope" == "remote" ]]; then local -a remote=() while IFS= read -r -d '' arg; do remote+=("$arg") done < <(remote_args) if [[ ${#remote[@]} -eq 0 ]]; then echo "No WSL distro detected for remote listing." >&2 return 1 fi args+=("${remote[@]}") fi run_code "${args[@]}" } install_extensions() { local scope=$1 local -a remote=() if [[ "$scope" == "remote" ]]; then while IFS= read -r -d '' arg; do remote+=("$arg") done < <(remote_args) if [[ ${#remote[@]} -eq 0 ]]; then echo "No WSL distro detected; skipping remote install." >&2 return 0 fi fi local line id installed=0 skipped=0 failed=0 while IFS= read -r line || [[ -n "$line" ]]; do line="${line%%#*}" line="${line//[[:space:]]/}" [[ -z "$line" ]] && continue id="$line" if [[ "$FORCE" -eq 0 ]] && list_extensions "$scope" | grep -Fxq "$id"; then echo "skip [$scope] $id" skipped=$((skipped + 1)) continue fi echo "install [$scope] $id" if [[ "$scope" == "remote" ]]; then if run_code --install-extension "$id" "${remote[@]}" --force; then installed=$((installed + 1)) else echo "failed [$scope] $id" >&2 failed=$((failed + 1)) fi elif run_code --install-extension "$id" --force; then installed=$((installed + 1)) else echo "failed [$scope] $id" >&2 failed=$((failed + 1)) fi done < "$EXT_FILE" echo "done [$scope]: installed=$installed skipped=$skipped failed=$failed" [[ "$failed" -eq 0 ]] } CODE_CLI=$(find_code_cli) || { echo "VS Code CLI not found. Install VS Code and ensure 'code' is on PATH." >&2 exit 1 } case "$TARGET" in list) list_extensions remote ;; export) list_extensions remote > "$EXT_FILE" echo "Wrote $(wc -l < "$EXT_FILE" | tr -d ' ') extensions to $EXT_FILE" ;; local) install_extensions local ;; remote) install_extensions remote ;; both) install_extensions local install_extensions remote ;; esac