feat(ext): expand workspace recommended extensions to extensions shared between Cursor and VSCode

This is done via the help of a new `./siren shared` command, and the `.vscode/extensions.json` file.
This commit is contained in:
2025-09-13 06:20:38 +01:00
parent 3b30c6a3ee
commit e2b2a221ca
2 changed files with 271 additions and 39 deletions

View File

@@ -1,9 +1,112 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
// Allows for use of settings.shared.json and settings.local.json.
"swellaby.workspace-config-plus"
]
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
// Allows for use of settings.shared.json and settings.local.json.
"swellaby.workspace-config-plus",
// Below list is produced by `./siren shared --json`
"alefragnani.project-manager",
"anthropic.claude-code",
"antiantisepticeye.vscode-color-picker",
"antyos.openscad",
"arrterian.nix-env-selector",
"arturodent.command-alias",
"bibhasdn.unique-lines",
"bierner.github-markdown-preview",
"bierner.markdown-checkbox",
"bierner.markdown-emoji",
"bierner.markdown-footnotes",
"bierner.markdown-mermaid",
"bierner.markdown-preview-github-styles",
"bierner.markdown-yaml-preamble",
"bmarkovic.haproxy",
"bodil.file-browser",
"britesnow.vscode-toggle-quotes",
"bufbuild.vscode-buf",
"carlos-algms.make-task-provider",
"christian-kohler.path-intellisense",
"connor4312.esbuild-problem-matchers",
"connorshea.vscode-ruby-test-adapter",
"ctf0.macros",
"davidanson.vscode-markdownlint",
"dbaeumer.vscode-eslint",
"dewski.simplecov",
"dnut.rewrap-revived",
"editorconfig.editorconfig",
"elijah-potter.harper",
"emeraldwalk.runonsave",
"esbenp.prettier-vscode",
"exiasr.hadolint",
"fogio.inline-go-struct-tags-syntax-highlight",
"github.remotehub",
"github.vscode-github-actions",
"github.vscode-pull-request-github",
"gofenix.go-lines",
"golang.go",
"gruntfuggly.todo-tree",
"hashicorp.terraform",
"hbenl.vscode-test-explorer",
"hoovercj.vscode-settings-cycler",
"humao.rest-client",
"hverlin.mise-vscode",
"jakearl.search-editor-apply-changes",
"jnoortheen.nix-ide",
"joshbolduc.commitlint",
"joshmu.periscope",
"kahole.magit",
"karunamurti.haml",
"kilocode.kilo-code",
"koichisasada.vscode-rdbg",
"letrieu.expand-region",
"llvm-vs-code-extensions.vscode-clangd",
"m4ns0ur.base64",
"mads-hartmann.bash-ide-vscode",
"matthewpi.caddyfile-support",
"mattn.lisp",
"mermaidchart.vscode-mermaid-chart",
"mhutchie.git-graph",
"mkhl.direnv",
"mrmlnc.vscode-duplicate",
"ms-azuretools.vscode-containers",
"ms-dotnettools.vscode-dotnet-runtime",
"ms-kubernetes-tools.vscode-kubernetes-tools",
"ms-python.debugpy",
"ms-python.python",
"ms-vscode.cmake-tools",
"ms-vscode.extension-test-runner",
"ms-vscode.hexeditor",
"ms-vscode.remote-repositories",
"ms-vscode.test-adapter-converter",
"ms-vscode.vscode-speech",
"ms-vsliveshare.vsliveshare",
"openai.chatgpt",
"pflannery.vscode-versionlens",
"pkief.material-icon-theme",
"pomdtr.excalidraw-editor",
"redhat.vscode-xml",
"redhat.vscode-yaml",
"romanpeshkov.vscode-text-tables",
"rrudi.vscode-dired",
"rust-lang.rust-analyzer",
"shopify.ruby-extensions-pack",
"shopify.ruby-lsp",
"sidneys1.gitconfig",
"sorbet.sorbet-vscode-extension",
"streetsidesoftware.code-spell-checker",
"stuart.unique-window-colors",
"sumneko.lua",
"svelte.svelte-vscode",
"swellaby.vscode-rust-test-adapter",
"tamasfe.even-better-toml",
"tootone.org-mode",
"tuttieee.emacs-mcx",
"tyriar.sort-lines",
"vadimcn.vscode-lldb",
"viktorzetterstrom.non-breaking-space-highlighter",
"wenhoujx.swiper",
"zhuangtongfa.material-theme",
"ziyasal.vscode-open-in-github",
"zxh404.vscode-proto3"
]
}

193
siren
View File

