diff --git a/cursor/setup.sh b/cursor/setup.sh index 51c3d4a..70a98cd 100755 --- a/cursor/setup.sh +++ b/cursor/setup.sh @@ -28,22 +28,28 @@ get_extensions_lock() { show_help() { cat << EOF -Usage: $(basename "$0") EDITOR COMMAND +Usage: $(basename "$0") EDITOR COMMAND [OPTIONS] Editors: - cursor Cursor editor - vscode, vsc Visual Studio Code - vscode-insiders, vsci Visual Studio Code Insiders + cursor, c Cursor editor + vscode, code, vsc, v Visual Studio Code + vscode-insiders, vsci, i Visual Studio Code Insiders + windsurf, surf, w Windsurf editor Commands: - config, conf Create symlinks for editor config files - dump-extensions, dump Export installed editor extensions to extensions.txt - extensions, ext Install editor extensions from extensions.txt + config, conf Create symlinks for editor config files + dump-extensions, dump Export installed editor extensions to extensions.txt + extensions, ext Install editor extensions from extensions.txt + +Options: + --latest When used with extensions command, installs the + latest version of each extension instead of the + exact version from the lock file Description: This script manages editor configuration files and extensions. It can create symlinks for settings, keybindings, and snippets, - as well as backup and restore extensions. + as well as dump extension lock files and install extensions from them. EOF } @@ -65,6 +71,9 @@ editor_config_dir() { "vscode-insiders") echo "${HOME}/Library/Application Support/Code - Insiders/User" ;; + "windsurf") + echo "${HOME}/Library/Application Support/Windsurf/User" + ;; *) echo "Error: Invalid editor '${SETUP_EDITOR}' for macOS" exit 1 @@ -82,6 +91,9 @@ editor_config_dir() { "vscode-insiders") echo "${HOME}/.config/Code - Insiders/User" ;; + "windsurf") + echo "${HOME}/.config/Windsurf/User" + ;; *) echo "Error: Invalid editor '${SETUP_EDITOR}' for Linux" exit 1 @@ -140,34 +152,40 @@ do_symlink() { # Find the editor CLI command find_editor_cmd() { local editor_cmd="" + local possible_commands=() case "${SETUP_EDITOR}" in "cursor") - # Check for cursor CLI in multiple possible locations - for cmd in "cursor" "/Applications/Cursor.app/Contents/Resources/app/bin/cursor" "${HOME}/Applications/Cursor.app/Contents/Resources/app/bin/cursor"; do - if command -v "${cmd}" > /dev/null 2>&1; then - editor_cmd="${cmd}" - break - fi - done + # Set possible Cursor CLI command locations + possible_commands=( + "cursor" + "/Applications/Cursor.app/Contents/Resources/app/bin/cursor" + "${HOME}/Applications/Cursor.app/Contents/Resources/app/bin/cursor" + ) ;; "vscode") - # Check for VSCode CLI in multiple possible locations - for cmd in "code" "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" "${HOME}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"; do - if command -v "${cmd}" > /dev/null 2>&1; then - editor_cmd="${cmd}" - break - fi - done + # Set possible VSCode CLI command locations + possible_commands=( + "code" + "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" + "${HOME}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" + ) ;; "vscode-insiders") - # Check for VSCode Insiders CLI in multiple possible locations - for cmd in "code-insiders" "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code" "${HOME}/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"; do - if command -v "${cmd}" > /dev/null 2>&1; then - editor_cmd="${cmd}" - break - fi - done + # Set possible VSCode Insiders CLI command locations + possible_commands=( + "code-insiders" + "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code" + "${HOME}/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code" + ) + ;; + "windsurf") + # Set possible Windsurf CLI command locations + possible_commands=( + "windsurf" + "/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf" + "${HOME}/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf" + ) ;; *) echo "Error: Invalid editor '${SETUP_EDITOR}'" @@ -175,6 +193,15 @@ find_editor_cmd() { ;; esac + # Check for the command in all possible locations + for cmd in "${possible_commands[@]}"; do + echo "Checking ${cmd}" >&2 + if command -v "${cmd}" > /dev/null 2>&1; then + editor_cmd="${cmd}" + break + fi + done + if [[ -z "${editor_cmd}" ]]; then echo "Error: ${SETUP_EDITOR} command not found" >&2 exit 1 @@ -219,6 +246,132 @@ is_extension_installed() { echo "${_INSTALLED_EXTENSIONS}" | grep -q "^${extension}@" } +# Install an extension directly using the marketplace +install_extension_direct() { + local editor_cmd="$1" + local extension="$2" + local version="$3" + local use_latest="$4" + local result=0 + + if [[ "${use_latest}" == "true" ]]; then + echo "Installing ${extension} (latest version)" + if ! "${editor_cmd}" --install-extension "${extension}"; then + echo "Warning: Direct install failed for ${extension}" + result=1 + fi + else + echo "Installing ${extension}@${version}" + if ! "${editor_cmd}" --install-extension "${extension}@${version}"; then + echo "Warning: Direct install failed for ${extension}@${version}" + result=1 + fi + fi + + return ${result} +} + +# Install an extension via downloading .vsix file +install_extension_via_vsix() { + local editor_cmd="$1" + local extension="$2" + local version="$3" + local use_latest="$4" + local extensions_cache_dir="$5" + local result=0 + + local publisher_id="${extension%%.*}" + local extension_id="${extension#*.}" + local vsix_path="" + local vsix_url="" + local install_version="" + + if [[ "${use_latest}" == "true" ]]; then + # In latest mode, we need to first query the marketplace to get the latest version + echo "Finding latest version for ${extension}..." + + # Query the VS Marketplace API to get the extension metadata + local metadata_url="https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery" + local temp_metadata="${extensions_cache_dir}/metadata-${extension}.json" + + # Create extensions directory if it doesn't exist + mkdir -p "${extensions_cache_dir}" + + # Create a JSON request payload to query for the extension details + local request_data='{ + "filters": [ + { + "criteria": [ + { "filterType": 7, "value": "'${extension}'" } + ] + } + ], + "flags": 2 + }' + + # Query the marketplace for extension metadata + if ! curl --silent --compressed -X POST -H "Content-Type: application/json" -H "Accept: application/json; api-version=7.2-preview.1" -d "${request_data}" "${metadata_url}" > "${temp_metadata}"; then + echo "Warning: Failed to query metadata for ${extension}" + rm -f "${temp_metadata}" + return 1 + fi + + # Extract the latest version from the response + if command -v jq > /dev/null 2>&1; then + # If jq is available, use it to parse JSON + install_version=$(jq -r '.results[0].extensions[0].versions[0].version' "${temp_metadata}" 2> /dev/null) + else + # Fallback to grep/sed for basic extraction if jq is not available + install_version=$(grep -o '"version":"[^"]*"' "${temp_metadata}" | head -1 | sed 's/"version":"//;s/"//' 2> /dev/null) + fi + + # Clean up metadata file + rm -f "${temp_metadata}" + + # If we couldn't extract a version, use original version as fallback + if [[ -z "${install_version}" || "${install_version}" == "null" ]]; then + echo "Warning: Could not determine latest version, falling back to lock file version" + install_version="${version}" + else + echo "Latest version of ${extension} is ${install_version}" + fi + + # Set up the download path and URL for the specific version we found + vsix_path="${extensions_cache_dir}/${extension}@${install_version}.vsix" + vsix_url="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher_id}/vsextensions/${extension_id}/${install_version}/vspackage" + else + # In strict mode, use the exact version from the lock file + echo "Installing ${extension}@${version} via .vsix" + vsix_path="${extensions_cache_dir}/${extension}@${version}.vsix" + vsix_url="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher_id}/vsextensions/${extension_id}/${version}/vspackage" + install_version="${version}" + fi + + # Create extensions directory if it doesn't exist + mkdir -p "${extensions_cache_dir}" + + # Download the .vsix file + echo "Downloading ${extension}@${install_version}.vsix..." + echo " - URL: ${vsix_url}" + if ! curl --compressed -L -o "${vsix_path}" "${vsix_url}"; then + echo "Warning: Failed to download ${extension}@${install_version}.vsix" + rm -f "${vsix_path}" # Clean up potential partial downloads + return 1 + fi + + # Install the extension from .vsix file + echo "Installing extension from ${vsix_path}" + if ! "${editor_cmd}" --install-extension "${vsix_path}"; then + echo "Warning: Failed to install ${extension}@${install_version} from .vsix" + result=1 + fi + + # Clean up the .vsix file after installation attempt + rm -f "${vsix_path}" + + return ${result} +} + # Install extensions from extensions.lock do_install_extensions() { local editor_cmd @@ -226,6 +379,7 @@ do_install_extensions() { local extensions_cache_dir="${SCRIPT_DIR}/cache/extensions" local extensions_lock extensions_lock="$(get_extensions_lock)" + local use_latest="${1:-false}" if [[ ! -f "${extensions_lock}" ]]; then echo "Error: ${extensions_lock} not found" @@ -244,43 +398,17 @@ do_install_extensions() { continue fi - # For VSCode and VSCode Insiders we can install directly from the marketplace - if [[ "${SETUP_EDITOR}" == "vscode" || "${SETUP_EDITOR}" == "vscode-insiders" ]]; then - echo "Installing ${extension}@${version}" - if ! "${editor_cmd}" --install-extension "${extension}@${version}"; then - echo "Warning: Failed to install ${extension}@${version}" - fi + # For Cursor we need to download and install from .vsix file, as + # installation via ID fails with a signature verification error. + if [[ "${SETUP_EDITOR}" == "cursor" ]]; then + install_extension_via_vsix "${editor_cmd}" "${extension}" "${version}" "${use_latest}" "${extensions_cache_dir}" continue fi - # For Cursor we need to download and install from .vsix file - local vsix_path="${extensions_cache_dir}/${extension}@${version}.vsix" - - # Create extensions directory if it doesn't exist - mkdir -p "${extensions_cache_dir}" - - # If .vsix doesn't exist, download it - if [[ ! -f "${vsix_path}" ]]; then - local publisher_id="${extension%%.*}" - local extension_id="${extension#*.}" - local vsix_url="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher_id}/vsextensions/${extension_id}/${version}/vspackage" - - echo "Downloading ${extension}@${version}.vsix..." - echo " - URL: ${vsix_url}" - if ! curl --compressed -L -o "${vsix_path}" "${vsix_url}"; then - echo "Warning: Failed to download ${extension}@${version}.vsix" - continue - fi + if ! install_extension_direct "${editor_cmd}" "${extension}" "${version}" "${use_latest}"; then + echo "Direct installation failed, trying .vsix download method..." + install_extension_via_vsix "${editor_cmd}" "${extension}" "${version}" "${use_latest}" "${extensions_cache_dir}" fi - - # Install the extension from .vsix file - echo "Installing extension from ${vsix_path}" - if ! "${editor_cmd}" --install-extension "${vsix_path}"; then - echo "Warning: Failed to install ${extension}@${version}" - fi - - # Clean up the .vsix file after installation attempt - rm "${vsix_path}" fi done < "${extensions_lock}" @@ -318,15 +446,37 @@ main() { "cursor" | "c") SETUP_EDITOR="cursor" ;; + "windsurf" | "wind" | "surf" | "w") + SETUP_EDITOR="windsurf" + ;; *) echo "Error: Unsupported editor '${editor}'" - echo "Supported editors: cursor, vscode (vsc), vscode-insiders (vsci)" + echo "Supported editors: cursor, vscode (vsc), vscode-insiders (vsci), windsurf (wind)" exit 1 ;; esac # Get command from second argument local command="${2}" + shift 2 + + # Default values for options + local use_latest="false" + + # Parse additional options + while [[ $# -gt 0 ]]; do + case "$1" in + --latest) + use_latest="true" + shift + ;; + *) + echo "Error: Unknown option '$1'" + show_help + exit 1 + ;; + esac + done # Handle commands case "${command}" in @@ -337,7 +487,7 @@ main() { do_dump_extensions ;; "extensions" | "ext") - do_install_extensions + do_install_extensions "${use_latest}" ;; "") echo "Error: No command provided"