This is an automated email from the ASF dual-hosted git repository.

christophd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 4fab747b6db254d7f1d80662e300a242e27c89c2
Author: Christoph Deppisch <[email protected]>
AuthorDate: Mon Nov 9 11:15:30 2026 +0100

    CAMEL-26766: Support custom plugin configuration path
    
    Add support for loading the Camel JBang plugin configuration from a
    custom location. The plugin config path is resolved in this order:
    
    1. Explicit Path argument passed to PluginHelper methods
    2. CAMEL_CLI_PLUGIN_CONFIG environment variable
    3. Default ~/.camel-jbang-plugins.json
    
    The path can point to a JSON file or a directory (in which case the
    default filename is resolved inside it).
    
    Plugin commands (add, get, list, delete) expose this via the
    --plugin-config CLI option.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../jbang-commands/camel-jbang-plugin-add.adoc     |   1 +
 .../jbang-commands/camel-jbang-plugin-delete.adoc  |   1 +
 .../jbang-commands/camel-jbang-plugin-get.adoc     |   1 +
 .../jbang-commands/camel-jbang-plugin-list.adoc    |   1 +
 .../pages/jbang-commands/camel-jbang-plugin.adoc   |   1 +
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../core/commands/plugin/PluginBaseCommand.java    |  26 ++++-
 .../camel/dsl/jbang/core/common/PluginHelper.java  |  73 ++++++++++--
 .../jbang/core/commands/plugin/PluginGetTest.java  |  61 ++++++++++
 .../dsl/jbang/core/common/PluginHelperTest.java    | 125 ++++++++++++++++++++-
 10 files changed, 273 insertions(+), 19 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-add.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-add.adoc
index 3f6cfc53d295..c42db1eab9bd 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-add.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-add.adoc
@@ -25,6 +25,7 @@ camel plugin add [options]
 | `--first-version` | First version of this plugin |  | String
 | `--gav` | Maven group and artifact coordinates. |  | String
 | `--groupId` | Maven groupId | org.apache.camel | String
+| `--plugin-config` | Path to a custom plugin configuration file or directory 
|  | String
 | `--repo,--repos` | Additional maven repositories to use for downloading the 
plugin (Use commas to separate multiple repositories) |  | String
 | `--version` | Maven artifact version |  | String
 | `-h,--help` | Display the help and sub-commands |  | boolean
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-delete.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-delete.adoc
index 31ebeb92b66c..631a979660a4 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-delete.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-delete.adoc
@@ -19,6 +19,7 @@ camel plugin delete [options]
 [cols="2,5,1,2",options="header"]
 |===
 | Option | Description | Default | Type
+| `--plugin-config` | Path to a custom plugin configuration file or directory 
|  | String
 | `-h,--help` | Display the help and sub-commands |  | boolean
 |===
 
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-get.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-get.adoc
index c778d9cd43d8..2ca6dd356b3f 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-get.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-get.adoc
@@ -20,6 +20,7 @@ camel plugin get [options]
 |===
 | Option | Description | Default | Type
 | `--json` | Output in JSON Format |  | boolean
+| `--plugin-config` | Path to a custom plugin configuration file or directory 
|  | String
 | `--repos` | Display maven repository column | false | boolean
 | `-h,--help` | Display the help and sub-commands |  | boolean
 |===
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-list.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-list.adoc
index 8bb7ff1c9d6a..0474a8002df1 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-list.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin-list.adoc
@@ -20,6 +20,7 @@ camel plugin list [options]
 |===
 | Option | Description | Default | Type
 | `--json` | Output in JSON Format |  | boolean
+| `--plugin-config` | Path to a custom plugin configuration file or directory 
|  | String
 | `--repos` | Display maven repository column | false | boolean
 | `-h,--help` | Display the help and sub-commands |  | boolean
 |===
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin.adoc
index 1397ec4d03a6..12414aa1d96c 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-plugin.adoc
@@ -31,6 +31,7 @@ camel plugin [options]
 [cols="2,5,1,2",options="header"]
 |===
 | Option | Description | Default | Type
