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

davsclaus pushed a commit to branch fix/CAMEL-25830
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 9ea3161a075ea78a51a52643bf3c5fe773d13e66
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 19 17:12:51 2026 +0200

    CAMEL-25830: camel-jbang - camel run of a whole directory without --name is 
named after the directory
    
    It was named after the first file in the order of the file system, which 
could
    be any file, such as a Java class. A folder of a project layout (src, main,
    resources, java, camel, routes) gives way to the first parent with another 
name.
    Files that are given keep the name of the first file, and --name and
    camel.main.name still win.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  8 +++++
 .../pages/jbang-commands/camel-jbang-debug.adoc    |  2 +-
 .../ROOT/pages/jbang-commands/camel-jbang-dev.adoc |  2 +-
 .../ROOT/pages/jbang-commands/camel-jbang-run.adoc |  2 +-
 .../META-INF/camel-jbang-commands-metadata.json    |  6 ++--
 .../apache/camel/dsl/jbang/core/commands/Run.java  | 37 +++++++++++++++++++++-
 .../camel/dsl/jbang/core/commands/RunTest.java     | 18 +++++++++++
 7 files changed, 68 insertions(+), 7 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 8cd5c733d623..34e6ec1d72d9 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
@@ -1787,3 +1787,11 @@ Content with more than one reading is refused as before.
 to the closest lines of the file when exactly one place matches well. It 
changes there the lines the model changed and
 keeps the file's version of the other lines. The edit stands only when its 
result is valid; otherwise the answer is
 the one of a miss, with the lines the file has there. `matchedLines` in the 
answer names the lines it matched.
+
+=== camel-jbang - the default name of a run of a whole directory
+
+`camel run --source-dir`, and `camel run` of a directory, without `--name` 
name the integration after the directory.
+A folder of a project layout (`src`, `main`, `resources`, `java`, `camel` or 
`routes`) gives way to the first parent
+with another name, and `.` is the working directory. Before, the name was the 
one of the first file, in the order that
+the file system lists them, which could be any file, such as a Java class that 
a route uses. `--name`, and
+`camel.main.name` in application.properties, still win.
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
index e6118b087330..75127cb9001f 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
@@ -67,7 +67,7 @@ camel debug [options]
 | `--mcp-tags` | Comma-separated ai-tool tags to expose on the ai-tool MCP 
server (maps to camel.server.mcp-tags). Use with --mcp-stdio or when 
camel.server.mcp-enabled is configured for HTTP transport. Not used by --mcp 
(dev/diagnostics MCP). |  | String
 | `--metrics` _(deprecated)_ | Deprecated: use --observe instead. Metrics 
(Micrometer and Prometheus) at /q/metrics on local HTTP server (port 8080 by 
default) | false | boolean
 | `--modeline` | Whether to support JBang style //DEPS to specify additional 
dependencies | true | boolean
-| `--name` | The name of the Camel application | CamelJBang | String
+| `--name` | The name of the Camel application. By default the name of the 
directory when running a whole directory (--source-dir, or a directory), else 
the name of the first file | CamelJBang | String
 | `--observe` | Enable observability services (health, metrics, dev console, 
and lightweight Camel-only tracing in the TUI Spans tab) | false | boolean
 | `--open-api` | Adds an OpenAPI spec from the given file (json or yaml file) 
|  | String
 | `--open-telemetry-agent` | Enable OpenTelemetry Java Agent for 
auto-instrumentation of third-party libraries (HTTP clients, JDBC, Kafka 
clients, gRPC, etc.). Traces are shown in the TUI Spans tab with Camel spans in 
cyan and 3rd-party agent spans in magenta. | false | boolean
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
index 705c8bae7259..021bf53e0e87 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
@@ -63,7 +63,7 @@ camel dev [options]
 | `--mcp-tags` | Comma-separated ai-tool tags to expose on the ai-tool MCP 
server (maps to camel.server.mcp-tags). Use with --mcp-stdio or when 
camel.server.mcp-enabled is configured for HTTP transport. Not used by --mcp 
(dev/diagnostics MCP). |  | String
 | `--metrics` _(deprecated)_ | Deprecated: use --observe instead. Metrics 
(Micrometer and Prometheus) at /q/metrics on local HTTP server (port 8080 by 
default) | false | boolean
 | `--modeline` | Whether to support JBang style //DEPS to specify additional 
dependencies | true | boolean
-| `--name` | The name of the Camel application | CamelJBang | String
+| `--name` | The name of the Camel application. By default the name of the 
directory when running a whole directory (--source-dir, or a directory), else 
the name of the first file | CamelJBang | String
 | `--observe` | Enable observability services (health, metrics, dev console, 
and lightweight Camel-only tracing in the TUI Spans tab) | false | boolean
 | `--open-api` | Adds an OpenAPI spec from the given file (json or yaml file) 
