From af65559c806a4b89ad8bbd13bf0595a22ced7a7f Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Sat, 12 Jul 2025 02:13:38 +0530 Subject: [PATCH 1/5] feat(tools): 207 Add pre-install & workspace settings to vscode-desktop --- .../coder/modules/vscode-desktop/README.md | 40 +++ .../coder/modules/vscode-desktop/main.test.ts | 55 ++++ registry/coder/modules/vscode-desktop/main.tf | 42 +++ .../coder/modules/vscode-desktop/setup.sh | 267 ++++++++++++++++++ 4 files changed, 404 insertions(+) create mode 100644 registry/coder/modules/vscode-desktop/setup.sh diff --git a/registry/coder/modules/vscode-desktop/README.md b/registry/coder/modules/vscode-desktop/README.md index 7fcda3d5..8ac82cfc 100644 --- a/registry/coder/modules/vscode-desktop/README.md +++ b/registry/coder/modules/vscode-desktop/README.md @@ -35,3 +35,43 @@ module "vscode" { folder = "/home/coder/project" } ``` + +### Auto-install extensions and configure settings + +```tf +module "vscode" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/vscode-desktop/coder" + version = "1.1.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" + + # Auto-install Python development extensions + extensions = [ + "ms-python.python", + "ms-python.pylint", + "ms-toolsai.jupyter" + ] + + # Configure workspace settings + settings = { + "editor.fontSize" = 14 + "editor.tabSize" = 2 + "python.defaultInterpreterPath" = "/usr/bin/python3" + "workbench.colorTheme" = "Dark+ (default dark)" + } +} +``` + +### Disable automatic extension installation + +```tf +module "vscode" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/vscode-desktop/coder" + version = "1.1.0" + agent_id = coder_agent.example.id + extensions = ["ms-python.python"] + install_extensions = false # Only create recommendations, don't install +} +``` diff --git a/registry/coder/modules/vscode-desktop/main.test.ts b/registry/coder/modules/vscode-desktop/main.test.ts index b59ef5dc..998c85f1 100644 --- a/registry/coder/modules/vscode-desktop/main.test.ts +++ b/registry/coder/modules/vscode-desktop/main.test.ts @@ -86,4 +86,59 @@ describe("vscode-desktop", async () => { expect(coder_app?.instances.length).toBe(1); expect(coder_app?.instances[0].attributes.order).toBe(22); }); + + it("creates setup script when extensions are provided", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + extensions: '["ms-python.python", "ms-vscode.cpptools"]', + }); + + const coder_script = state.resources.find( + (res) => res.type === "coder_script" && res.name === "vscode_desktop_setup", + ); + + expect(coder_script).not.toBeNull(); + expect(coder_script?.instances.length).toBe(1); + expect(coder_script?.instances[0].attributes.run_on_start).toBe(true); + }); + + it("creates setup script when settings are provided", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + settings: '{"editor.fontSize": 14, "workbench.colorTheme": "Dark+ (default dark)"}', + }); + + const coder_script = state.resources.find( + (res) => res.type === "coder_script" && res.name === "vscode_desktop_setup", + ); + + expect(coder_script).not.toBeNull(); + expect(coder_script?.instances.length).toBe(1); + }); + + it("does not create setup script when install_extensions is false", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + extensions: '["ms-python.python"]', + install_extensions: "false", + }); + + const coder_script = state.resources.find( + (res) => res.type === "coder_script" && res.name === "vscode_desktop_setup", + ); + + expect(coder_script).toBeNull(); + }); + + it("does not create setup script when no extensions or settings", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + + const coder_script = state.resources.find( + (res) => res.type === "coder_script" && res.name === "vscode_desktop_setup", + ); + + expect(coder_script).toBeNull(); + }); }); diff --git a/registry/coder/modules/vscode-desktop/main.tf b/registry/coder/modules/vscode-desktop/main.tf index f93d14e3..27a31366 100644 --- a/registry/coder/modules/vscode-desktop/main.tf +++ b/registry/coder/modules/vscode-desktop/main.tf @@ -38,9 +38,51 @@ variable "group" { default = null } +variable "extensions" { + type = list(string) + description = "A list of VS Code extensions to install. Extensions should be specified in the format 'publisher.extension-name'." + default = [] + + validation { + condition = alltrue([ + for ext in var.extensions : can(regex("^[a-zA-Z0-9][a-zA-Z0-9\\-_]*\\.[a-zA-Z0-9][a-zA-Z0-9\\-_]*$", ext)) + ]) + error_message = "Extensions must be in the format 'publisher.extension-name' (e.g., 'ms-python.python')." + } +} + +variable "settings" { + type = any + description = "A map of VS Code settings to apply to the workspace. These settings will be written to the workspace's settings.json file." + default = {} +} + +variable "install_extensions" { + type = bool + description = "Whether to automatically install the specified extensions when the workspace starts." + default = true +} + data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} +# Script to install extensions and configure settings +resource "coder_script" "vscode_desktop_setup" { + count = var.install_extensions && (length(var.extensions) > 0 || length(var.settings) > 0) ? 1 : 0 + agent_id = var.agent_id + display_name = "VS Code Desktop Setup" + icon = "/icon/code.svg" + run_on_start = true + run_on_stop = false + timeout = 300 + + script = templatefile("${path.module}/setup.sh", { + EXTENSIONS = jsonencode(var.extensions) + SETTINGS = jsonencode(var.settings) + FOLDER = var.folder + }) +} + resource "coder_app" "vscode" { agent_id = var.agent_id external = true diff --git a/registry/coder/modules/vscode-desktop/setup.sh b/registry/coder/modules/vscode-desktop/setup.sh new file mode 100644 index 00000000..4b9b0523 --- /dev/null +++ b/registry/coder/modules/vscode-desktop/setup.sh @@ -0,0 +1,267 @@ +#!/bin/bash +set -euo pipefail + +# VS Code Desktop Extension and Settings Setup Script +# This script installs VS Code extensions and configures workspace settings + +EXTENSIONS='${EXTENSIONS}' +SETTINGS='${SETTINGS}' +FOLDER='${FOLDER}' + +# Function to log messages +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +# Function to check if VS Code CLI is available +check_vscode_cli() { + if command -v code >/dev/null 2>&1; then + return 0 + fi + + # Try alternative paths where VS Code might be installed + local vscode_paths=( + "/usr/bin/code" + "/usr/local/bin/code" + "/opt/visual-studio-code/bin/code" + "$HOME/.local/bin/code" + "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" + ) + + for path in "$${vscode_paths[@]}"; do + if [ -x "$path" ]; then + export PATH="$PATH:$(dirname "$path")" + return 0 + fi + done + + return 1 +} + +# Function to install VS Code extensions +install_extensions() { + local extensions_json="$1" + + if [ "$extensions_json" = "[]" ] || [ "$extensions_json" = "null" ]; then + log "No extensions to install" + return 0 + fi + + log "Installing VS Code extensions..." + + # Parse extensions from JSON array + local extensions + extensions=$(echo "$extensions_json" | jq -r '.[]' 2>/dev/null || echo "") + + if [ -z "$extensions" ]; then + log "No valid extensions found in configuration" + return 0 + fi + + local failed_extensions=() + local successful_extensions=() + local total_extensions=0 + + # Count total extensions first + while IFS= read -r extension; do + if [ -n "$extension" ]; then + ((total_extensions++)) + fi + done <<< "$extensions" + + log "Found $total_extensions extensions to install" + + # Install extensions with progress tracking + local current=0 + while IFS= read -r extension; do + if [ -n "$extension" ]; then + ((current++)) + log "Installing extension ($current/$total_extensions): $extension" + + # Validate extension format before attempting installation + if [[ ! "$extension" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*\.[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then + log "WARNING: Invalid extension format: $extension (skipping)" + failed_extensions+=("$extension") + continue + fi + + if timeout 30 code --install-extension "$extension" --force >/dev/null 2>&1; then + successful_extensions+=("$extension") + log "✓ Successfully installed: $extension" + else + failed_extensions+=("$extension") + log "✗ Failed to install: $extension" + fi + fi + done <<< "$extensions" + + # Report results + if [ $${#successful_extensions[@]} -gt 0 ]; then + log "Successfully installed $${#successful_extensions[@]} extensions: $${successful_extensions[*]}" + fi + + if [ $${#failed_extensions[@]} -gt 0 ]; then + log "WARNING: Failed to install $${#failed_extensions[@]} extensions: $${failed_extensions[*]}" + fi +} + +# Function to configure workspace settings +configure_settings() { + local settings_json="$1" + local folder_path="$2" + + if [ "$settings_json" = "{}" ] || [ "$settings_json" = "null" ]; then + log "No settings to configure" + return 0 + fi + + log "Configuring VS Code workspace settings..." + + # Determine the workspace directory + local workspace_dir + if [ -n "$folder_path" ] && [ -d "$folder_path" ]; then + workspace_dir="$folder_path" + else + workspace_dir="$(pwd)" + fi + + # Create .vscode directory if it doesn't exist + local vscode_dir="$workspace_dir/.vscode" + mkdir -p "$vscode_dir" + + # Path to settings.json + local settings_file="$vscode_dir/settings.json" + + # Merge with existing settings if they exist + local final_settings + if [ -f "$settings_file" ]; then + log "Merging with existing settings in $settings_file" + # Merge existing settings with new settings, giving priority to new settings + final_settings=$(jq -s '.[0] * .[1]' "$settings_file" <(echo "$settings_json") 2>/dev/null || echo "$settings_json") + else + final_settings="$settings_json" + fi + + # Write settings to file with proper formatting + if echo "$final_settings" | jq empty 2>/dev/null; then + echo "$final_settings" | jq --indent 2 '.' > "$settings_file" + + if [ $? -eq 0 ]; then + log "Successfully configured workspace settings in $settings_file" + # Log the settings that were applied (first 3 keys for brevity) + local setting_keys + setting_keys=$(echo "$final_settings" | jq -r 'keys[0:3] | join(", ")' 2>/dev/null || echo "") + if [ -n "$setting_keys" ]; then + log "Applied settings: $setting_keys$(echo "$final_settings" | jq -r 'if (keys | length) > 3 then " and " + ((keys | length) - 3 | tostring) + " more" else "" end' 2>/dev/null || echo "")" + fi + else + log "ERROR: Failed to write settings to $settings_file" + return 1 + fi + else + log "ERROR: Invalid JSON format in settings" + return 1 + fi +} + +# Function to create recommended extensions file +create_extensions_recommendations() { + local extensions_json="$1" + local folder_path="$2" + + if [ "$extensions_json" = "[]" ] || [ "$extensions_json" = "null" ]; then + return 0 + fi + + # Determine the workspace directory + local workspace_dir + if [ -n "$folder_path" ] && [ -d "$folder_path" ]; then + workspace_dir="$folder_path" + else + workspace_dir="$(pwd)" + fi + + # Create .vscode directory if it doesn't exist + local vscode_dir="$workspace_dir/.vscode" + mkdir -p "$vscode_dir" + + # Create extensions.json with recommendations + local extensions_file="$vscode_dir/extensions.json" + local recommendations + + # Create recommendations with proper formatting + recommendations=$(echo "$extensions_json" | jq '{ + recommendations: ., + unwantedRecommendations: [] + }' 2>/dev/null || echo '{"recommendations":[], "unwantedRecommendations":[]}') + + echo "$recommendations" | jq --indent 2 '.' > "$extensions_file" + + if [ $? -eq 0 ]; then + local ext_count + ext_count=$(echo "$extensions_json" | jq 'length' 2>/dev/null || echo "0") + log "Created extensions recommendations in $extensions_file ($ext_count extensions)" + else + log "WARNING: Failed to create extensions recommendations file" + fi +} + +# Main execution +main() { + log "Starting VS Code Desktop setup..." + + # Check if jq is available for JSON parsing + if ! command -v jq >/dev/null 2>&1; then + log "jq is not installed. Attempting installation..." + + # Try to install jq on common distributions (with error suppression) + if command -v apt-get >/dev/null 2>&1; then + log "Attempting to install jq via apt-get..." + sudo apt-get update >/dev/null 2>&1 && sudo apt-get install -y jq >/dev/null 2>&1 + elif command -v yum >/dev/null 2>&1; then + log "Attempting to install jq via yum..." + sudo yum install -y jq >/dev/null 2>&1 + elif command -v pacman >/dev/null 2>&1; then + log "Attempting to install jq via pacman..." + sudo pacman -S --noconfirm jq >/dev/null 2>&1 + elif command -v brew >/dev/null 2>&1; then + log "Attempting to install jq via brew..." + brew install jq >/dev/null 2>&1 + fi + + # Final check + if ! command -v jq >/dev/null 2>&1; then + log "ERROR: jq installation failed. Cannot process JSON configuration." + log "Please install jq manually: https://stedolan.github.io/jq/download/" + exit 1 + else + log "✓ jq installed successfully" + fi + fi + + # Check if VS Code CLI is available + if ! check_vscode_cli; then + log "WARNING: VS Code CLI (code command) is not available in PATH." + log "Extensions cannot be installed automatically, but settings will still be configured." + log "To install extensions manually, ensure VS Code is installed and the 'code' command is available." + + # Still configure settings and create recommendations + configure_settings "$SETTINGS" "$FOLDER" + create_extensions_recommendations "$EXTENSIONS" "$FOLDER" + return 0 + fi + + # Install extensions + install_extensions "$EXTENSIONS" + + # Configure settings + configure_settings "$SETTINGS" "$FOLDER" + + # Create extensions recommendations file + create_extensions_recommendations "$EXTENSIONS" "$FOLDER" + + log "VS Code Desktop setup completed successfully!" +} + +# Run main function +main "$@" From 83e71062bfa54a777d93cedb4c69f4c7430dbd07 Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Sat, 12 Jul 2025 02:21:59 +0530 Subject: [PATCH 2/5] chore(app): rename setup.sh to run.sh --- registry/coder/modules/vscode-desktop/{setup.sh => run.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename registry/coder/modules/vscode-desktop/{setup.sh => run.sh} (100%) diff --git a/registry/coder/modules/vscode-desktop/setup.sh b/registry/coder/modules/vscode-desktop/run.sh similarity index 100% rename from registry/coder/modules/vscode-desktop/setup.sh rename to registry/coder/modules/vscode-desktop/run.sh From 438e9e0af4ca11280d21ef8402aa1e4d630abfae Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Sat, 12 Jul 2025 02:36:48 +0530 Subject: [PATCH 3/5] fix(app): fixed test error check of null instead of undefined --- registry/coder/modules/vscode-desktop/main.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder/modules/vscode-desktop/main.test.ts b/registry/coder/modules/vscode-desktop/main.test.ts index 998c85f1..08fd622e 100644 --- a/registry/coder/modules/vscode-desktop/main.test.ts +++ b/registry/coder/modules/vscode-desktop/main.test.ts @@ -127,7 +127,7 @@ describe("vscode-desktop", async () => { (res) => res.type === "coder_script" && res.name === "vscode_desktop_setup", ); - expect(coder_script).toBeNull(); + expect(coder_script).toBeUndefined(); }); it("does not create setup script when no extensions or settings", async () => { @@ -139,6 +139,6 @@ describe("vscode-desktop", async () => { (res) => res.type === "coder_script" && res.name === "vscode_desktop_setup", ); - expect(coder_script).toBeNull(); + expect(coder_script).toBeUndefined(); }); }); From a6c90aa85865f5e72cede8487bde22398465a311 Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Sat, 12 Jul 2025 02:50:12 +0530 Subject: [PATCH 4/5] chore(app): fix bash filename --- registry/coder/modules/vscode-desktop/main.test.ts | 2 +- registry/coder/modules/vscode-desktop/main.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder/modules/vscode-desktop/main.test.ts b/registry/coder/modules/vscode-desktop/main.test.ts index 08fd622e..667f47dd 100644 --- a/registry/coder/modules/vscode-desktop/main.test.ts +++ b/registry/coder/modules/vscode-desktop/main.test.ts @@ -55,7 +55,7 @@ describe("vscode-desktop", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", folder: "/foo/bar", - openRecent: "false", + open_recent: "false", }); expect(state.outputs.vscode_url.value).toBe( "vscode://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", diff --git a/registry/coder/modules/vscode-desktop/main.tf b/registry/coder/modules/vscode-desktop/main.tf index 27a31366..241bf199 100644 --- a/registry/coder/modules/vscode-desktop/main.tf +++ b/registry/coder/modules/vscode-desktop/main.tf @@ -76,7 +76,7 @@ resource "coder_script" "vscode_desktop_setup" { run_on_stop = false timeout = 300 - script = templatefile("${path.module}/setup.sh", { + script = templatefile("${path.module}/run.sh", { EXTENSIONS = jsonencode(var.extensions) SETTINGS = jsonencode(var.settings) FOLDER = var.folder From 7da905f1643396b48570ac125871cb1f98907cc4 Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Tue, 15 Jul 2025 14:24:05 +0530 Subject: [PATCH 5/5] chore(app): Add commands to install vscode cli --- registry/coder/modules/vscode-desktop/run.sh | 61 ++++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/registry/coder/modules/vscode-desktop/run.sh b/registry/coder/modules/vscode-desktop/run.sh index 4b9b0523..303f4104 100644 --- a/registry/coder/modules/vscode-desktop/run.sh +++ b/registry/coder/modules/vscode-desktop/run.sh @@ -13,7 +13,7 @@ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } -# Function to check if VS Code CLI is available +# Function to check if VS Code CLI is available, and install if missing check_vscode_cli() { if command -v code >/dev/null 2>&1; then return 0 @@ -28,14 +28,58 @@ check_vscode_cli() { "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" ) - for path in "$${vscode_paths[@]}"; do + for path in "${vscode_paths[@]}"; do if [ -x "$path" ]; then export PATH="$PATH:$(dirname "$path")" return 0 fi done + + # Try to install VS Code Desktop (which provides the CLI) + log "VS Code CLI (code) not found. Attempting to install VS Code Desktop..." + if command -v apt-get >/dev/null 2>&1; then + log "Attempting to install VS Code via apt-get..." + sudo apt-get update >/dev/null 2>&1 && sudo apt-get install -y wget gpg >/dev/null 2>&1 + wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg + sudo install -o root -g root -m 644 microsoft.gpg /etc/apt/trusted.gpg.d/ + sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture)] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list' + sudo apt-get update >/dev/null 2>&1 && sudo apt-get install -y code >/dev/null 2>&1 + rm -f microsoft.gpg + + elif command -v yum >/dev/null 2>&1; then + log "Attempting to install VS Code via yum..." + sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc + sudo sh -c 'echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo' + sudo yum check-update >/dev/null 2>&1 && sudo yum install -y code >/dev/null 2>&1 + + elif command -v dnf >/dev/null 2>&1; then + log "Attempting to install VS Code via dnf..." + sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc + sudo sh -c 'echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo' + sudo dnf check-update >/dev/null 2>&1 && sudo dnf install -y code >/dev/null 2>&1 + + elif command -v pacman >/dev/null 2>&1; then + log "Attempting to install VS Code via pacman..." + sudo pacman -Sy --noconfirm code >/dev/null 2>&1 || sudo pacman -Sy --noconfirm visual-studio-code-bin >/dev/null 2>&1 + + elif command -v brew >/dev/null 2>&1; then + log "Attempting to install VS Code via brew..." + brew install --cask visual-studio-code >/dev/null 2>&1 + + else + log "ERROR: Could not determine package manager to install VS Code. Please install it manually." + return 1 - return 1 + fi + + # Re-check for code CLI + if command -v code >/dev/null 2>&1; then + log "✓ VS Code CLI installed successfully." + return 0 + else + log "ERROR: VS Code CLI installation failed. Please install VS Code Desktop and ensure 'code' is in your PATH." + return 1 + fi } # Function to install VS Code extensions @@ -241,14 +285,9 @@ main() { # Check if VS Code CLI is available if ! check_vscode_cli; then - log "WARNING: VS Code CLI (code command) is not available in PATH." - log "Extensions cannot be installed automatically, but settings will still be configured." - log "To install extensions manually, ensure VS Code is installed and the 'code' command is available." - - # Still configure settings and create recommendations - configure_settings "$SETTINGS" "$FOLDER" - create_extensions_recommendations "$EXTENSIONS" "$FOLDER" - return 0 + log "ERROR: VS Code CLI (code command) is not available in PATH after installation attempt." + log "Please ensure VS Code Desktop is installed and the 'code' command is available." + exit 1 fi # Install extensions