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); }
