mirror of
https://github.com/jimeh/dotfiles.git
synced 2026-02-19 09:26:42 +00:00
feat(cursor/setup): refactor setup script and add support for Windsurf
This commit is contained in:
278
cursor/setup.sh
278
cursor/setup.sh
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user