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