chore(siren): various refactoring and improvements

This commit is contained in:
2025-07-26 16:09:32 +01:00
parent 0dc9c48166
commit e7f533f0a9
2 changed files with 469 additions and 171 deletions

View File

@@ -1,10 +1,11 @@
# cursor Extensions
# Generated on Fri Jul 25 10:15:07 BST 2025
# Generated on Sat Jul 26 16:05:14 BST 2025
alefragnani.project-manager@12.8.0
anthropic.claude-code@1.0.60
anthropic.claude-code@1.0.61
antiantisepticeye.vscode-color-picker@0.0.4
antyos.openscad@1.3.2
anysphere.cursorpyright@1.0.7
arrterian.nix-env-selector@1.1.0
arturodent.command-alias@0.6.2
bibhasdn.unique-lines@1.0.0
@@ -24,25 +25,25 @@ connor4312.esbuild-problem-matchers@0.0.3
connorshea.vscode-ruby-test-adapter@0.9.2
ctf0.macros@1.1.1
davidanson.vscode-markdownlint@0.60.0
dbaeumer.vscode-eslint@3.0.10
dbaeumer.vscode-eslint@3.0.15
dewski.simplecov@0.0.7
dnut.rewrap-revived@1.16.3
dnut.rewrap-revived@17.9.0
editorconfig.editorconfig@0.17.4
elijah-potter.harper@0.52.0
emeraldwalk.runonsave@0.2.7
elijah-potter.harper@0.53.0
emeraldwalk.runonsave@0.3.2
esbenp.prettier-vscode@11.0.0
exiasr.hadolint@1.1.2
fogio.inline-go-struct-tags-syntax-highlight@1.0.0
github.remotehub@0.64.0
github.remotehub@0.65.2025063001
github.vscode-github-actions@0.27.2
github.vscode-pull-request-github@0.108.0
gofenix.go-lines@0.0.10
golang.go@0.48.0
golang.go@0.49.0
gruntfuggly.todo-tree@0.0.226
hashicorp.terraform@2.34.5
hashicorp.terraform@2.34.2025012311
hbenl.vscode-test-explorer@2.22.1
hoovercj.vscode-settings-cycler@1.0.1
humao.rest-client@0.26.0
humao.rest-client@0.25.1
hverlin.mise-vscode@0.52.0
jakearl.search-editor-apply-changes@0.1.1
jnoortheen.nix-ide@0.4.22
@@ -51,53 +52,54 @@ kahole.magit@0.6.67
karunamurti.haml@1.4.1
koichisasada.vscode-rdbg@0.2.2
letrieu.expand-region@0.1.4
lroolle.doom-themes@1.2.1
m4ns0ur.base64@1.0.0
mads-hartmann.bash-ide-vscode@1.43.0
matthewpi.caddyfile-support@0.4.0
mattn.lisp@0.1.12
mermaidchart.vscode-mermaid-chart@2.4.1
mermaidchart.vscode-mermaid-chart@2.5.0
mhutchie.git-graph@1.30.0
mkhl.direnv@0.17.0
mrmlnc.vscode-duplicate@1.2.1
ms-azuretools.vscode-containers@2.1.0
ms-kubernetes-tools.vscode-kubernetes-tools@1.3.25
ms-python.debugpy@2025.8.0
ms-python.debugpy@2025.11.2025072201
ms-python.python@2025.6.1
ms-python.vscode-pylance@2024.8.1
ms-vscode-remote.remote-containers@0.394.0
ms-vscode-remote.remote-ssh@0.113.1
ms-vscode-remote.remote-containers@0.424.0
ms-vscode-remote.remote-ssh@0.121.2025071515
ms-vscode-remote.remote-ssh-edit@0.87.0
ms-vscode.extension-test-runner@0.0.12
ms-vscode.hexeditor@1.11.1
ms-vscode.remote-explorer@0.5.0
ms-vscode.remote-repositories@0.42.0
ms-vscode.remote-server@1.5.2
ms-vscode.remote-explorer@0.6.2025072209
ms-vscode.remote-repositories@0.43.2025063001
ms-vscode.remote-server@1.6.2025072309
ms-vscode.test-adapter-converter@0.2.1
ms-vscode.vscode-speech@0.14.0
ms-vscode.vscode-speech@0.16.0
ms-vsliveshare.vsliveshare@1.0.5948
pflannery.vscode-versionlens@1.22.2
pkief.material-icon-theme@5.24.0
redhat.vscode-xml@0.29.0
redhat.vscode-xml@0.29.2025051008
redhat.vscode-yaml@1.18.0
romanpeshkov.vscode-text-tables@0.1.5
rrudi.vscode-dired@0.0.9
rust-lang.rust-analyzer@0.3.2547
rust-lang.rust-analyzer@0.4.2552
shopify.ruby-extensions-pack@0.1.13
shopify.ruby-lsp@0.9.31
sidneys1.gitconfig@2.0.1
sorbet.sorbet-vscode-extension@0.3.44
streetsidesoftware.code-spell-checker@4.0.47
streetsidesoftware.code-spell-checker@4.2.3
stuart.unique-window-colors@1.0.51
sumneko.lua@3.15.0
svelte.svelte-vscode@109.10.0
swellaby.vscode-rust-test-adapter@0.11.1
swellaby.vscode-rust-test-adapter@0.11.0
swellaby.workspace-config-plus@0.2.5
tamasfe.even-better-toml@0.21.2
tootone.org-mode@0.5.0
tuttieee.emacs-mcx@0.75.0
tuttieee.emacs-mcx@0.78.0
tyriar.sort-lines@1.12.0
viktorzetterstrom.non-breaking-space-highlighter@0.0.3
wenhoujx.swiper@2.1.2
zhuangtongfa.material-theme@3.19.0
ziyasal.vscode-open-in-github@1.4.1
ziyasal.vscode-open-in-github@1.3.6
zxh404.vscode-proto3@0.5.5

