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

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


The following commit(s) were added to refs/heads/main by this push:
     new cdccd72d98f8 CAMEL-26931: camel-jbang - a local camel-cli.properties 
is laid over the global one instead of hiding it (#29917)
cdccd72d98f8 is described below

commit cdccd72d98f8f99c85e678b45b62b2c1304985e8
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 14 19:23:55 2026 +0100

    CAMEL-26931: camel-jbang - a local camel-cli.properties is laid over the 
global one instead of hiding it (#29917)
    
    The CLI used the local ./camel-cli.properties when it existed, else the 
global
    ~/.camel-cli.properties, so a project file with one key dropped the global 
repos,
    camel-version or runtime. The plugin repos, the printed configuration and 
the plugin
    banner read only the global file.
    
    CommandLineHelper.loadUserConfiguration loads the global file with the 
local one
    laid over it (a local key wins, the other global keys stay); the option 
defaults
    and those readers use it. A file that cannot be parsed is left out with a 
warning.
    
    CAMEL-26931: camel-jbang - the merged user configuration keeps the order of 
the files, and its Javadoc tags
    
    CAMEL-26931: camel-jbang - the merged user configuration keeps the order of 
the files, and loadUserConfiguration documents its parameter and result
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  7 +++
 .../ROOT/pages/camel-jbang-configuration.adoc      |  3 +
 .../dsl/jbang/core/commands/CamelCommand.java      |  4 +-
 .../dsl/jbang/core/commands/CamelJBangMain.java    | 12 +---
 .../dsl/jbang/core/common/CommandLineHelper.java   | 63 ++++++++++++--------
 .../camel/dsl/jbang/core/common/PluginHelper.java  |  5 +-
 .../config/UserConfigurationMergeTest.java         | 68 ++++++++++++++++++++++
 7 files changed, 123 insertions(+), 39 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 910293f7c017..f618b71cd1db 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -1619,6 +1619,13 @@ Inserting a `List` of `Map` now fails with a 
`ClickHouseException` that says why
 class is registered on the `Client` for the table. Before, both failed with a 
`NullPointerException` from the ClickHouse
 client.
 
+=== camel-jbang - a local camel-cli.properties no longer hides the global one
+
+When the current directory has a `camel-cli.properties`, the Camel CLI used it 
instead of the global
+`~/.camel-cli.properties`, so a project file with one key dropped all the 
global settings, such as `repos`,
+`camel-version` or `runtime`. The local file is now laid over the global one: 
its keys win, and the other global keys
+still apply.
+
 === camel-clickup - an event of another kind than taskTimeTrackedUpdated 
reaches the route
 
 A webhook event of another kind than `taskTimeTrackedUpdated`, such as 
`taskCreated`, now reaches the route as a
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
index 40ee90b06334..34a662127b5e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
@@ -83,6 +83,9 @@ Camel CLI uses two configuration files:
 - Global: `~/.camel-cli.properties`
 - Local: `./camel-cli.properties` (takes precedence)
 
+The local file is laid over the global one: a key of the local file wins, and 
the other keys of the global file
+still apply. With `repos` in the global file and `runtime` in the local one, a 
command in that directory uses both.
+
 Commands target global by default. Use `--global=false` for local 
configuration.
 
 NOTE: These files were previously named `.camel-jbang-user.properties` 
(global) and
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
index b0ddd85fe223..608d4ad24496 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
@@ -365,8 +365,8 @@ public abstract class CamelCommand implements 
Command<CommandInvocation>, Comman
 
     protected void printConfigurationValues(String header) {
         if (processedCommand != null) {
-            final Properties configProperties = new Properties();
-            CommandLineHelper.loadProperties(configProperties::putAll);
+            // the global user configuration with the local one laid over it, 
as the option defaults come from it
+            final Properties configProperties = 
CommandLineHelper.loadUserConfiguration(printer());
             List<String> lines = new ArrayList<>();
             for (ProcessedOption opt : processedCommand.getOptions()) {
                 Object holder = holderOf(opt);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index 71392d5cbc77..ebeaa0e54ad0 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -621,15 +621,9 @@ public class CamelJBangMain implements 
Command<CommandInvocation> {
     }
 
     void printAvailablePlugins() {
-        // check if the plugin banner is disabled via global config
-        boolean[] enabled = { true };
-        CommandLineHelper.loadProperties(properties -> {
-            String v = properties.getProperty("camel.jbang.plugin.banner");
-            if ("false".equalsIgnoreCase(v)) {
-                enabled[0] = false;
-            }
-        });
-        if (!enabled[0]) {
+        // check if the plugin banner is disabled via the user configuration
+        String banner = 
CommandLineHelper.loadUserConfiguration(getOut()).getProperty("camel.jbang.plugin.banner");
+        if ("false".equalsIgnoreCase(banner)) {
             return;
         }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CommandLineHelper.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CommandLineHelper.java
index d7f87e1e4e8a..336ec2943539 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CommandLineHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CommandLineHelper.java
@@ -136,36 +136,49 @@ public final class CommandLineHelper {
     }
 
     public static void augmentWithUserConfiguration(CommandTree commandLine, 
Printer printer) {
-        Path file = getUserConfigurationFile();
-        if (Files.isRegularFile(file) && Files.exists(file)) {
-            Properties properties = new Properties();
-            try (Reader reader = Files.newBufferedReader(file, 
StandardCharsets.UTF_8)) {
-                properties.load(reader);
-            } catch (Exception e) {
-                // best-effort: a malformed config file (e.g. malformed 
unicode escape) must never break the CLI;
-                // warn the user so they know to fix it, but let the command 
continue without defaults.
-                // Warn once per path: loadProperties may also warn for the 
same file, so we deduplicate here.
-                String key = file.toAbsolutePath().toString();
-                if (WARNED_PATHS.add(key)) {
-                    printer.printErr(
-                            "Warning: could not parse '" + 
file.toAbsolutePath() + "' (" + e.getMessage()
-                                     + "). Edit the file by hand, escaping 
'\\' as '\\\\' or using '/'.");
-                }
-                return;
-            }
-
+        Properties properties = loadUserConfiguration(printer);
+        if (!properties.isEmpty()) {
             commandLine.setDefaultValueProvider(new 
CamelUserConfigDefaultValueProvider(properties));
         }
     }
 
-    private static Path getUserConfigurationFile() {
-        Path file;
-        if (Files.exists(Paths.get(LOCAL_USER_CONFIG))) {
-            file = Paths.get(LOCAL_USER_CONFIG);
-        } else {
-            file = getUserPropertyFile(false);
+    /**
+     * The user configuration as the commands use it: the global file in the 
user home ({@value #USER_CONFIG}), with the
+     * local file of the current directory ({@value #LOCAL_USER_CONFIG}) laid 
over it, so a key of the local file wins.
+     * Before, a local file hid the whole global one, such as its repos or 
camel-version (CAMEL-26931). A file that
+     * cannot be parsed is left out, with a warning (once per file).
+     *
+     * @param  printer where a file that cannot be parsed is reported (once 
per file)
+     * @return         the configuration, empty when neither file exists
+     */
+    public static Properties loadUserConfiguration(Printer printer) {
+        Properties answer = new OrderedProperties();
+        readInto(getUserPropertyFile(false), answer, printer);
+        readInto(Paths.get(LOCAL_USER_CONFIG), answer, printer);
+        return answer;
+    }
+
+    private static void readInto(Path file, Properties answer, Printer 
printer) {
+        if (!Files.isRegularFile(file)) {
+            return;
+        }
+        // in the order of the file, as the configuration keeps it
+        Properties properties = new OrderedProperties();
+        try (Reader reader = Files.newBufferedReader(file, 
StandardCharsets.UTF_8)) {
+            properties.load(reader);
+        } catch (Exception e) {
+            // best-effort: a malformed config file (e.g. malformed unicode 
escape) must never break the CLI;
+            // warn the user so they know to fix it, but let the command 
continue without its defaults.
+            // Warn once per path: loadProperties may also warn for the same 
file, so we deduplicate here.
+            String key = file.toAbsolutePath().toString();
+            if (WARNED_PATHS.add(key)) {
+                printer.printErr(
+                        "Warning: could not parse '" + file.toAbsolutePath() + 
"' (" + e.getMessage()
+                                 + "). Edit the file by hand, escaping '\\' as 
'\\\\' or using '/'.");
+            }
+            return;
         }
-        return file;
+        answer.putAll(properties);
     }
 
     public static void createPropertyFile(boolean local) throws IOException {
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 cc9826f67682..faf49f4d1233 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
@@ -325,9 +325,8 @@ public final class PluginHelper {
         }
 
         if (repos == null) {
-            // fallback to load user configuration
-            Properties configProperties = new Properties();
-            CommandLineHelper.loadProperties(configProperties::putAll);
+            // fallback to load user configuration (the global file with the 
local one laid over it)
+            Properties configProperties = 
CommandLineHelper.loadUserConfiguration(new Printer.SystemOutPrinter());
             repos = configProperties.getProperty("repos");
             if (repos == null) {
                 repos = configProperties.getProperty("repo");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/config/UserConfigurationMergeTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/config/UserConfigurationMergeTest.java
new file mode 100644
index 000000000000..e671f6aced11
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/config/UserConfigurationMergeTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.config;
+
+import java.util.Properties;
+
+import org.apache.camel.dsl.jbang.core.commands.UserConfigHelper;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.run.common.StringPrinter;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The local ./camel-cli.properties is laid over the global 
~/.camel-cli.properties: before, it hid the whole global
+ * file, such as its repos (CAMEL-26931).
+ */
+class UserConfigurationMergeTest extends BaseConfigTestSupport {
+
+    @Test
+    void theLocalFileIsLaidOverTheGlobalOne() throws Exception {
+        
UserConfigHelper.createUserConfig("repos=https://repo.acme.com/maven\ncamel-version=4.22.0\nruntime=main\n";);
+        UserConfigHelper.createUserConfig("runtime=quarkus\n", true);
+
+        Properties config = CommandLineHelper.loadUserConfiguration(printer);
+
+        // the global keys the local file does not have stay
+        
assertThat(config.getProperty("repos")).isEqualTo("https://repo.acme.com/maven";);
+        assertThat(config.getProperty("camel-version")).isEqualTo("4.22.0");
+        // a key of the local file wins
+        assertThat(config.getProperty("runtime")).isEqualTo("quarkus");
+    }
+
+    @Test
+    void theGlobalFileAloneIsTheConfiguration() throws Exception {
+        UserConfigHelper.createUserConfig("runtime=spring-boot\n");
+
+        
assertThat(CommandLineHelper.loadUserConfiguration(printer)).containsEntry("runtime",
 "spring-boot")
+                .hasSize(1);
+    }
+
+    @Test
+    void aLocalFileThatCannotBeParsedLeavesTheGlobalOne() throws Exception {
+        
UserConfigHelper.createUserConfig("repos=https://repo.acme.com/maven\n";);
+        // a malformed unicode escape, such as a Windows path written by hand
+        UserConfigHelper.createUserConfig("path=C:\\users\\name\n", true);
+        StringPrinter warnings = new StringPrinter();
+
+        Properties config = CommandLineHelper.loadUserConfiguration(warnings);
+
+        assertThat(config).containsEntry("repos", 
"https://repo.acme.com/maven";).doesNotContainKey("path");
+        assertThat(warnings.getStderr()).contains("could not 
parse").contains(CommandLineHelper.LOCAL_USER_CONFIG);
+    }
+}

Reply via email to