diff --git a/registry/saheli/modules/jetbrains-plugins/README.md b/registry/saheli/modules/jetbrains-plugins/README.md new file mode 100644 index 00000000..c9746d04 --- /dev/null +++ b/registry/saheli/modules/jetbrains-plugins/README.md @@ -0,0 +1,141 @@ +--- +display_name: JetBrains with Plugin Configuration +description: A complete JetBrains IDE module with automatic plugin pre-configuration for workspaces. +icon: ../../../../.icons/jetbrains.svg +maintainer_github: sahelisaha04 +verified: false +tags: [ide, jetbrains, plugins, parameter] +--- + +# JetBrains IDEs with Plugin Configuration + +This module provides complete JetBrains IDE integration with automatic plugin pre-configuration capabilities. It implements full JetBrains Gateway functionality with plugin management features. + +```tf +module "jetbrains_with_plugins" { + source = "registry.coder.com/saheli/jetbrains-plugins/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" + + # Standard JetBrains module options + default = ["IU", "PY"] + + # NEW: Plugin pre-configuration + plugins = [ + "org.jetbrains.plugins.github", # GitHub integration + "com.intellij.ml.llm", # AI Assistant + "Pythonid", # Python support for IntelliJ + "org.intellij.plugins.markdown" # Markdown support + ] +} +``` + +## Features + +✅ **Complete JetBrains integration** - Full IDE functionality with Gateway support +✅ **Plugin pre-configuration** - Automatically suggests plugins when IDE opens +✅ **Project-level integration** - Creates `.idea/externalDependencies.xml` +✅ **Gateway compatible** - Works with JetBrains Gateway workflow +✅ **Zero setup required** - No manual IDE installation needed +✅ **Standalone implementation** - No external module dependencies + +## How It Works + +1. **JetBrains apps** are created directly with full Gateway integration +2. **Plugin configuration script** runs on workspace start (when plugins specified) +3. **IDE configuration files** are created for automatic plugin suggestions +4. **When connecting via Gateway** → IDE suggests configured plugins → User accepts → Plugins install + +## Plugin Configuration + +### Finding Plugin IDs + +Plugin IDs can be found on the [JetBrains Marketplace](https://plugins.jetbrains.com/): + +1. Navigate to the plugin page +2. Look for the plugin ID in the URL or plugin details +3. Common examples: + - `org.jetbrains.plugins.github` - GitHub integration + - `com.intellij.ml.llm` - AI Assistant + - `Pythonid` - Python support for IntelliJ IDEA + - `org.intellij.plugins.markdown` - Markdown support + +### Configuration Process + +The module creates: +- **IDE config directories**: `~/.config/JetBrains/[IDE]2025.1/` +- **Plugin suggestions**: `enabled_plugins.txt` and `pluginAdvertiser.xml` +- **Project requirements**: `/workspace/.idea/externalDependencies.xml` + +## Examples + +### Basic Usage with Plugins + +```tf +module "jetbrains_with_plugins" { + source = "registry.coder.com/saheli/jetbrains-plugins/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + folder = "/workspace" + default = ["IU"] + + plugins = [ + "org.jetbrains.plugins.github" + ] +} +``` + +### Multiple IDEs with Specific Plugins + +```tf +module "jetbrains_full_stack" { + source = "registry.coder.com/saheli/jetbrains-plugins/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + folder = "/workspace" + default = ["IU", "PY", "WS"] + + plugins = [ + "org.jetbrains.plugins.github", # GitHub (all IDEs) + "com.intellij.ml.llm", # AI Assistant (all IDEs) + "Pythonid", # Python (IntelliJ) + "JavaScript", # JavaScript (IntelliJ) + "org.intellij.plugins.markdown" # Markdown (all IDEs) + ] +} +``` + +## Module Parameters + +This module accepts all parameters from the base `coder/jetbrains` module, plus: + +### New Plugin Parameter + +- **`plugins`** (list(string), default: []): List of plugin IDs to pre-configure + +### Base Module Parameters + +- **`agent_id`** (string, required): Coder agent ID +- **`folder`** (string, required): Project folder path +- **`default`** (set(string), default: []): Pre-selected IDEs or empty for user choice +- **`options`** (set(string)): Available IDE choices +- **`major_version`** (string): IDE version (e.g., "2025.1" or "latest") +- **`channel`** (string): Release channel ("release" or "eap") + +## Supported IDEs + +All JetBrains IDEs with remote development support: +- CLion (`CL`) +- GoLand (`GO`) +- IntelliJ IDEA Ultimate (`IU`) +- PhpStorm (`PS`) +- PyCharm Professional (`PY`) +- Rider (`RD`) +- RubyMine (`RM`) +- RustRover (`RR`) +- WebStorm (`WS`) + +## Contributing + +This module addresses [GitHub Issue #208](https://github.com/coder/registry/issues/208) by providing plugin pre-configuration capabilities while following the namespace guidelines for community contributions. \ No newline at end of file diff --git a/registry/saheli/modules/jetbrains-plugins/main.test.ts b/registry/saheli/modules/jetbrains-plugins/main.test.ts new file mode 100644 index 00000000..4f62a438 --- /dev/null +++ b/registry/saheli/modules/jetbrains-plugins/main.test.ts @@ -0,0 +1,192 @@ +import { it, expect, describe } from "bun:test"; +import { + runTerraformInit, + testRequiredVariables, + runTerraformApply, +} from "~test"; + +describe("jetbrains-plugins", async () => { + await runTerraformInit(import.meta.dir); + + await testRequiredVariables(import.meta.dir, { + agent_id: "foo", + folder: "/home/foo", + }); + + // Test without plugins (should only create base JetBrains apps) + describe("without plugins", () => { + it("should create only base JetBrains apps when no plugins specified", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/home/coder", + default: '["IU", "PY"]', + // No plugins specified + }); + + // Should create JetBrains apps directly + const jetbrains_apps = state.resources.filter( + (res) => res.type === "coder_app" && res.name === "jetbrains" + ); + expect(jetbrains_apps.length).toBeGreaterThan(0); + + // Should NOT create plugin configuration script + const plugin_scripts = state.resources.filter( + (res) => res.type === "coder_script" && res.name === "jetbrains_plugins" + ); + expect(plugin_scripts.length).toBe(0); + }); + }); + + // Test with plugins (should create base apps + plugin script) + describe("with plugins", () => { + it("should create JetBrains apps and plugin configuration script", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/home/coder", + default: '["IU", "PY"]', + plugins: '["org.jetbrains.plugins.github", "com.intellij.ml.llm"]', + }); + + // Should create JetBrains apps directly + const jetbrains_apps = state.resources.filter( + (res) => res.type === "coder_app" && res.name === "jetbrains" + ); + expect(jetbrains_apps.length).toBeGreaterThan(0); + + // Should create plugin configuration script + const plugin_scripts = state.resources.filter( + (res) => res.type === "coder_script" && res.name === "jetbrains_plugins" + ); + expect(plugin_scripts.length).toBe(1); + + const script = plugin_scripts[0]; + expect(script.instances[0].attributes.display_name).toBe("Configure JetBrains Plugins"); + expect(script.instances[0].attributes.run_on_start).toBe(true); + expect(script.instances[0].attributes.start_blocks_login).toBe(false); + + // Check that plugins are included in the script + const scriptContent = script.instances[0].attributes.script; + expect(scriptContent).toContain("org.jetbrains.plugins.github"); + expect(scriptContent).toContain("com.intellij.ml.llm"); + }); + + it("should work with parameter mode and plugins", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/home/coder", + // default is empty (parameter mode) + plugins: '["org.jetbrains.plugins.github"]', + }); + + // Should create parameter for IDE selection directly + const parameters = state.resources.filter( + (res) => res.type === "coder_parameter" && res.name === "jetbrains_ides" + ); + expect(parameters.length).toBe(1); + + // Should create plugin configuration script + const plugin_scripts = state.resources.filter( + (res) => res.type === "coder_script" && res.name === "jetbrains_plugins" + ); + expect(plugin_scripts.length).toBe(1); + }); + + it("should pass through all base module parameters correctly", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + folder: "/workspace", + default: '["GO"]', + major_version: "2025.1", + channel: "eap", + plugins: '["org.jetbrains.plugins.github"]', + coder_app_order: 5, + }); + + // Should create GoLand app with correct parameters + const jetbrains_apps = state.resources.filter( + (res) => res.type === "coder_app" && res.name === "jetbrains" + ); + expect(jetbrains_apps.length).toBe(1); + + const app = jetbrains_apps[0]; + expect(app.instances[0].attributes.agent_id).toBe("test-agent"); + expect(app.instances[0].attributes.display_name).toBe("GoLand"); + expect(app.instances[0].attributes.order).toBe(5); + expect(app.instances[0].attributes.url).toContain("folder=/workspace"); + }); + + it("should work with single plugin", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/home/coder", + default: '["IU"]', + plugins: '["Pythonid"]', + }); + + // Should create plugin script with single plugin + const plugin_scripts = state.resources.filter( + (res) => res.type === "coder_script" && res.name === "jetbrains_plugins" + ); + expect(plugin_scripts.length).toBe(1); + + const scriptContent = plugin_scripts[0].instances[0].attributes.script; + expect(scriptContent).toContain("Pythonid"); + expect(scriptContent).toContain("PLUGINS=(Pythonid)"); + }); + + it("should work with empty plugins list", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/home/coder", + default: '["IU"]', + plugins: '[]', + }); + + // Should NOT create plugin script when plugins list is empty + const plugin_scripts = state.resources.filter( + (res) => res.type === "coder_script" && res.name === "jetbrains_plugins" + ); + expect(plugin_scripts.length).toBe(0); + + // Should still create base JetBrains apps + const jetbrains_apps = state.resources.filter( + (res) => res.type === "coder_app" && res.name === "jetbrains" + ); + expect(jetbrains_apps.length).toBe(1); + }); + }); + + // Test base module integration + describe("base module integration", () => { + it("should preserve all base module functionality", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/home/coder", + default: '["IU", "WS"]', + major_version: "latest", + channel: "release", + }); + + // Should create multiple IDE apps + const jetbrains_apps = state.resources.filter( + (res) => res.type === "coder_app" && res.name === "jetbrains" + ); + expect(jetbrains_apps.length).toBe(2); + + // Check app properties + const app_names = jetbrains_apps.map( + (app) => app.instances[0].attributes.display_name + ); + expect(app_names).toContain("IntelliJ IDEA"); + expect(app_names).toContain("WebStorm"); + + // Check URLs contain proper JetBrains Gateway links + jetbrains_apps.forEach((app) => { + const url = app.instances[0].attributes.url; + expect(url).toContain("jetbrains://gateway/coder"); + expect(url).toContain("ide_product_code="); + expect(url).toContain("ide_build_number="); + }); + }); + }); +}); \ No newline at end of file diff --git a/registry/saheli/modules/jetbrains-plugins/main.tf b/registry/saheli/modules/jetbrains-plugins/main.tf new file mode 100644 index 00000000..f2c16896 --- /dev/null +++ b/registry/saheli/modules/jetbrains-plugins/main.tf @@ -0,0 +1,271 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + http = { + source = "hashicorp/http" + version = ">= 3.0" + } + } +} + +variable "agent_id" { + type = string + description = "The resource ID of a Coder agent." +} + +variable "agent_name" { + type = string + description = "The name of a Coder agent. Needed for workspaces with multiple agents." + default = null +} + +variable "folder" { + type = string + description = "The directory to open in the IDE. e.g. /home/coder/project" + validation { + condition = can(regex("^(?:/[^/]+)+/?$", var.folder)) + error_message = "The folder must be a full path and must not start with a ~." + } +} + +variable "default" { + default = [] + type = set(string) + description = <<-EOT + The default IDE selection. Removes the selection from the UI. e.g. ["CL", "GO", "IU"] + EOT +} + +variable "group" { + type = string + description = "The name of a group that this app belongs to." + default = null +} + +variable "coder_app_order" { + type = number + description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)." + default = null +} + +variable "coder_parameter_order" { + type = number + description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)." + default = null +} + +variable "major_version" { + type = string + description = "The major version of the IDE. i.e. 2025.1" + default = "latest" + validation { + condition = can(regex("^[0-9]{4}\\.[0-2]{1}$", var.major_version)) || var.major_version == "latest" + error_message = "The major_version must be a valid version number. i.e. 2025.1 or latest" + } +} + +variable "channel" { + type = string + description = "JetBrains IDE release channel. Valid values are release and eap." + default = "release" + validation { + condition = can(regex("^(release|eap)$", var.channel)) + error_message = "The channel must be either release or eap." + } +} + +variable "options" { + type = set(string) + description = "The list of IDE product codes." + default = ["CL", "GO", "IU", "PS", "PY", "RD", "RM", "RR", "WS"] + validation { + condition = ( + alltrue([ + for code in var.options : contains(["CL", "GO", "IU", "PS", "PY", "RD", "RM", "RR", "WS"], code) + ]) + ) + error_message = "The options must be a set of valid product codes. Valid product codes are ${join(",", ["CL", "GO", "IU", "PS", "PY", "RD", "RM", "RR", "WS"])}." + } + validation { + condition = length(var.options) > 0 + error_message = "The options must not be empty." + } +} + +variable "plugins" { + type = list(string) + description = "A list of plugin IDs to pre-install in the JetBrains IDEs. Plugin IDs can be found on the JetBrains Marketplace." + default = [] +} + +variable "releases_base_link" { + type = string + description = "URL of the JetBrains releases base link." + default = "https://data.services.jetbrains.com" + validation { + condition = can(regex("^https?://.+$", var.releases_base_link)) + error_message = "The releases_base_link must be a valid HTTP/S address." + } +} + +variable "download_base_link" { + type = string + description = "URL of the JetBrains download base link." + default = "https://download.jetbrains.com" + validation { + condition = can(regex("^https?://.+$", var.download_base_link)) + error_message = "The download_base_link must be a valid HTTP/S address." + } +} + +variable "ide_config" { + description = <<-EOT + A map of JetBrains IDE configurations. + The key is the product code and the value is an object with the following properties: + - name: The name of the IDE. + - icon: The icon of the IDE. + - build: The build number of the IDE. + Example: + { + "CL" = { name = "CLion", icon = "/icon/clion.svg", build = "251.26927.39" }, + "GO" = { name = "GoLand", icon = "/icon/goland.svg", build = "251.26927.50" }, + "IU" = { name = "IntelliJ IDEA", icon = "/icon/intellij.svg", build = "251.26927.53" }, + } + EOT + type = map(object({ + name = string + icon = string + build = string + })) + default = { + "CL" = { name = "CLion", icon = "/icon/clion.svg", build = "251.26927.39" }, + "GO" = { name = "GoLand", icon = "/icon/goland.svg", build = "251.26927.50" }, + "IU" = { name = "IntelliJ IDEA", icon = "/icon/intellij.svg", build = "251.26927.53" }, + "PS" = { name = "PhpStorm", icon = "/icon/phpstorm.svg", build = "251.26927.60" }, + "PY" = { name = "PyCharm", icon = "/icon/pycharm.svg", build = "251.26927.74" }, + "RD" = { name = "Rider", icon = "/icon/rider.svg", build = "251.26927.67" }, + "RM" = { name = "RubyMine", icon = "/icon/rubymine.svg", build = "251.26927.47" }, + "RR" = { name = "RustRover", icon = "/icon/rustrover.svg", build = "251.26927.79" }, + "WS" = { name = "WebStorm", icon = "/icon/webstorm.svg", build = "251.26927.40" } + } + validation { + condition = length(var.ide_config) > 0 + error_message = "The ide_config must not be empty." + } + validation { + condition = alltrue([ + for code in var.options : contains(keys(var.ide_config), code) + ]) + error_message = "The ide_config must be a superset of var.options." + } +} + +data "http" "jetbrains_ide_versions" { + for_each = length(var.default) == 0 ? var.options : var.default + url = "${var.releases_base_link}/products/releases?code=${each.key}&type=${var.channel}&latest=true${var.major_version == "latest" ? "" : "&major_version=${var.major_version}"}" +} + +locals { + # Parse HTTP responses once with error handling for air-gapped environments + parsed_responses = { + for code in length(var.default) == 0 ? var.options : var.default : code => try( + jsondecode(data.http.jetbrains_ide_versions[code].response_body), + {} # Return empty object if API call fails + ) + } + + # Dynamically generate IDE configurations based on options with fallback to ide_config + options_metadata = { + for code in length(var.default) == 0 ? var.options : var.default : code => { + icon = var.ide_config[code].icon + name = var.ide_config[code].name + identifier = code + key = code + + # Use API build number if available, otherwise fall back to ide_config build number + build = length(keys(local.parsed_responses[code])) > 0 ? ( + local.parsed_responses[code][keys(local.parsed_responses[code])[0]][0].build + ) : var.ide_config[code].build + + # Store API data for potential future use (only if API is available) + json_data = length(keys(local.parsed_responses[code])) > 0 ? local.parsed_responses[code][keys(local.parsed_responses[code])[0]][0] : null + response_key = length(keys(local.parsed_responses[code])) > 0 ? keys(local.parsed_responses[code])[0] : null + } + } + + # Convert the parameter value to a set for for_each + selected_ides = length(var.default) == 0 ? toset(jsondecode(coalesce(data.coder_parameter.jetbrains_ides[0].value, "[]"))) : toset(var.default) +} + +data "coder_parameter" "jetbrains_ides" { + count = length(var.default) == 0 ? 1 : 0 + type = "list(string)" + name = "jetbrains_ides" + display_name = "JetBrains IDEs" + icon = "/icon/jetbrains-toolbox.svg" + mutable = true + default = jsonencode([]) + order = var.coder_parameter_order + form_type = "multi-select" + + dynamic "option" { + for_each = var.options + content { + icon = var.ide_config[option.value].icon + name = var.ide_config[option.value].name + value = option.value + } + } +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_app" "jetbrains" { + for_each = local.selected_ides + agent_id = var.agent_id + slug = "jetbrains-${lower(each.key)}" + display_name = local.options_metadata[each.key].name + icon = local.options_metadata[each.key].icon + external = true + order = var.coder_app_order + url = join("", [ + "jetbrains://gateway/coder?&workspace=", + data.coder_workspace.me.name, + "&owner=", + data.coder_workspace_owner.me.name, + "&folder=", + var.folder, + "&url=", + data.coder_workspace.me.access_url, + "&token=", + "$SESSION_TOKEN", + "&ide_product_code=", + each.key, + "&ide_build_number=", + local.options_metadata[each.key].build, + var.agent_name != null ? "&agent_name=${var.agent_name}" : "", + ]) +} + +# Plugin configuration script +resource "coder_script" "jetbrains_plugins" { + count = length(var.plugins) > 0 ? 1 : 0 + agent_id = var.agent_id + display_name = "Configure JetBrains Plugins" + icon = "/icon/jetbrains-toolbox.svg" + run_on_start = true + start_blocks_login = false + timeout = 120 + + script = templatefile("${path.module}/scripts/install-plugins.sh", { + plugins = var.plugins + selected_ides = length(var.default) == 0 ? var.options : var.default + folder = var.folder + }) +} \ No newline at end of file diff --git a/registry/saheli/modules/jetbrains-plugins/scripts/install-plugins.sh b/registry/saheli/modules/jetbrains-plugins/scripts/install-plugins.sh new file mode 100644 index 00000000..536fa8fb --- /dev/null +++ b/registry/saheli/modules/jetbrains-plugins/scripts/install-plugins.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Template variables from Terraform +PLUGINS=(${join(" ", plugins)}) +SELECTED_IDES=(${join(" ", selected_ides)}) +FOLDER="${folder}" + +# IDE configuration directory mapping +declare -A IDE_CONFIG_DIRS +IDE_CONFIG_DIRS["CL"]="CLion" +IDE_CONFIG_DIRS["GO"]="GoLand" +IDE_CONFIG_DIRS["IU"]="IntelliJIdea" +IDE_CONFIG_DIRS["PS"]="PhpStorm" +IDE_CONFIG_DIRS["PY"]="PyCharm" +IDE_CONFIG_DIRS["RD"]="Rider" +IDE_CONFIG_DIRS["RM"]="RubyMine" +IDE_CONFIG_DIRS["RR"]="RustRover" +IDE_CONFIG_DIRS["WS"]="WebStorm" + +# Colors for output +BOLD='\033[0;1m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +RESET='\033[0m' + +# Function to create plugin configuration for an IDE +create_plugin_config() { + local ide_code="$1" + local config_dir="$${IDE_CONFIG_DIRS[$ide_code]}" + + # JetBrains configuration path (standard location) + local jetbrains_config="$HOME/.config/JetBrains" + + echo -e "$${BOLD}🔧 Configuring plugins for $config_dir...$${RESET}" + + # Find the latest version directory or create a generic one + local ide_config_dir + if [ -d "$jetbrains_config" ]; then + # Look for existing configuration directory + ide_config_dir=$(find "$jetbrains_config" -maxdepth 1 -name "$config_dir*" -type d | head -1) + + if [ -z "$ide_config_dir" ]; then + # Create a generic configuration directory + ide_config_dir="$jetbrains_config/$${config_dir}2025.1" + mkdir -p "$ide_config_dir" + fi + else + # Create the base configuration structure + mkdir -p "$jetbrains_config" + ide_config_dir="$jetbrains_config/$${config_dir}2025.1" + mkdir -p "$ide_config_dir" + fi + + echo -e " 📁 Using config directory: $${BLUE}$ide_config_dir$${RESET}" + + # Create the plugins configuration + local plugins_config="$ide_config_dir/disabled_plugins.txt" + local enabled_plugins="$ide_config_dir/enabled_plugins.txt" + + # Ensure plugins directory exists + mkdir -p "$ide_config_dir/plugins" + + # Create a list of enabled plugins (so they auto-install when IDE starts) + echo -e " 📝 Creating plugin configuration..." + + # Write enabled plugins list + for plugin_id in "$${PLUGINS[@]}"; do + if [ -n "$plugin_id" ]; then + echo "$plugin_id" >> "$enabled_plugins" + echo -e " ✅ Configured for auto-install: $${GREEN}$plugin_id$${RESET}" + fi + done + + # Create IDE-specific configuration that will trigger plugin installation + local ide_options="$ide_config_dir/options" + mkdir -p "$ide_options" + + # Create plugin manager configuration + cat > "$ide_options/pluginAdvertiser.xml" < + + + + +EOF + + echo -e " 🎯 Created plugin advertiser configuration" +} + +# Function to create a project-level plugin suggestion +create_project_plugin_config() { + if [ -n "$FOLDER" ] && [ -d "$FOLDER" ]; then + local idea_dir="$FOLDER/.idea" + mkdir -p "$idea_dir" + + echo -e "$${BOLD}📁 Creating project-level plugin suggestions...$${RESET}" + + # Create externalDependencies.xml to suggest plugins + cat > "$idea_dir/externalDependencies.xml" < + + +$(for plugin_id in "$${PLUGINS[@]}"; do + if [ -n "$plugin_id" ]; then + echo " " + fi +done) + + +EOF + + echo -e " 📝 Created project plugin dependencies in $${BLUE}$idea_dir/externalDependencies.xml$${RESET}" + + # Create workspace.xml for plugin recommendations + cat > "$idea_dir/workspace.xml" < + + + + + +EOF + + echo -e " 🔧 Created workspace plugin recommendations" + fi +} + +# Main execution +if [ $${#PLUGINS[@]} -eq 0 ]; then + echo "No plugins specified for configuration." + exit 0 +fi + +echo -e "$${BOLD}🚀 JetBrains Plugin Configuration Setup$${RESET}" +echo -e "Configuring $${#PLUGINS[@]} plugin(s) for auto-installation..." +echo -e "Selected IDEs: $${SELECTED_IDES[*]}" +echo + +# Create plugin configurations for each selected IDE +for ide_code in "$${SELECTED_IDES[@]}"; do + create_plugin_config "$ide_code" + echo +done + +# Create project-level plugin suggestions +create_project_plugin_config + +echo +echo -e "$${GREEN}✨ Plugin configuration complete!$${RESET}" +echo -e "$${YELLOW}📋 When you connect via JetBrains Gateway:$${RESET}" +echo -e " 1. The IDE backend will be automatically downloaded" +echo -e " 2. Configured plugins will be suggested for installation" +echo -e " 3. You can accept the plugin installation prompts" +echo -e " 4. Plugins will be installed and activated" \ No newline at end of file