+| `--plugin-config` | Path to a custom plugin configuration file or directory 
|  | String
 | `-h,--help` | Display the help and sub-commands |  | boolean
 |===
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 5f0350208ef6..5a5d8790c89f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -24,7 +24,7 @@
     { "name": "log", "fullName": "log", "description": "Tail logs from running 
Camel integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelLogAction", "options": [ 
{ "names": "--duration", "description": "How long to keep watching or 
following, in millis or as a duration such as 30s (default: until Enter is 
pressed)", "javaType": "long", "type": "integer" }, { "names": "--find", 
"description": "Find and highlight matching text (ignore case).", "javaType": 
"j [...]
     { "name": "nano", "fullName": "nano", "description": "Nano editor to edit 
file", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Nano", 
"options": [ { "names": "-h,--help", "description": "Display the help and 
sub-commands", "javaType": "boolean", "type": "boolean" } ] },
     { "name": "overview", "fullName": "overview", "description": "High-level 
overview of the integrations in a project, optionally explained by AI\/LLM", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.Overview", "options": 
[ { "names": "--ai", "description": "Ask an LLM to explain the project (an 
overview, the capabilities, labels and notes of routes that have none) and save 
it in camel-summary.md, marked as AI-assisted", "javaType": "boolean", "type": 
"boolean" }, { "names": " [...]
-    { "name": "plugin", "fullName": "plugin", "description": "Manage plugins 
that add sub-commands to this CLI", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "add", 
"fullName": "plugin add", "description": "Add new plugin", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginA [...]
+    { "name": "plugin", "fullName": "plugin", "description": "Manage plugins 
that add sub-commands to this CLI", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand", "options": [ { 
"names": "--plugin-config", "description": "Path to a custom plugin 
configuration file or directory", "javaType": "java.lang.String", "type": 
"string" }, { "names": "-h,--help", "description": "Display the help and 
sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcomm [...]
     { "name": "ps", "fullName": "ps", "description": "List running Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.ListProcess", "options": [ { 
"names": "--duration", "description": "How long to keep watching or following, 
in millis or as a duration such as 30s (default: until Enter is pressed)", 
"javaType": "long", "type": "integer" }, { "names": "--json", "description": 
"Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": 
[...]
     { "name": "restart", "fullName": "restart", "description": "Restarts 
running Camel integrations (stop + re-launch)", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.RestartProcess", "options": [ 
{ "names": "--timeout", "description": "How long to wait for the integration to 
stop, and again to start, in millis or as a duration such as 30s", 
"defaultValue": "60000", "javaType": "long", "type": "integer" }, { "names": 
"-h,--help", "description": "Display the help and su [...]
     { "name": "run", "fullName": "run", "description": "Run as local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run", 
"options": [ { "names": "--background", "description": "Run in the background", 
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "How long to wait for the integration in 
the background to start, before returning, in millis or as a duration such as 
2m (true waits 60 seconds [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginBaseCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginBaseCommand.java
index ecdb433042d2..22a57a2b15ba 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginBaseCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginBaseCommand.java
@@ -17,26 +17,46 @@
 
 package org.apache.camel.dsl.jbang.core.commands.plugin;
 
+import java.nio.file.Path;
+
 import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.common.PluginHelper;
 import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
 
 /**
- * Base command supports Kubernetes client related options such as namespace 
or custom kube config option. Automatically
- * applies the options to the Kubernetes client instance that is being used to 
run commands.
+ * Base command for plugin subcommands. Provides the {@code --plugin-config} 
option that overrides the default plugin
+ * configuration file location.
  */
 abstract class PluginBaseCommand extends CamelCommand {
 
+    @CommandLine.Option(names = { "--plugin-config" },
+                        description = "Path to a custom plugin configuration 
file or directory")
+    String pluginConfig;
+
     protected PluginBaseCommand(CamelJBangMain main) {
         super(main);
     }
 
+    private Path pluginConfigPath() {
+        return pluginConfig != null ? Path.of(pluginConfig) : null;
+    }
+
     JsonObject loadConfig() {
+        Path path = pluginConfigPath();
+        if (path != null) {
+            return PluginHelper.getOrCreatePluginConfig(path);
+        }
         return PluginHelper.getOrCreatePluginConfig();
     }
 
     void saveConfig(JsonObject plugins) {
-        PluginHelper.savePluginConfig(plugins);
+        Path path = pluginConfigPath();
+        if (path != null) {
+            PluginHelper.savePluginConfig(plugins, path);
+        } else {
+            PluginHelper.savePluginConfig(plugins);
+        }
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
index 9405cd6fdb7d..959b0c2995e6 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
@@ -68,7 +68,9 @@ import picocli.CommandLine;
  */
 public final class PluginHelper {
 
-    public static final String PLUGIN_CONFIG = ".camel-jbang-plugins.json";
+    public static final String PLUGIN_CONFIG_FILE = 
".camel-jbang-plugins.json";
+    public static final String PLUGIN_CONFIG_ENV = "CAMEL_CLI_PLUGIN_CONFIG";
+
     public static final String PLUGIN_SERVICE_DIR = 
"META-INF/services/org/apache/camel/camel-jbang-plugin/";
 
     /**
@@ -88,6 +90,37 @@ public final class PluginHelper {
         // prevent instantiation of utility class
     }
 
+    public static Path getCustomPluginConfigPath() {
+        return getCustomPluginConfigPath(null);
+    }
+
+    /**
+     * Resolves the path to the plugin configuration file. Checks, in order:
+     * <ol>
+     * <li>A path set programmatically via method argument</li>
+     * <li>The {@value #PLUGIN_CONFIG_ENV} environment variable</li>
+     * <li>The default {@value #PLUGIN_CONFIG_FILE} in the user home 
directory</li>
+     * </ol>
+     */
+    public static Path getCustomPluginConfigPath(Path configPath) {
+        Path pluginConfigPath;
+        if (configPath != null) {
+            pluginConfigPath = configPath;
+        } else {
+            String env = System.getenv(PLUGIN_CONFIG_ENV);
+            if (env != null && !env.isBlank()) {
+                pluginConfigPath = Path.of(env);
+            } else {
+                return 
CommandLineHelper.getHomeDir().resolve(PLUGIN_CONFIG_FILE);
+            }
+        }
+
+        if (pluginConfigPath.getFileName().toString().endsWith(".json")) {
+            return pluginConfigPath;
+        }
+        return pluginConfigPath.resolve(PLUGIN_CONFIG_FILE);
+    }
+
     /**
      * Decides whether plugin discovery (classpath scan + JSON config + Maven 
resolution) is needed for the current
      * invocation. Returns false when the target command is a built-in that 
does not consume plugins, skipping all
@@ -659,9 +692,17 @@ public final class PluginHelper {
         return 
Optional.ofNullable(getPluginConfig()).orElseGet(PluginHelper::createPluginConfig);
     }
 
+    public static JsonObject getOrCreatePluginConfig(Path configPath) {
+        return Optional.ofNullable(getPluginConfig(configPath)).orElseGet(() 
-> PluginHelper.createPluginConfig(configPath));
+    }
+
     public static JsonObject getPluginConfig() {
+        return getPluginConfig(null);
+    }
+
+    public static JsonObject getPluginConfig(Path configPath) {
         try {
-            Path f = CommandLineHelper.getHomeDir().resolve(PLUGIN_CONFIG);
+            Path f = getCustomPluginConfigPath(configPath);
             if (Files.exists(f)) {
                 String text = Files.readString(f);
                 return (JsonObject) Jsoner.deserialize(text);
@@ -674,9 +715,17 @@ public final class PluginHelper {
     }
 
     public static JsonObject createPluginConfig() {
-        Path f = CommandLineHelper.getHomeDir().resolve(PLUGIN_CONFIG);
+        return createPluginConfig(null);
+    }
+
+    public static JsonObject createPluginConfig(Path configPath) {
+        Path f = getCustomPluginConfigPath(configPath);
         JsonObject config = Jsoner.deserialize("{ \"plugins\": {} }", new 
JsonObject());
         try {
+            Path parent = f.getParent();
+            if (parent != null) {
+                Files.createDirectories(parent);
+            }
             Files.writeString(f, config.toJson(),
                     StandardOpenOption.CREATE,
                     StandardOpenOption.WRITE,
@@ -689,7 +738,11 @@ public final class PluginHelper {
     }
 
     public static void savePluginConfig(JsonObject plugins) {
-        Path f = CommandLineHelper.getHomeDir().resolve(PLUGIN_CONFIG);
+        savePluginConfig(plugins, null);
+    }
+
+    public static void savePluginConfig(JsonObject plugins, Path configPath) {
+        Path f = getCustomPluginConfigPath(configPath);
         try {
             Files.writeString(f, plugins.toJson(),
                     StandardOpenOption.CREATE,
@@ -735,12 +788,12 @@ public final class PluginHelper {
         JsonObject pluginConfig = PluginHelper.getOrCreatePluginConfig();
         JsonObject plugins = pluginConfig.getMap("plugins");
 
-        JsonObject kubePlugin = new JsonObject();
-        kubePlugin.put("name", pluginType.getName());
-        kubePlugin.put("command", pluginType.getCommand());
-        kubePlugin.put("description", pluginType.getDescription());
-        kubePlugin.put("firstVersion", pluginType.getFirstVersion());
-        plugins.put(pluginType.getName(), kubePlugin);
+        JsonObject newPlugin = new JsonObject();
+        newPlugin.put("name", pluginType.getName());
+        newPlugin.put("command", pluginType.getCommand());
+        newPlugin.put("description", pluginType.getDescription());
+        newPlugin.put("firstVersion", pluginType.getFirstVersion());
+        plugins.putIfAbsent(pluginType.getName(), newPlugin);
 
         PluginHelper.savePluginConfig(pluginConfig);
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
index 39588257ab4d..02dd47070b85 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.plugin;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.util.List;
 
 import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTestSupport;
@@ -26,9 +29,13 @@ import org.apache.camel.util.json.JsonObject;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 class PluginGetTest extends CamelCommandBaseTestSupport {
 
+    @TempDir
+    Path customConfigDir;
+
     @BeforeEach
     public void setup() throws Exception {
         super.setup();
@@ -63,6 +70,60 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
                 output.get(3));
     }
 
+    @Test
+    public void shouldUseCustomPluginConfigFile() throws Exception {
+        Path customConfig = customConfigDir.resolve("custom-plugins.json");
+        String configContent = """
+                {
+                  "plugins": {
+                    "custom-plugin": {
+                      "name": "custom-plugin",
+                      "command": "custom",
+                      "description": "Custom plugin from custom config"
+                    }
+                  }
+                }
+                """;
+        Files.writeString(customConfig, configContent, 
StandardOpenOption.CREATE);
+
+        PluginGet command = new PluginGet(new 
CamelJBangMain().withPrinter(printer));
+        command.pluginConfig = customConfig.toString();
+        command.doCall();
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals(4, output.size());
+        Assertions.assertTrue(output.get(3).contains("custom-plugin"));
+        Assertions.assertTrue(output.get(3).contains("Custom plugin from 
custom config"));
+    }
+
+    @Test
+    public void shouldUseCustomPluginConfigDirectory() throws Exception {
+        Path configDir = customConfigDir.resolve("config-dir");
+        Files.createDirectories(configDir);
+        Path configFile = configDir.resolve(PluginHelper.PLUGIN_CONFIG_FILE);
+        String configContent = """
+                {
+                  "plugins": {
+                    "dir-plugin": {
+                      "name": "dir-plugin",
+                      "command": "dir",
+                      "description": "Plugin from directory config"
+                    }
+                  }
+                }
+                """;
+        Files.writeString(configFile, configContent, 
StandardOpenOption.CREATE);
+
+        PluginGet command = new PluginGet(new 
CamelJBangMain().withPrinter(printer));
+        command.pluginConfig = configDir.toString();
+        command.doCall();
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals(4, output.size());
+        Assertions.assertTrue(output.get(3).contains("dir-plugin"));
+        Assertions.assertTrue(output.get(3).contains("Plugin from directory 
config"));
+    }
+
     @Test
     public void shouldGenerateDependencyAndDescription() throws Exception {
         JsonObject pluginConfig = PluginHelper.getOrCreatePluginConfig();
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/PluginHelperTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/PluginHelperTest.java
index f49dbc7afeaa..cd5bbafdce9c 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/PluginHelperTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/PluginHelperTest.java
@@ -37,6 +37,7 @@ import static 
org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -65,7 +66,7 @@ public class PluginHelperTest {
 
     @Test
     public void aPluginCommandIsOfACamelPluginOrOfTheConfiguration() throws 
Exception {
-        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG);
+        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG_FILE);
         Files.writeString(userConfig, """
                 {
                   "plugins": {
@@ -91,7 +92,7 @@ public class PluginHelperTest {
     @Test
     public void testFallbackToJsonConfig() throws Exception {
         // Create a user plugin config file in home directory
-        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG);
+        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG_FILE);
         String userConfigContent = """
                 {
                   "plugins": {
@@ -120,7 +121,7 @@ public class PluginHelperTest {
 
     @Test
     public void testGetActivePluginsFiltersByTarget() throws Exception {
-        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG);
+        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG_FILE);
         String configContent = """
                 {
                   "plugins": {
@@ -150,7 +151,7 @@ public class PluginHelperTest {
 
     @Test
     public void testGetActivePluginsNoFilterLoadsAll() throws Exception {
-        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG);
+        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG_FILE);
         String configContent = """
                 {
                   "plugins": {
@@ -411,12 +412,126 @@ public class PluginHelperTest {
         assertEquals("", printer.getOutput());
     }
 
+    @Test
+    public void testDefaultPluginConfigPath() {
+        Path expected = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG_FILE);
+        assertEquals(expected, PluginHelper.getCustomPluginConfigPath());
+    }
+
+    @Test
+    public void testCustomPluginConfigPath() {
+        Path custom = tempDir.resolve("custom-plugins.json");
+        assertEquals(custom, PluginHelper.getCustomPluginConfigPath(custom));
+    }
+
+    @Test
+    public void testCustomPluginConfigPathAsDirectory() throws Exception {
+        Path customDir = tempDir.resolve("custom-config-dir");
+        Files.createDirectories(customDir);
+        assertEquals(customDir.resolve(PluginHelper.PLUGIN_CONFIG_FILE), 
PluginHelper.getCustomPluginConfigPath(customDir));
+    }
+
+    @Test
+    public void testCustomPluginConfigPathReset() {
+        Path custom = tempDir.resolve("custom-plugins.json");
+        assertEquals(custom, PluginHelper.getCustomPluginConfigPath(custom));
+
+        Path expected = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG_FILE);
+        assertEquals(expected, PluginHelper.getCustomPluginConfigPath());
+    }
+
+    @Test
+    public void testCustomPluginConfigPathUsedByGetPluginConfig() throws 
Exception {
+        Path customConfig = tempDir.resolve("custom-plugins.json");
+        String configContent = """
+                {
+                  "plugins": {
+                    "custom-plugin": {
+                      "name": "custom-plugin",
+                      "command": "custom",
+                      "description": "Custom plugin"
+                    }
+                  }
+                }
+                """;
+        Files.writeString(customConfig, configContent, 
StandardOpenOption.CREATE);
+
+        JsonObject config = PluginHelper.getPluginConfig(customConfig);
+        assertNotNull(config);
+        JsonObject plugins = config.getMap("plugins");
+        assertNotNull(plugins);
+        assertNotNull(plugins.getMap("custom-plugin"));
+    }
+
+    @Test
+    public void testCustomPluginConfigPathUsedByCreatePluginConfig() {
+        Path customConfig = 
tempDir.resolve("sub").resolve("custom-plugins.json");
+
+        JsonObject config = PluginHelper.createPluginConfig(customConfig);
+        assertNotNull(config);
+        assertTrue(Files.exists(customConfig));
+    }
+
+    @Test
+    public void testCustomPluginConfigPathUsedBySavePluginConfig() throws 
Exception {
+        Path customConfig = tempDir.resolve("save-plugins.json");
+
+        PluginHelper.createPluginConfig(customConfig);
+
+        JsonObject config = PluginHelper.getPluginConfig(customConfig);
+        assertNotNull(config);
+        JsonObject plugins = config.getMap("plugins");
+        plugins.put("saved-plugin", new JsonObject());
+
+        PluginHelper.savePluginConfig(config, customConfig);
+
+        String saved = Files.readString(customConfig);
+        assertTrue(saved.contains("saved-plugin"));
+    }
+
+    @Test
+    public void testCustomPathIsolatesFromDefaultConfig() throws Exception {
+        Path defaultConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG_FILE);
+        String defaultContent = """
+                {
+                  "plugins": {
+                    "default-plugin": {
+                      "name": "default-plugin",
+                      "command": "default",
+                      "description": "Default plugin"
+                    }
+                  }
+                }
+                """;
+        Files.writeString(defaultConfig, defaultContent, 
StandardOpenOption.CREATE);
+
+        Path customConfig = tempDir.resolve("isolated-plugins.json");
+        String customContent = """
+                {
+                  "plugins": {
+                    "isolated-plugin": {
+                      "name": "isolated-plugin",
+                      "command": "isolated",
+                      "description": "Isolated plugin"
+                    }
+                  }
+                }
+                """;
+        Files.writeString(customConfig, customContent, 
StandardOpenOption.CREATE);
+
+        JsonObject config = PluginHelper.getPluginConfig(customConfig);
+        assertNotNull(config);
+        JsonObject plugins = config.getMap("plugins");
+        assertNotNull(plugins.getMap("isolated-plugin"));
+        assertNull(plugins.getMap("default-plugin"));
+    }
+
     private void writeConfig(JsonObject pluginEntry) throws Exception {
         JsonObject plugins = new JsonObject();
         plugins.put(pluginEntry.getString("name"), pluginEntry);
         JsonObject config = new JsonObject();
         config.put("plugins", plugins);
-        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG);
+        Path userConfig = 
CommandLineHelper.getHomeDir().resolve(PluginHelper.PLUGIN_CONFIG_FILE);
         Files.writeString(userConfig, config.toJson(), 
StandardOpenOption.CREATE);
     }
 

Reply via email to