|  | String
 | `--open-telemetry-agent` | Enable OpenTelemetry Java Agent for 
auto-instrumentation of third-party libraries (HTTP clients, JDBC, Kafka 
clients, gRPC, etc.). Traces are shown in the TUI Spans tab with Camel spans in 
cyan and 3rd-party agent spans in magenta. | false | boolean
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
index 9924d6fc4309..51405a1c31f8 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
@@ -63,7 +63,7 @@ camel run [options]
 | `--mcp-tags` | Comma-separated ai-tool tags to expose on the ai-tool MCP 
server (maps to camel.server.mcp-tags). Use with --mcp-stdio or when 
camel.server.mcp-enabled is configured for HTTP transport. Not used by --mcp 
(dev/diagnostics MCP). |  | String
 | `--metrics` _(deprecated)_ | Deprecated: use --observe instead. Metrics 
(Micrometer and Prometheus) at /q/metrics on local HTTP server (port 8080 by 
default) | false | boolean
 | `--modeline` | Whether to support JBang style //DEPS to specify additional 
dependencies | true | boolean
-| `--name` | The name of the Camel application | CamelJBang | String
+| `--name` | The name of the Camel application. By default the name of the 
directory when running a whole directory (--source-dir, or a directory), else 
the name of the first file | CamelJBang | String
 | `--observe` | Enable observability services (health, metrics, dev console, 
and lightweight Camel-only tracing in the TUI Spans tab) | false | boolean
 | `--open-api` | Adds an OpenAPI spec from the given file (json or yaml file) 
|  | String
 | `--open-telemetry-agent` | Enable OpenTelemetry Java Agent for 