590
siren
View File

@@ -1,4 +1,4 @@
#! /usr/bin/env bash
#!/usr/bin/env bash
# ==============================================================================
# Settings
@@ -34,6 +34,8 @@ define_settings() {
}
# Get extensions lockfile path for current editor.
#
# Returns: Path to extensions lock file via `STDOUT`.
get_extensions_lock() {
echo "${SCRIPT_DIR}/extensions.${SETUP_EDITOR}.lock"
}
@@ -58,6 +60,8 @@ Commands:
config, conf Create symlinks for editor config files
dump-extensions, dump Export installed editor extensions to a lock file.
extensions, ext Install editor extensions from a lock file.
install Install a specific extension by identifier (e.g., ms-python.python).
If no version is specified, installs the latest version.
Options:
--latest When used with the extensions command, installs the
@@ -80,6 +84,8 @@ EOF
# ==============================================================================
# Determine editor config directory.
#
# Returns: Editor config directory path via `STDOUT`.
editor_config_dir() {
case "$(uname -s)" in
"Darwin")
@@ -136,6 +142,8 @@ editor_config_dir() {
}
# Determine harper-ls config directory.
#
# Returns: Harper-ls config directory path via `STDOUT`.
harper_config_dir() {
case "$(uname -s)" in
"Darwin")
@@ -156,6 +164,8 @@ harper_config_dir() {
}
# Cross-platform function to resolve symlinks.
#
# Returns: Resolved symlink path via `STDOUT`.
resolve_symlink() {
local path="$1"
if command -v realpath > /dev/null 2>&1; then
@@ -236,6 +246,8 @@ do_config() {
}
# Find the editor CLI command.
#
# Returns: Editor command path via `STDOUT`.
find_editor_cmd() {
local editor_cmd=""
local possible_commands=()
@@ -316,7 +328,7 @@ do_dump_extensions() {
echo "# ${SETUP_EDITOR} Extensions"
echo "# Generated on ${current_date}"
echo
"${editor_cmd}" --list-extensions --show-versions
"${editor_cmd}" --list-extensions --show-versions 2> /dev/null
} > "${extensions_lock}"
echo "Extensions list dumped to ${extensions_lock}"
@@ -372,26 +384,105 @@ validate_extension_line() {
_INSTALLED_EXTENSIONS=""
# Get installed extensions with versions, using cache if available.
#
# Returns: List of installed extensions with versions via `STDOUT`.
installed_extensions() {
local editor_cmd="$1"
# Populate the cache if it's not already populated.
if [[ -z "${_INSTALLED_EXTENSIONS}" ]]; then
_INSTALLED_EXTENSIONS="$("${editor_cmd}" --list-extensions --show-versions)"
_INSTALLED_EXTENSIONS="$(
"${editor_cmd}" --list-extensions --show-versions 2> /dev/null
)"
fi
echo "${_INSTALLED_EXTENSIONS}"
}
# Get the currently installed version of an extension.
#
# Returns: Version string of installed extension via `STDOUT` (empty if not installed).
get_installed_version() {
local editor_cmd="$1"
local extension="$2"
local extension_pattern
extension_pattern="$(printf '%s' "${extension}" | sed 's/[[\.*^()$+?{|]/\\&/g')"
# Extract version from cached list.
installed_extensions "${editor_cmd}" |
grep "^$(printf '%s' "${extension}" | sed "s/[[\.*^$()+?{|]/\\\\&/g")@" |
sed "s/^[^@]*@//"
grep "^${extension_pattern}@" |
sed 's/^[^@]*@//'
}
# Query latest version of an extension.
#
# Returns: Latest version string via `STDOUT` on success.
query_latest_version() {
local extension="$1"
if query_marketplace_latest_version "${extension}"; then
return 0
elif query_openvsx_latest_version "${extension}"; then
return 0
else
return 1
fi
}
# Query latest version from OpenVSX registry.
#
# Returns: Latest version string via `STDOUT` on success.
query_openvsx_latest_version() {
local extension="$1"
local publisher_id="${extension%%.*}"
local extension_id="${extension#*.}"
local openvsx_api_url="https://open-vsx.org/api/${publisher_id}/${extension_id}"
# Check for jq availability
if ! command -v jq > /dev/null 2>&1; then
return 1
fi
# Query OpenVSX API and extract latest version
if curl --silent --compressed "${openvsx_api_url}" 2> /dev/null |
jq -r '.version // empty' 2> /dev/null; then
return 0
else
return 1
fi
}
# Query latest version from VS Marketplace.
#
# Returns: Latest version string via `STDOUT` on success.
query_marketplace_latest_version() {
local extension="$1"
local metadata_url="https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery"
# Check for jq availability
if ! command -v jq > /dev/null 2>&1; then
return 1
fi
# Use jq to properly construct JSON
local request_data
request_data=$(jq -n --arg ext "$extension" '{
filters: [{
criteria: [{ filterType: 7, value: $ext }]
}],
flags: 2
}')
# Query the marketplace and extract latest version
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}" 2> /dev/null |
jq -r '.results[0].extensions[0].versions[0].version // empty' 2> /dev/null; then
return 0
else
return 1
fi
}
# Install an extension directly using the marketplace.
@@ -399,14 +490,13 @@ install_extension_direct() {
local editor_cmd="$1"
local extension="$2"
local version="$3"
local use_latest="$4"
local force_install="$5"
local force_install="$4"
local result=0
if [[ "${use_latest}" == "true" ]]; then
echo "Installing ${extension} (latest version)"
if ! "${editor_cmd}" --install-extension "${extension}" --force; then
echo "Warning: Direct install failed for ${extension}"
if [[ "${version}" == "latest" ]]; then
echo "Installing ${extension} (latest version)" >&2
if ! "${editor_cmd}" --install-extension "${extension}" --force 2> /dev/null; then
echo "Warning: Direct install failed for ${extension}" >&2
result=1
fi
else
@@ -415,8 +505,8 @@ install_extension_direct() {
if [[ "${force_install}" == "true" ]]; then
install_cmd+=(--force)
fi
if ! "${install_cmd[@]}"; then
echo "Warning: Direct install failed for ${extension}@${version}"
if ! "${install_cmd[@]}" 2> /dev/null; then
echo "Warning: Direct install failed for ${extension}@${version}" >&2
result=1
fi
fi
@@ -424,43 +514,89 @@ install_extension_direct() {
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
# Download extension from OpenVSX registry.
#
# Returns: Path to downloaded `.vsix` file via `STDOUT` on success.
download_from_openvsx() {
local extension="$1"
local version="$2"
local extensions_cache_dir="$3"
local publisher_id="${extension%%.*}"
local extension_id="${extension#*.}"
local install_version="${version}"
local vsix_path=""
local vsix_url=""
local install_version=""
if [[ "${use_latest}" == "true" ]]; then
# Check for `jq` availability when using `--latest` flag.
# If version is "latest", query OpenVSX API for latest version
if [[ "${version}" == "latest" ]]; then
echo "Querying OpenVSX for latest version of ${extension}..." >&2
if install_version=$(query_openvsx_latest_version "${extension}"); then
if [[ -z "${install_version}" ]]; then
echo "Error: Could not determine latest version from OpenVSX for ${extension}" >&2
return 1
fi
echo "Latest version of ${extension} from OpenVSX is ${install_version}" >&2
else
echo "Error: Failed to query OpenVSX API for ${extension}" >&2
return 1
fi
fi
# Set up download path and URL
vsix_path="${extensions_cache_dir}/${extension}@${install_version}.vsix"
local openvsx_url="https://open-vsx.org/api/${publisher_id}/${extension_id}/${install_version}/file/${publisher_id}.${extension_id}-${install_version}.vsix"
echo "Downloading ${extension}@${install_version} from OpenVSX..." >&2
echo " - OpenVSX URL: ${openvsx_url}" >&2
# Create extensions directory if it doesn't exist
mkdir -p "${extensions_cache_dir}"
if curl --compressed -L -o "${vsix_path}" "${openvsx_url}" 2> /dev/null; then
# Verify the download was successful by checking file size
if [[ -s "${vsix_path}" ]]; then
echo "Successfully downloaded from OpenVSX" >&2
echo "${vsix_path}"
return 0
else
echo "OpenVSX download failed (empty file)" >&2
rm -f "${vsix_path}"
return 1
fi
else
echo "OpenVSX download failed" >&2
rm -f "${vsix_path}"
return 1
fi
}
# Download extension from VS Marketplace.
#
# Returns: Path to downloaded `.vsix` file via `STDOUT` on success.
download_from_marketplace() {
local extension="$1"
local version="$2"
local extensions_cache_dir="$3"
local publisher_id="${extension%%.*}"
local extension_id="${extension#*.}"
local install_version="${version}"
local vsix_path=""
# If version is "latest", query VS Marketplace API for latest version
if [[ "${version}" == "latest" ]]; then
echo "Querying VS Marketplace for latest version of ${extension}..." >&2
local metadata_url="https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery"
local temp_metadata="${extensions_cache_dir}/marketplace-${extension}.json"
# Create extensions directory if it doesn't exist
mkdir -p "${extensions_cache_dir}"
# Check for jq availability
if ! command -v jq > /dev/null 2>&1; then
echo "Error: jq is required when using --latest flag to parse" \
"marketplace API responses."
echo "Please install jq or remove the --latest flag to use exact" \
"versions from the lock file."
echo "Error: jq is required to parse VS Marketplace API response for latest version" >&2
return 1
fi
# 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}"
# Use `jq` to properly construct JSON.
# Use jq to properly construct JSON
local request_data
request_data=$(jq -n --arg ext "$extension" '{
filters: [{
@@ -469,77 +605,193 @@ install_extension_via_vsix() {
flags: 2
}')
# Query the marketplace for extension metadata.
if ! curl --silent --compressed -X POST \
# 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}"
-d "${request_data}" "${metadata_url}" > "${temp_metadata}" 2> /dev/null; then
# Extract the latest version from the response using jq
install_version=$(
jq -r '.results[0].extensions[0].versions[0].version // empty' "${temp_metadata}" \
2> /dev/null
)
if [[ -z "${install_version}" || "${install_version}" == "null" ]]; then
echo "Error: Could not determine latest version from VS Marketplace for ${extension}" >&2
rm -f "${temp_metadata}"
return 1
fi
echo "Latest version of ${extension} from VS Marketplace is ${install_version}" >&2
else
echo "Error: Failed to query VS Marketplace API for ${extension}" >&2
rm -f "${temp_metadata}"
return 1
fi
# Extract the latest version from the response using `jq`.
install_version=$(
jq -r '.results[0].extensions[0].versions[0].version' "${temp_metadata}" \
2> /dev/null
)
# 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.
# Set up download path and URL
vsix_path="${extensions_cache_dir}/${extension}@${install_version}.vsix"
local marketplace_url="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher_id}/vsextensions/${extension_id}/${install_version}/vspackage"
echo "Downloading ${extension}@${install_version} from VS Marketplace..." >&2
echo " - Marketplace URL: ${marketplace_url}" >&2
# 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
if curl --compressed -L -o "${vsix_path}" "${marketplace_url}"; then
if [[ -s "${vsix_path}" ]]; then
echo "Successfully downloaded from VS Marketplace" >&2
echo "${vsix_path}"
return 0
else
echo "VS Marketplace download failed (empty file)" >&2
rm -f "${vsix_path}"
return 1
fi
else
echo "VS Marketplace download failed" >&2
rm -f "${vsix_path}"
return 1
fi
}
# Install the extension from `*.vsix` file.
# Note: Installing from `*.vsix` automatically overwrites existing versions.
echo "Installing extension from ${vsix_path}"
if ! "${editor_cmd}" --install-extension "${vsix_path}"; then
echo "Warning: Failed to install ${extension}@${install_version}" \
"from '*.vsix'"
# Try downloading extension from OpenVSX first, then fallback to official marketplace.
#
# Returns: Path to downloaded `.vsix` file via `STDOUT` on success.
download_extension_vsix() {
local extension="$1"
local version="$2"
local extensions_cache_dir="$3"
local downloaded_path=""
# Try VS Marketplace first
echo "Trying VS Marketplace for ${extension}@${version}..." >&2
if downloaded_path=$(download_from_marketplace "${extension}" "${version}" "${extensions_cache_dir}"); then
echo "${downloaded_path}"
return 0
fi
# Try OpenVSX second
echo "Trying OpenVSX for ${extension}@${version}..." >&2
if downloaded_path=$(download_from_openvsx "${extension}" "${version}" "${extensions_cache_dir}"); then
echo "${downloaded_path}"
return 0
fi
echo "Error: Failed to download ${extension}@${version} from both OpenVSX and VS Marketplace" >&2
return 1
}
# Install an extension via downloading `*.vsix` file.
install_extension_via_vsix() {
local editor_cmd="$1"
local extension="$2"
local version="$3"
local extensions_cache_dir="$4"
local result=0
local vsix_path=""
vsix_path="$(
download_extension_vsix "${extension}" "${version}" "${extensions_cache_dir}"
)"
if [[ -n "${vsix_path}" ]]; then
# Install the extension from the downloaded `*.vsix` file.
# Note: Installing from `*.vsix` automatically overwrites existing versions.
echo "Installing extension from ${vsix_path}"
if ! "${editor_cmd}" --install-extension "${vsix_path}" --force; then
echo "Warning: Failed to install ${extension} from '*.vsix'" >&2
result=1
fi
# Clean up the `*.vsix` file after installation attempt.
rm -f "${vsix_path}"
else
echo "Warning: Failed to download ${extension}@${version}.vsix" >&2
result=1
fi
# Clean up the `*.vsix` file after installation attempt.
rm -f "${vsix_path}"
return ${result}
}
# Install an extension.
install_extension() {
local editor_cmd="$1"
local extension="$2"
local version="$3"
local use_latest="$4"
local force_install="false"
local extensions_cache_dir
extensions_cache_dir="${SCRIPT_DIR}/cache/extensions"
# Check if already installed and get current version
local current_version
current_version="$(get_installed_version "${editor_cmd}" "${extension}")"
if [[ "${use_latest}" != "false" ]]; then
if [[ -n "${current_version}" && "${use_latest}" != "force" ]]; then
echo "Extension ${extension} is already installed" \
"(current: ${current_version}), skipping"
return 0
fi
echo "Checking latest version for ${extension}..." >&2
local latest_version
if latest_version=$(query_latest_version "${extension}"); then
echo " - Latest available version: ${latest_version}"
version="${latest_version}"
else
echo "Error: Could not determine latest version for ${extension}" >&2
exit 1
fi
fi
if [[ -z "${current_version}" ]]; then
# Extension not installed.
echo "Installing ${extension}@${version}"
elif [[ "${current_version}" == "${version}" ]]; then
# Exact version already installed.
echo "Extension ${extension}@${version} is already installed, skipping"
return 0
else
# Wrong version installed, need to force install.
echo "Extension ${extension} has wrong version installed" \
"(current: ${current_version}, wanted: ${version})," \
"force-installing ${version}"
force_install="true"
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}" \
"${extensions_cache_dir}"
return 0
fi
if ! install_extension_direct "${editor_cmd}" "${extension}" \
"${version}" "${force_install}"; then
echo "Direct installation failed, trying .vsix download method..." >&2
install_extension_via_vsix "${editor_cmd}" "${extension}" "${version}" \
"${extensions_cache_dir}"
fi
# Clean up extensions directory if empty
rmdir "${extensions_cache_dir}" 2> /dev/null || true
echo "Extension ${extension} installed successfully!"
return 0
}
# Install extensions from `extensions.lock`.
do_install_extensions() {
local editor_cmd
editor_cmd="$(find_editor_cmd)"
local extensions_cache_dir="${SCRIPT_DIR}/cache/extensions"
local extensions_lock
extensions_lock="$(get_extensions_lock)"
local use_latest="${1:-false}"
@@ -549,9 +801,8 @@ do_install_extensions() {
exit 1
fi
# Warn the installed extensions cache before we start processing the lock
# file.
installed_extensions "${editor_cmd}"
# Warm the installed extensions cache before we start processing the lockfile.
installed_extensions "${editor_cmd}" > /dev/null
# Process each extension.
while IFS= read -r line; do
@@ -561,54 +812,64 @@ do_install_extensions() {
continue
fi
local extension
local version
extension="${line%@*}"
version="${line#*@}"
# Check if already installed and get current version.
local current_version
current_version="$(get_installed_version "${editor_cmd}" "${extension}")"
local force_install="false"
if [[ -z "${current_version}" ]]; then
# Extension not installed.
echo "Installing ${extension}@${version}"
elif [[ "${use_latest}" == "true" ]]; then
# In latest mode, skip if any version is installed.
echo "Extension ${extension} is already installed" \
"(current: ${current_version}), skipping"
continue
elif [[ "${current_version}" == "${version}" ]]; then
# Exact version already installed.
echo "Extension ${extension}@${version} is already installed, skipping"
continue
else
# Wrong version installed, need to force install.
echo "Extension ${extension} is installed but wrong version" \
"(current: ${current_version}, wanted: ${version})," \
"force-installing ${version}"
force_install="true"
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
if ! install_extension_direct "${editor_cmd}" "${extension}" \
"${version}" "${use_latest}" "${force_install}"; then
echo "Direct installation failed, trying .vsix download method..."
install_extension_via_vsix "${editor_cmd}" "${extension}" "${version}" \
"${use_latest}" "${extensions_cache_dir}"
fi
install_extension "${editor_cmd}" "${extension}" "${version}" \
"${use_latest}"
fi
done < "${extensions_lock}"
}
# Clean up extensions directory if empty.
rmdir "${extensions_cache_dir}" 2> /dev/null || true
echo "Extensions installation complete!"
# Install a specific extension by identifier.
do_install_extension() {
local extension_id="$1"
local use_latest="${2:-false}"
local editor_cmd
editor_cmd="$(find_editor_cmd)"
local extensions_cache_dir="${SCRIPT_DIR}/cache/extensions"
local extension=""
local version=""
if [[ -z "${extension_id}" ]]; then
echo "Error: Extension identifier required"
echo "Usage: siren EDITOR install EXTENSION_ID"
echo "Example: siren cursor install ms-python.python"
exit 1
fi
# Parse extension ID - can be just extension name or extension@version
if [[ "${extension_id}" =~ @ ]]; then
# Extension with specific version
if ! validate_extension_line "${extension_id}"; then
echo "Error: Invalid extension format '${extension_id}'"
echo "Expected format: publisher.extension or publisher.extension@version"
exit 1
fi
extension="${extension_id%@*}"
version="${extension_id#*@}"
else
# Extension without version - install latest
if [[ ! "${extension_id}" =~ ^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$ ]]; then
echo "Error: Invalid extension format '${extension_id}'"
echo "Expected format: publisher.extension or publisher.extension@version"
exit 1
fi
extension="${extension_id}"
version="latest"
fi
# Warm the installed extensions cache
installed_extensions "${editor_cmd}" > /dev/null
if [[ "${version}" == "latest" ]]; then
use_latest="force"
fi
install_extension "${editor_cmd}" "${extension}" "${version}" "${use_latest}"
return 0
}
# ==============================================================================
@@ -676,21 +937,53 @@ main() {
# Default values for options.
local use_latest="false"
local extension_id=""
# Parse additional options.
while [[ $# -gt 0 ]]; do
case "$1" in
--latest)
use_latest="true"
shift
;;
*)
echo "Error: Unknown option '$1'"
show_help
# Handle command-specific options.
case "${command}" in
"install" | "inst")
# Handle install command specially since it requires an extension argument
if [[ $# -lt 1 ]]; then
echo "Error: Extension identifier required for install command"
echo "Usage: siren EDITOR install EXTENSION_ID"
echo "Example: siren cursor install ms-python.python"
exit 1
;;
esac
done
fi
extension_id="$1"
shift
# For install command, reject any additional options
if [[ $# -gt 0 ]]; then
echo "Error: Unknown option '$1' for install command"
echo "Usage: siren EDITOR install EXTENSION_ID"
exit 1
fi
;;
"extensions" | "ext")
# Parse additional options for other commands
while [[ $# -gt 0 ]]; do
case "$1" in
--latest)
if [[ "${use_latest}" != "force" ]]; then
use_latest="true"
fi
shift
;;
--force-latest)
use_latest="force"
shift
;;
*)
echo "Error: Unknown option '$1'"
show_help
exit 1
;;
esac
done
;;
esac
# Handle commands.
case "${command}" in
@@ -703,6 +996,9 @@ main() {
"extensions" | "ext")
do_install_extensions "${use_latest}"
;;
"install")
do_install_extension "${extension_id}"
;;
"")
echo "Error: No command provided"
show_help