@@ -57,6 +57,48 @@ get_extensions_lock() {
echo "${SCRIPT_DIR}/extensions.${SETUP_EDITOR}.lock"
}
# Get extensions lockfile path for a specific editor name.
# Does not rely on SETUP_EDITOR.
extensions_lock_for_editor() {
local editor_name="$1"
echo "${SCRIPT_DIR}/extensions.${editor_name}.lock"
}
# Normalize editor name/synonyms to canonical editor id.
# Returns canonical id via STDOUT or empty string if unknown.
normalize_editor_name() {
local name
name="$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')"
case "${name}" in
cursor | c)
echo "cursor"
;;
vscode | code | vsc | v)
echo "vscode"
;;
vscode-insiders | code-insiders | insiders | vsci | i)
echo "vscode-insiders"
;;
windsurf | wind | surf | w)
echo "windsurf"
;;
kiro | k)
echo "kiro"
;;
*)
echo ""
;;
esac
}
# Read a lockfile and output sorted unique extension ids without versions.
extensions_ids_from_lock() {
local lock_path="$1"
grep -Ev '^\s*(#|$)' "${lock_path}" |
cut -d@ -f1 |
sort -u
}
# ==============================================================================
# Help
# ==============================================================================
@@ -65,6 +107,7 @@ show_help() {
cat << EOF
Usage: $(basename "$0") EDITOR COMMAND [OPTIONS]
$(basename "$0") config
$(basename "$0") shared-extensions [--json] [EDITORS...]
Editors:
cursor, c Cursor editor
@@ -79,6 +122,10 @@ Commands:
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.
shared-extensions, shared
Print extensions present in all specified editors.
Defaults to comparing: cursor, vscode
Use --json to output a JSON array
Options:
--latest When used with the extensions command, installs the
@@ -1210,6 +1257,89 @@ do_install_extension() {
return 0
}
# Compute and print the intersection of extensions across editors.
do_shared_extensions() {
local -a input_editors
local -a editors
local -A seen
local arg
local output_json="false"
# Parse optional flags (currently only --json)
while [[ $# -gt 0 ]]; do
case "$1" in
--json)
output_json="true"
shift
;;
--)
shift
break
;;
--*)
fatal "Unknown option for shared-extensions: '$1'"
;;
*)
break
;;
esac
done
if [[ $# -eq 0 ]]; then
input_editors=("cursor" "vscode")
else
input_editors=("$@")
fi
# Normalize and deduplicate editors.
for arg in "${input_editors[@]}"; do
local norm
norm="$(normalize_editor_name "${arg}")"
if [[ -z "${norm}" ]]; then
fatal "Unknown editor '${arg}'"
fi
if [[ -z "${seen[${norm}]:-}" ]]; then
editors+=("${norm}")
seen["${norm}"]=1
fi
done
if [[ ${#editors[@]} -eq 0 ]]; then
fatal "No valid editors specified"
fi
# Build intersection iteratively.
local result=""
local idx=0
for arg in "${editors[@]}"; do
local lock
lock="$(extensions_lock_for_editor "${arg}")"
if [[ ! -f "${lock}" ]]; then
fatal "Lock file not found for ${arg}: ${lock}"
fi
local current
current="$(extensions_ids_from_lock "${lock}")"
if [[ ${idx} -eq 0 ]]; then
result="${current}"
else
# 'comm' requires sorted input; our helper already sorts.
result="$(comm -12 <(printf '%s\n' "${result}") \
<(printf '%s\n' "${current}"))"
fi
idx=$((idx + 1))
done
# Print final result.
if [[ "${output_json}" == "true" ]]; then
# Convert newline-separated list to JSON array using jq safely
printf '%s\n' "${result}" | jq -R -s 'split("\n") | map(select(length>0))'
else
printf '%s\n' "${result}"
fi
}
# ==============================================================================
# Main
# ==============================================================================
@@ -1224,18 +1354,39 @@ main() {
fi
# Handle help command.
if [[ "$1" == "help" || "$1" == "--help" || "$1" == "-h" ]]; then
if [[ "$1" == "help" || " $* " == *" --help "* || " $* " == *" -h "* ]]; then
show_help
exit 0
fi
# Handle standalone config command.
# Normalize first argument.
local first_arg
first_arg="$(echo "${1}" | tr '[:upper:]' '[:lower:]')"
if [[ "${first_arg}" == "config" || "${first_arg}" == "conf" ]]; then
define_settings
do_static_config
exit 0
# Handle standalone top-level commands.
case "${first_arg}" in
config | conf)
define_settings
do_static_config
exit 0
;;
shared-extensions | shared)
shift 1
# Remaining arguments are optional editor names
do_shared_extensions "$@"
exit 0
;;
esac
# Set editor from first argument using normalization.
SETUP_EDITOR="$(normalize_editor_name "${first_arg}")"
if [[ -z "${SETUP_EDITOR}" ]]; then
error "Unsupported editor '${first_arg}'"
show_help
exit 1
fi
if [[ "${SETUP_EDITOR}" == "kiro" ]]; then
PREFER_OPENVSX="true"
fi
# Require at least two arguments from this point on (editor and command).
@@ -1245,32 +1396,6 @@ main() {
exit 1
fi
# Set editor from first argument.
local editor="${first_arg}"
case "${editor}" in
"vscode" | "code" | "vsc" | "v")
SETUP_EDITOR="vscode"
;;
"vscode-insiders" | "code-insiders" | "insiders" | "vsci" | "i")
SETUP_EDITOR="vscode-insiders"
;;
"cursor" | "c")
SETUP_EDITOR="cursor"
;;
"windsurf" | "wind" | "surf" | "w")
SETUP_EDITOR="windsurf"
;;
"kiro" | "k")
SETUP_EDITOR="kiro"
PREFER_OPENVSX="true"
;;
*)
error "Unsupported editor '${editor}'"
show_help
exit 1
;;
esac
# Initialize settings now that SETUP_EDITOR is known.
define_settings
@@ -1301,6 +1426,10 @@ main() {
# Handle commands.
case "${command}" in
"help" | "h")
show_help
exit 0
;;
"config" | "conf")
do_config
;;