feat(cursor/setup): refactor setup script and add support for Windsurf

This commit is contained in:
2025-03-09 00:16:18 +00:00
parent 27a3193c01
commit 4bd7fbcc19

View File

@@ -28,22 +28,28 @@ get_extensions_lock() {
show_help() { show_help() {
cat << EOF cat << EOF
Usage: $(basename "$0") EDITOR COMMAND Usage: $(basename "$0") EDITOR COMMAND [OPTIONS]
Editors: Editors:
cursor Cursor editor cursor, c Cursor editor
vscode, vsc Visual Studio Code vscode, code, vsc, v Visual Studio Code
vscode-insiders, vsci Visual Studio Code Insiders vscode-insiders, vsci, i Visual Studio Code Insiders
windsurf, surf, w Windsurf editor
Commands: Commands:
config, conf Create symlinks for editor config files config, conf Create symlinks for editor config files
dump-extensions, dump Export installed editor extensions to extensions.txt dump-extensions, dump Export installed editor extensions to extensions.txt
extensions, ext Install editor extensions from 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: Description:
This script manages editor configuration files and extensions. This script manages editor configuration files and extensions.
It can create symlinks for settings, keybindings, and snippets, 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 EOF
} }
@@ -65,6 +71,9 @@ editor_config_dir() {
"vscode-insiders") "vscode-insiders")
echo "${HOME}/Library/Application Support/Code - Insiders/User" echo "${HOME}/Library/Application Support/Code - Insiders/User"
;; ;;
"windsurf")
echo "${HOME}/Library/Application Support/Windsurf/User"
;;
*) *)
echo "Error: Invalid editor '${SETUP_EDITOR}' for macOS" echo "Error: Invalid editor '${SETUP_EDITOR}' for macOS"
exit 1 exit 1
@@ -82,6 +91,9 @@ editor_config_dir() {
"vscode-insiders") "vscode-insiders")
echo "${HOME}/.config/Code - Insiders/User" echo "${HOME}/.config/Code - Insiders/User"
;; ;;
"windsurf")
echo "${HOME}/.config/Windsurf/User"
;;
*) *)
echo "Error: Invalid editor '${SETUP_EDITOR}' for Linux" echo "Error: Invalid editor '${SETUP_EDITOR}' for Linux"
exit 1 exit 1
@@ -140,34 +152,40 @@ do_symlink() {
# Find the editor CLI command # Find the editor CLI command
find_editor_cmd() { find_editor_cmd() {
local editor_cmd="" local editor_cmd=""
local possible_commands=()
case "${SETUP_EDITOR}" in case "${SETUP_EDITOR}" in
"cursor") "cursor")
# Check for cursor CLI in multiple possible locations # Set possible Cursor CLI command locations
for cmd in "cursor" "/Applications/Cursor.app/Contents/Resources/app/bin/cursor" "${HOME}/Applications/Cursor.app/Contents/Resources/app/bin/cursor"; do possible_commands=(
if command -v "${cmd}" > /dev/null 2>&1; then "cursor"
editor_cmd="${cmd}" "/Applications/Cursor.app/Contents/Resources/app/bin/cursor"
break "${HOME}/Applications/Cursor.app/Contents/Resources/app/bin/cursor"
fi )
done
;; ;;
"vscode") "vscode")
# Check for VSCode CLI in multiple possible locations # Set possible VSCode CLI command 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 possible_commands=(
if command -v "${cmd}" > /dev/null 2>&1; then "code"
editor_cmd="${cmd}" "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
break "${HOME}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
fi )
done
;; ;;
"vscode-insiders") "vscode-insiders")
# Check for VSCode Insiders CLI in multiple possible locations # Set possible VSCode Insiders CLI command 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 possible_commands=(
if command -v "${cmd}" > /dev/null 2>&1; then "code-insiders"
editor_cmd="${cmd}" "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"
break "${HOME}/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"
fi )
done ;;
"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}'" echo "Error: Invalid editor '${SETUP_EDITOR}'"
@@ -175,6 +193,15 @@ find_editor_cmd() {
;; ;;
esac 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 if [[ -z "${editor_cmd}" ]]; then
echo "Error: ${SETUP_EDITOR} command not found" >&2 echo "Error: ${SETUP_EDITOR} command not found" >&2
exit 1 exit 1
@@ -219,6 +246,132 @@ is_extension_installed() {
echo "${_INSTALLED_EXTENSIONS}" | grep -q "^${extension}@" 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 # Install extensions from extensions.lock
do_install_extensions() { do_install_extensions() {
local editor_cmd local editor_cmd
@@ -226,6 +379,7 @@ do_install_extensions() {
local extensions_cache_dir="${SCRIPT_DIR}/cache/extensions" local extensions_cache_dir="${SCRIPT_DIR}/cache/extensions"
local extensions_lock local extensions_lock
extensions_lock="$(get_extensions_lock)" extensions_lock="$(get_extensions_lock)"
local use_latest="${1:-false}"
if [[ ! -f "${extensions_lock}" ]]; then if [[ ! -f "${extensions_lock}" ]]; then
echo "Error: ${extensions_lock} not found" echo "Error: ${extensions_lock} not found"
@@ -244,43 +398,17 @@ do_install_extensions() {
continue continue
fi fi
# For VSCode and VSCode Insiders we can install directly from the marketplace # For Cursor we need to download and install from .vsix file, as
if [[ "${SETUP_EDITOR}" == "vscode" || "${SETUP_EDITOR}" == "vscode-insiders" ]]; then # installation via ID fails with a signature verification error.
echo "Installing ${extension}@${version}" if [[ "${SETUP_EDITOR}" == "cursor" ]]; then
if ! "${editor_cmd}" --install-extension "${extension}@${version}"; then install_extension_via_vsix "${editor_cmd}" "${extension}" "${version}" "${use_latest}" "${extensions_cache_dir}"
echo "Warning: Failed to install ${extension}@${version}"
fi
continue continue
fi fi
# For Cursor we need to download and install from .vsix file if ! install_extension_direct "${editor_cmd}" "${extension}" "${version}" "${use_latest}"; then
local vsix_path="${extensions_cache_dir}/${extension}@${version}.vsix" echo "Direct installation failed, trying .vsix download method..."
install_extension_via_vsix "${editor_cmd}" "${extension}" "${version}" "${use_latest}" "${extensions_cache_dir}"
# 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
fi 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 fi
done < "${extensions_lock}" done < "${extensions_lock}"
@@ -318,15 +446,37 @@ main() {
"cursor" | "c") "cursor" | "c")
SETUP_EDITOR="cursor" SETUP_EDITOR="cursor"
;; ;;
"windsurf" | "wind" | "surf" | "w")
SETUP_EDITOR="windsurf"
;;
*) *)
echo "Error: Unsupported editor '${editor}'" 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 exit 1
;; ;;
esac esac
# Get command from second argument # Get command from second argument
local command="${2}" 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 # Handle commands
case "${command}" in case "${command}" in
@@ -337,7 +487,7 @@ main() {
do_dump_extensions do_dump_extensions
;; ;;
"extensions" | "ext") "extensions" | "ext")
do_install_extensions do_install_extensions "${use_latest}"
;; ;;
"") "")
echo "Error: No command provided" echo "Error: No command provided"