This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-kit.git
commit 42acfe141fd117fc9d148d9269a9b486ffefd472 Author: Jinal Sarvaiya <[email protected]> AuthorDate: Mon Nov 9 09:14:24 2026 +0000 fix #99: scan raw config for substitutions, platform-specific managed dir --- .../camel/kit/generator/OpenCodeGenerator.java | 65 ++++++++++------ .../camel/kit/generator/OpenCodeGeneratorTest.java | 86 ++++++++++++++++++++++ 2 files changed, 130 insertions(+), 21 deletions(-) diff --git a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java index 6d7201b6..5c8a6ac4 100644 --- a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java +++ b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java @@ -6,6 +6,7 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -49,14 +50,13 @@ public class OpenCodeGenerator extends DefaultGenerator { Map.of( "COMMAND_PREFIX", ctx.commandPrefix())); - List<String> mcpServers = readMcpServerNames(ctx); + McpInventory inventory = readMcpInventory(ctx); Set<String> seen = new LinkedHashSet<>(); boolean collisionDetected = false; - boolean unresolvedSubstitution = false; StringBuilder denials = new StringBuilder(); - for (String server : mcpServers) { + for (String server : inventory.servers) { if (SUBSTITUTION_PATTERN.matcher(server).find()) { - unresolvedSubstitution = true; + inventory = new McpInventory(inventory.servers, true); continue; } String normalized = normalizeServerName(server); @@ -69,7 +69,7 @@ public class OpenCodeGenerator extends DefaultGenerator { data.put("MCP_TOOL_DENIALS", denials.toString()); // OpenCode-specific: generate agent definitions - generateAgents(ctx, data, !collisionDetected && !unresolvedSubstitution); + generateAgents(ctx, data, !collisionDetected && !inventory.hasUnresolvedSubstitution); } static String normalizeServerName(String name) { @@ -85,45 +85,61 @@ public class OpenCodeGenerator extends DefaultGenerator { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-'; } - private List<String> readMcpServerNames(InitContext ctx) throws IOException { + record McpInventory(List<String> servers, boolean hasUnresolvedSubstitution) { + } + + private McpInventory readMcpInventory(InitContext ctx) throws IOException { Set<String> names = new LinkedHashSet<>(); + boolean hasSubstitution = false; for (Path configFile : OpenCodeProjectConfig.existingFiles(ctx.projectDir())) { - collectMcpServersFromFile(configFile, names); + hasSubstitution |= collectFromFileWithSubstitutionCheck(configFile, names); } for (Path ancestorFile : ancestorConfigFiles(ctx.projectDir())) { - collectMcpServersFromFile(ancestorFile, names); + hasSubstitution |= collectFromFileWithSubstitutionCheck(ancestorFile, names); } for (Path globalFile : globalConfigFiles()) { - collectMcpServersFromFile(globalFile, names); + hasSubstitution |= collectFromFileWithSubstitutionCheck(globalFile, names); } - collectMcpServersFromDir(customConfigDir(), names); - collectMcpServersFromDir(managedConfigDir(), names); + hasSubstitution |= collectFromDirWithSubstitutionCheck(customConfigDir(), names); + hasSubstitution |= collectFromDirWithSubstitutionCheck(managedConfigDir(), names); Path explicitConfig = explicitConfigFile(); if (explicitConfig != null) { - collectMcpServersFromFile(explicitConfig, names); + hasSubstitution |= collectFromFileWithSubstitutionCheck(explicitConfig, names); } String inlineContent = inlineConfigContent(); if (inlineContent != null && !inlineContent.isBlank()) { + if (SUBSTITUTION_PATTERN.matcher(inlineContent).find()) { + hasSubstitution = true; + } collectMcpServersFromContent(inlineContent, names); } - return List.copyOf(names); + return new McpInventory(List.copyOf(names), hasSubstitution); + } + + private static boolean collectFromFileWithSubstitutionCheck(Path configFile, Set<String> names) + throws IOException { + String rawContent = Files.readString(configFile); + if (rawContent.startsWith("")) { + rawContent = rawContent.substring(1); + } + boolean hasSubstitution = SUBSTITUTION_PATTERN.matcher(rawContent).find(); + collectMcpServersFromContent(rawContent, names); + return hasSubstitution; } - private static void collectMcpServersFromDir(Path dir, Set<String> names) throws IOException { + private static boolean collectFromDirWithSubstitutionCheck(Path dir, Set<String> names) + throws IOException { if (dir == null) { - return; + return false; } + boolean hasSubstitution = false; for (String fileName : GLOBAL_CONFIG_FILES) { Path candidate = dir.resolve(fileName); if (Files.exists(candidate)) { - collectMcpServersFromFile(candidate, names); + hasSubstitution |= collectFromFileWithSubstitutionCheck(candidate, names); } } - } - - static void collectMcpServersFromFile(Path configFile, Set<String> names) throws IOException { - collectMcpServersFromNode( - OpenCodeProjectConfig.newJsonMapper().readTree(configFile.toFile()), names); + return hasSubstitution; } static void collectMcpServersFromContent(String jsonContent, Set<String> names) throws IOException { @@ -213,6 +229,13 @@ public class OpenCodeGenerator extends DefaultGenerator { } Path managedConfigDir() { + String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); + if (os.contains("mac") || os.contains("darwin")) { + return Path.of("/Library/Application Support/opencode"); + } else if (os.contains("win")) { + String programData = System.getenv("ProgramData"); + return Path.of(programData != null ? programData : "C:\\ProgramData", "opencode"); + } return Path.of("/etc/opencode"); } diff --git a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/OpenCodeGeneratorTest.java b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/OpenCodeGeneratorTest.java index 353b99ed..b19b9df0 100644 --- a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/OpenCodeGeneratorTest.java +++ b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/OpenCodeGeneratorTest.java @@ -1410,4 +1410,90 @@ class OpenCodeGeneratorTest { assertFalse(Files.exists(workerFile), "Ship worker must not be generated when project config has unresolved substitution"); } + + @Test + void shipWorkerNotGeneratedWhenEnclosingKeyHasEnvSubstitution() throws Exception { + OpenCodeGenerator gen = new OpenCodeGenerator() { + @Override + String inlineConfigContent() { + return """ + { + "{env:MCP_SECTION}": { + "probe": {"type": "local", "command": ["echo"]} + } + } + """; + } + }; + InitContext ctx = createContext(); + gen.generate(ctx); + + Path workerFile = tempDir.resolve(".opencode/agents/camel-ship-worker.md"); + assertFalse(Files.exists(workerFile), + "Ship worker must not be generated when enclosing config key has env substitution"); + } + + @Test + void shipWorkerNotGeneratedWhenEnclosingKeyHasFileSubstitution() throws Exception { + OpenCodeGenerator gen = new OpenCodeGenerator() { + @Override + String inlineConfigContent() { + return """ + { + "{file:section.txt}": { + "probe": {"type": "local", "command": ["echo"]} + } + } + """; + } + }; + InitContext ctx = createContext(); + gen.generate(ctx); + + Path workerFile = tempDir.resolve(".opencode/agents/camel-ship-worker.md"); + assertFalse(Files.exists(workerFile), + "Ship worker must not be generated when enclosing config key has file substitution"); + } + + @Test + void shipWorkerNotGeneratedWhenFileConfigHasSubstitutionInKey() throws Exception { + Path configWithSub = tempDir.resolve("sub-config.json"); + Files.writeString(configWithSub, """ + { + "{env:MCP_KEY}": { + "probe": {"type": "local", "command": ["echo"]} + } + } + """); + + OpenCodeGenerator gen = new OpenCodeGenerator() { + @Override + Path explicitConfigFile() { + return configWithSub; + } + }; + InitContext ctx = createContext(); + gen.generate(ctx); + + Path workerFile = tempDir.resolve(".opencode/agents/camel-ship-worker.md"); + assertFalse(Files.exists(workerFile), + "Ship worker must not be generated when file config has substitution in enclosing key"); + } + + @Test + void managedConfigDirIsPlatformSpecific() { + OpenCodeGenerator gen = new OpenCodeGenerator(); + Path dir = gen.managedConfigDir(); + String os = System.getProperty("os.name", "").toLowerCase(java.util.Locale.ROOT); + if (os.contains("mac") || os.contains("darwin")) { + assertEquals(Path.of("/Library/Application Support/opencode"), dir, + "macOS managed dir must use /Library/Application Support/opencode"); + } else if (os.contains("win")) { + assertTrue(dir.toString().endsWith("opencode"), + "Windows managed dir must end with opencode"); + } else { + assertEquals(Path.of("/etc/opencode"), dir, + "Linux managed dir must use /etc/opencode"); + } + } }