auto-instrumentation of third-party libraries (HTTP clients, JDBC, Kafka 
clients, gRPC, etc.). Traces are shown in the TUI Spans tab with Camel spans in 
cyan and 3rd-party agent spans in magenta. | false | 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 fcf519c28b5a..1dd87eec1c65 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
@@ -6,9 +6,9 @@
     { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
     { "name": "completion", "fullName": "completion", "description": "Generate 
completion script for bash\/zsh", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ] },
     { "name": "config", "fullName": "config", "description": "Get and set user 
configuration values", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "config get", "description": "Display user configuration value", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
-    { "name": "debug", "fullName": "debug", "description": "Debug local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", 
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd 
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background", "description": "Run in the background", "defaultValue": 
"false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "How [...]
+    { "name": "debug", "fullName": "debug", "description": "Debug local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", 
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd 
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background", "description": "Run in the background", "defaultValue": 
"false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "How [...]
     { "name": "dependency", "fullName": "dependency", "description": "Displays 
all Camel dependencies required to run", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"copy", "fullName": "dependency copy", "description": "Copies all Camel 
dependencies required to run to a specific directory", "sourc [...]
-    { "name": "dev", "fullName": "dev", "description": "Run in dev mode with 
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev", 
"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 secon [...]
+    { "name": "dev", "fullName": "dev", "description": "Run in dev mode with 
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev", 
"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 secon [...]
     { "name": "dirty", "fullName": "dirty", "description": "Check if there are 
dirty files from previous Camel runs that did not terminate gracefully", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty", 
"options": [ { "names": "--clean", "description": "Clean dirty files which are 
no longer in use", "defaultValue": "false", "javaType": "boolean", "type": 
"boolean" }, { "names": "-h,--help", "description": "Display the help and 
sub-commands", "javaType": "boolean", " [...]
     { "name": "doc", "fullName": "doc", "description": "Shows documentation 
for kamelet, component, EIP, and other Camel resources", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ { 
"names": "--camel-version", "description": "To use a different Camel version 
than the default version", "javaType": "java.lang.String", "type": "string" }, 
{ "names": "--download", "description": "Whether to allow automatic downloading 
JAR dependencies (over the int [...]
     { "name": "doctor", "fullName": "doctor", "description": "Checks the 
environment and reports potential issues", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Doctor", "options": [ { "names": 
"--json", "description": "Output in JSON Format: the status (OK, WARN or FAIL) 
of each check", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ] },
@@ -27,7 +27,7 @@
     { "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": "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 [...]
+    { "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 [...]
     { "name": "sbom", "fullName": "sbom", "description": "Generate a CycloneDX 
or SPDX SBOM for a specific project", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.SBOMGenerator", "options": [ { 
"names": "--build-property", "description": "Maven build properties, ex. 
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { 
"names": "--camel-spring-boot-version", "description": "Camel version to use 
with Spring Boot", "javaType": "java.lang.String", "type" [...]
     { "name": "script", "fullName": "script", "description": "Run Camel 
integration as shell script for terminal scripting", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Script", "options": [ { "names": 
"--logging", "description": "Can be used to turn on logging (logs to file in 
<user home>\/.camel directory)", "defaultValue": "false", "javaType": 
"boolean", "type": "boolean" }, { "names": "--logging-level", "description": 
"Logging level (ERROR, WARN, INFO, DEBUG, TRACE)", "d [...]
     { "name": "semantic", "fullName": "semantic", "description": "List 
semantic definitions and expert contracts", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelSemantic", "options": [ { 
"names": "--expert", "description": "Show the operations and parameter contract 
of this expert", "javaType": "java.lang.String", "type": "string" }, { "names": 
"--json", "description": "Output a single JSON document", "javaType": 
"boolean", "type": "boolean" }, { "names": "--timeou [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index c81ca8ef00ee..c453e0a0f82b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -305,7 +305,9 @@ public class Run extends CamelCommand {
             description = "Additional JVM arguments (e.g. -Xmx256m -Xms128m)")
     String jvmArgs;
 
-    @Option(names = { "--name" }, defaultValue = "CamelJBang", description = 
"The name of the Camel application")
+    @Option(names = { "--name" }, defaultValue = "CamelJBang",
+            description = "The name of the Camel application. By default the 
name of the directory when running a whole directory"
+                          + " (--source-dir, or a directory), else the name of 
the first file")
     String name;
 
     @Option(names = { "--exclude" }, description = "Exclude files by name or 
pattern")
@@ -1173,6 +1175,15 @@ public class Run extends CamelCommand {
             }
         }
 
+        if ("CamelJBang".equals(name) && sourceDir != null && !exportRun && 
!transformRun) {
+            // a whole directory is one app, named after the directory 
(CAMEL-25830): its first file depends on the
+            // order of the file system, and can be any file, such as a Java 
class that a route uses
+            String dirName = directoryName(sourceDir);
+            if (dirName != null) {
+                name = dirName;
+            }
+        }
+
         for (String file : files) {
             if (file.startsWith("clipboard") && 
!(Files.exists(Paths.get(file)))) {
                 file = loadFromClipboard(file);
@@ -3050,6 +3061,30 @@ public class Run extends CamelCommand {
         }
     }
 
+    /**
+     * The folders of a project layout, whose name says nothing about the app 
(CAMEL-25830).
+     */
+    private static final Set<String> LAYOUT_FOLDERS = Set.of("src", "main", 
"resources", "java", "camel", "routes");
+
+    /**
+     * The name of an app that is a whole directory: the name of the 
directory, or of the first parent with a name that
+     * is not one of the folders of a project layout ({@code 
src/main/resources/camel} is named after the project), and
+     * {@code .} is the working directory.
+     *
+     * @return the name, or <tt>null</tt> when the directory and its parents 
have no such name
+     */
+    static String directoryName(String dir) {
+        Path path = Paths.get(dir).toAbsolutePath().normalize();
+        while (path != null && path.getFileName() != null) {
+            String answer = path.getFileName().toString();
+            if (!LAYOUT_FOLDERS.contains(answer.toLowerCase(Locale.ROOT))) {
+                return answer;
+            }
+            path = path.getParent();
+        }
+        return null;
+    }
+
     protected int runKameletMain(KameletMain main) throws Exception {
         Recording jfrRecording = startJfrRecording();
         try {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java
index 317c5ca1b55b..84b04bfc7616 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java
@@ -17,11 +17,14 @@
 
 package org.apache.camel.dsl.jbang.core.commands;
 
+import java.nio.file.Path;
+
 import org.apache.camel.dsl.jbang.core.common.RuntimeType;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.main.KameletMain;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import picocli.CommandLine;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -110,6 +113,21 @@ class RunTest extends CamelCommandBaseTestSupport {
         assertThat(command.hasNoRoutes(main)).isFalse();
     }
 
+    @Test
+    void aWholeDirectoryIsNamedAfterTheDirectory(@TempDir Path dir) {
+        // CAMEL-25830
+        Path project = dir.resolve("orders-app");
+        
assertThat(Run.directoryName(project.toString())).isEqualTo("orders-app");
+        // the folders of a project layout say nothing about the app: the 
project does
+        
assertThat(Run.directoryName(project.resolve("src/main/resources/camel").toString())).isEqualTo("orders-app");
+        
assertThat(Run.directoryName(project.resolve("src/main/java").toString())).isEqualTo("orders-app");
+        
assertThat(Run.directoryName(project.resolve("routes").toString())).isEqualTo("orders-app");
+        // the working directory
+        
assertThat(Run.directoryName(".")).isEqualTo(Path.of("").toAbsolutePath().getFileName().toString());
+        // no name to give
+        assertThat(Run.directoryName("/src")).isNull();
+    }
+
     @Test
     public void shouldListExamples() throws Exception {
         Run command = new Run(new CamelJBangMain().withPrinter(printer));

Reply via email to