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 be0857aab86c chore: camel-jbang - the AI tools see the beans in 
src/main/resources, catch a route id another file has, and say why a start 
failed (#28001)
be0857aab86c is described below

commit be0857aab86c93fe0a03e86a43072350cc407c4c
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 17 18:16:32 2026 +0200

    chore: camel-jbang - the AI tools see the beans in src/main/resources, 
catch a route id another file has, and say why a start failed (#28001)
    
    Two fixes for the AI tools, found with the local-model benchmark, where the
    step that adds a Java class and declares it as a bean failed in 8 of 10 
runs.
    
    - Bean declarations also come from the YAML in src/main/resources, which 
camel
      run --source-dir loads, so a reference to a bean declared there is no 
longer
      refused, and a file with only beans (or another top-level entry of the 
YAML
      DSL) is listed with the route files.
    - The validator refuses a route id that a route in another YAML or XML file 
of
      the project has, and names that file. A failed camel_run or restart says 
its
      cause first: the error camel run prints as it exits, else the first 
exception
      logged at ERROR (the cause of the camel_run answer).
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/ai/AuthoringTools.java |  8 ++-
 .../core/commands/ai/IntegrationLauncher.java      | 66 ++++++++++++++++++-
 .../jbang/core/commands/ai/SourceValidator.java    | 75 +++++++++++++++++++---
 .../jbang/core/commands/ai/AuthoringToolsTest.java | 15 +++++
 .../core/commands/ai/IntegrationLauncherTest.java  | 50 +++++++++++++++
 .../commands/ai/SourceValidatorBeanRefsTest.java   | 40 ++++++++++++
 .../commands/ai/SourceValidatorRoutesTest.java     | 33 ++++++++++
 7 files changed, 275 insertions(+), 12 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
index 048a1faa09de..20e0f79b2692 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
@@ -81,9 +81,15 @@ public final class AuthoringTools {
     static final Set<String> SKIPPED_DIRS = Set.of(
             "target", "build", "out", "node_modules", ".git", ".mvn", ".idea", 
".vscode", ".gradle", ".settings",
             ".camel-jbang");
+    // the top-level entries of the YAML DSL: a file with only beans 
(beans.yaml) is loaded with the routes too, and a
+    // model that did not see it among the route files took the beans it 
declares for missing
     private static final Pattern YAML_ROUTE = Pattern.compile(
             
"(?m)^\\s*-\\s*(route|from|rest|routeTemplate|route-template|templatedRoute|templated-route"
-                                                              + 
"|routeConfiguration|route-configuration|kamelet)\\s*:");
+                                                              + 
"|routeConfiguration|route-configuration|kamelet|beans"
+                                                              + 
"|errorHandler|error-handler|onException|on-exception"
+                                                              + 
"|intercept|interceptFrom|intercept-from"
+                                                              + 
"|interceptSendToEndpoint|intercept-send-to-endpoint"
+                                                              + 
"|onCompletion|on-completion)\\s*:");
 
     private AuthoringTools() {
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
index 551d2637c772..031b5a1fc738 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncher.java
@@ -28,6 +28,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
@@ -61,6 +62,13 @@ public final class IntegrationLauncher {
 
     /** A stack frame, or the count of the frames a cause shares with the 
exception it caused. */
     private static final Pattern FRAME = Pattern.compile("^\\s*(at 
\\S.*|\\.\\.\\. \\d+ (more|common frames omitted))$");
+    /** The line camel run prints when it exits with an error. */
+    private static final Pattern CLI_ERROR = Pattern.compile("^ERROR: (.+)$");
+    /** An exception of the console output with its message. */
+    private static final Pattern EXCEPTION_LINE
+            = Pattern.compile("^(?:Caused by: )?[\\w.$]+(?:Exception|Error): 
(.+)$");
+    /** The longest cause put first in an answer; the output that follows has 
all of it. */
+    private static final int MAX_CAUSE = 500;
 
     /**
      * The processes this JVM started from a directory with --source-dir, so a 
copy that has not written its status file
@@ -142,10 +150,16 @@ public final class IntegrationLauncher {
         long deadline = System.currentTimeMillis() + STARTUP_TIMEOUT_MS;
         while (System.currentTimeMillis() < deadline) {
             if (!process.isAlive()) {
+                String cause = failureCause(output);
                 result.put("status", "failed");
                 result.put("exitCode", process.exitValue());
+                if (cause != null) {
+                    result.put("cause", cause);
+                }
                 result.put("output", failureOutput(output));
-                result.put("message", "camel run exited before the integration 
started; the output has the reason");
+                result.put("message", cause != null
+                        ? "camel run exited before the integration started: " 
+ cause
+                        : "camel run exited before the integration started; 
the output has the reason");
                 return result;
             }
             RuntimeHelper.ProcessInfo info = findByPid(pid);
@@ -272,6 +286,49 @@ public final class IntegrationLauncher {
         return null;
     }
 
+    /**
+     * What made a run fail to start, in one line: the error camel run prints 
as it exits ({@code ERROR: ...}), else the
+     * message of the first exception after a record logged at ERROR. The 
output shows it among the startup records,
+     * under the banner; the answer puts it first. A model retried camel_run 
three times with "Duplicate route ids
+     * detected" in the output, and never changed the file that held the copy 
of the route. Null when the output has
+     * neither.
+     */
+    static String failureCause(Path output) {
+        List<String> lines;
+        try {
+            lines = readText(output).lines().map(String::strip).toList();
+        } catch (IOException e) {
+            return null;
+        }
+        String cause = null;
+        // camel run prints its error last
+        for (int i = lines.size() - 1; i >= 0 && cause == null; i--) {
+            Matcher m = CLI_ERROR.matcher(lines.get(i));
+            if (m.matches()) {
+                cause = m.group(1);
+            }
+        }
+        for (int i = 0; i < lines.size() && cause == null; i++) {
+            if (lines.get(i).contains(" ERROR ")) {
+                for (int j = i + 1; j < Math.min(lines.size(), i + 5) && cause 
== null; j++) {
+                    Matcher m = EXCEPTION_LINE.matcher(lines.get(j));
+                    if (m.matches()) {
+                        cause = m.group(1);
+                    }
+                }
+                if (cause == null) {
+                    int at = lines.get(i).indexOf(" : ");
+                    cause = at > 0 ? lines.get(i).substring(at + 3) : 
lines.get(i);
+                }
+            }
+        }
+        if (cause == null || cause.isBlank()) {
+            return null;
+        }
+        cause = cause.strip();
+        return cause.length() > MAX_CAUSE ? cause.substring(0, MAX_CAUSE) + 
"..." : cause;
+    }
+
     /**
      * The console output of a run that failed to start, as camel_get_log 
shows a log (CAMEL-25364): each record keeps
      * its message, the lines of its exceptions (the exception, its causes, 
and a message over several lines such as the
@@ -565,10 +622,13 @@ public final class IntegrationLauncher {
             }
             if (!p.isAlive()) {
                 boolean kept = stoppedByRestart(directory, name);
+                String cause = failureCause(output);
                 throw new ToolExecutionException(
                         "Restart of " + (name != null ? name : "pid " + pid) + 
" failed with exit code "
-                                                 + p.exitValue()
-                                                 + ", so it is stopped now: 
fix the cause below, then camel_run starts it again"
+                                                 + p.exitValue() + ", so it is 
stopped now"
+                                                 + (cause != null
+                                                         ? ". The cause: " + 
cause + "\nFix it" : ": fix the cause below")
+                                                 + ", then camel_run starts it 
again"
                                                  + (kept ? " (directory " + 
directory + ") under the same name, " + name : "")
                                                  + ".\n" + 
failureOutput(output));
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
index 7982bbfe663a..19001ee8989f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
@@ -143,6 +143,8 @@ public final class SourceValidator {
                 msgs.addAll(OpenApiPathParams.validate(content, directory));
                 // an insert-or-update in the dialect of another database than 
the datasource (CAMEL-25557)
                 msgs.addAll(SqlDialectChecks.validate(content, directory));
+                // a route id that a route in another file of the project has: 
Camel does not start
+                msgs.addAll(duplicateRouteIds(fileName, content, catalog, 
directory));
                 if (checkConsumers) {
                     // a direct: or seda: endpoint no route of the application 
consumes (CAMEL-24955)
                     
msgs.addAll(EndpointConsumerChecks.validateYamlConsumers(content, directory, 
fileName, catalog));
@@ -379,20 +381,20 @@ public final class SourceValidator {
             if (directory == null || !Files.isDirectory(directory)) {
                 return NONE;
             }
+            Path excluded = excludeFile != null ? 
directory.resolve(excludeFile).normalize() : null;
             try (var stream = Files.list(directory)) {
                 List<Path> files = new 
ArrayList<>(stream.filter(Files::isRegularFile).toList());
                 // a class of the project may also sit in the Maven layout, 
src/main/java/<package>/<Class>.java, which
                 // camel run --source-dir compiles too: without it a bean of 
that class was refused as not found
-                Path mavenSources = directory.resolve("src/main/java");
-                if (Files.isDirectory(mavenSources)) {
-                    try (var walk = Files.walk(mavenSources)) {
-                        walk.filter(Files::isRegularFile).filter(q -> 
q.getFileName().toString().endsWith(".java"))
-                                .forEach(files::add);
-                    }
-                }
+                addMavenFiles(directory.resolve("src/main/java"), ".java", 
files);
+                // and the YAML in src/main/resources, which camel run 
--source-dir loads as well (it loads the whole
+                // directory): a model that put the class in src/main/java 
wrote beans.yaml there, 6 times out of 8 in
+                // a benchmark night, and each reference to the bean was then 
refused as not declared
+                addMavenFiles(directory.resolve("src/main/resources"), 
".yaml", files);
+                addMavenFiles(directory.resolve("src/main/resources"), ".yml", 
files);
                 for (Path p : files) {
                     String fn = p.getFileName().toString();
-                    if (fn.equals(excludeFile)) {
+                    if (p.normalize().equals(excluded)) {
                         continue;
                     }
                     String lower = fn.toLowerCase(Locale.ROOT);
@@ -430,6 +432,63 @@ public final class SourceValidator {
             }
             return new BeanDeclarations(names, classes, signatures, 
publicMethods, publicMethodNamesByClass);
         }
+
+        /** Adds the files with the suffix in a directory of the Maven layout, 
subdirectories included, if it exists. */
+        private static void addMavenFiles(Path dir, String suffix, List<Path> 
files) throws IOException {
+            if (Files.isDirectory(dir)) {
+                try (var walk = Files.walk(dir)) {
+                    walk.filter(Files::isRegularFile).filter(q -> 
q.getFileName().toString().endsWith(suffix))
+                            .forEach(files::add);
+                }
+            }
+        }
+    }
+
+    /**
+     * The routes of the content whose id a route in another YAML or XML file 
of the project has. camel run --source-dir
+     * loads every file of the directory, and Camel does not start with two 
routes of one id ("Duplicate route ids
+     * detected"). A model asked to declare a bean in beans.yaml copied the 
route there too: the write went ahead, the
+     * restart failed, and the model ran camel_run again until its calls ran 
out.
+     */
+    static List<String> duplicateRouteIds(String fileName, String content, 
CamelCatalog catalog, Path directory) {
+        // the id of each route of the content, with its line
+        Map<String, Integer> ids = new LinkedHashMap<>();
+        try {
+            for (ProjectRoutes.Route r : ProjectRoutes.parse(fileName, 
content, catalog)) {
+                if ("route".equals(r.kind()) && r.id() != null && 
!r.id().isBlank()) {
+                    ids.putIfAbsent(r.id(), r.line());
+                }
+            }
+        } catch (RuntimeException e) {
+            return List.of();
+        }
+        if (ids.isEmpty()) {
+            return List.of();
+        }
+        Path self = directory.resolve(fileName).normalize();
+        List<String> msgs = new ArrayList<>();
+        for (Path p : AuthoringTools.projectFiles(directory)) {
+            String rel = AuthoringTools.relativePath(directory, p);
+            String type = AuthoringTools.fileType(rel);
+            if (p.normalize().equals(self) || !("yaml".equals(type) || 
"xml".equals(type))
+                    || !"route".equals(AuthoringTools.fileKind(p, rel, type))) 
{
+                continue;
+            }
+            try {
+                for (ProjectRoutes.Route r : ProjectRoutes.parse(rel, 
Files.readString(p), catalog)) {
+                    Integer line = "route".equals(r.kind()) && r.id() != null 
? ids.remove(r.id()) : null;
+                    if (line != null) {
+                        msgs.add("Line " + line + ": route id '" + r.id() + "' 
is already the id of a route in " + rel
+                                 + ", and Camel does not start with two routes 
of one id (camel run loads every file of"
+                                 + " the directory): change the route in " + 
rel + " rather than adding a copy here, or"
+                                 + " give this route another id");
+                    }
+                }
+            } catch (IOException | RuntimeException e) {
+                // an unreadable or unparsable file has no route to compare
+            }
+        }
+        return msgs;
     }
 
     /**
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsTest.java
index 60d970a98157..86a0883b39d3 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsTest.java
@@ -211,6 +211,21 @@ class AuthoringToolsTest {
         assertEquals(VALID_ROUTE, 
Files.readString(dir.resolve("src/main/resources/camel/x.camel.yaml")));
     }
 
+    @Test
+    void aBeansFileIsListedWithTheRouteFiles(@TempDir Path dir) throws 
IOException {
+        // beans.yaml has no route, yet camel run loads it with the routes: a 
model that did not see it among the route
+        // files took the beans it declares for missing
+        Files.writeString(dir.resolve("demo.camel.yaml"), VALID_ROUTE);
+        Files.createDirectories(dir.resolve("src/main/resources"));
+        Files.writeString(dir.resolve("src/main/resources/beans.yaml"), """
+                - beans:
+                    - name: orderNumber
+                      type: camel.example.OrderNumber
+                """);
+        JsonObject list = call("camel_get_files", new ToolContext(), 
Map.of("directory", dir.toString()));
+        assertEquals(List.of("demo.camel.yaml", 
"src/main/resources/beans.yaml"), list.getCollection("routeFiles"));
+    }
+
     @Test
     void aMavenProjectListsItsRouteFilesFirstAndReadsByRelativePath(@TempDir 
Path dir) throws IOException {
         Files.writeString(dir.resolve("pom.xml"), "<project/>");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
index 0d7994b61f72..fcfbd42a33ac 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationLauncherTest.java
@@ -101,6 +101,56 @@ class IntegrationLauncherTest {
         assertThat(text.indexOf("is 
starting")).isLessThan(text.indexOf("Failed to create route"));
     }
 
+    @Test
+    void failureCauseIsTheErrorCamelRunPrintsAsItExits(@TempDir Path dir) 
throws Exception {
+        // the console of camel run with a copy of the route in beans.yaml, 
next to the route file
+        String duplicate = "Failed to create route because: Duplicate route 
ids detected: order-generator. Please correct"
+                           + " ids to be unique among all your routes.";
+        StringBuilder out = new StringBuilder();
+        out.append("Running integration with the following configuration:\n    
--kamelets-version=4.22.1\n");
+        out.append("2026-10-17 11:03:43.201  INFO 81710 --- [           main] 
org.apache.camel.main.MainSupport   :"
+                   + " Apache Camel (JBang) 4.24.0-SNAPSHOT is starting\n");
+        out.append("2026-10-17 11:03:43.640  WARN 81710 --- [           main] 
sl.yaml.common.YamlDeserializerBase :"
+                   + " YAML DSL compact notation detected in: 
file:./beans.yaml\n");
+        out.append("2026-10-17 11:03:43.652 ERROR 81710 --- [           main] 
org.apache.camel.main.MainSupport   :"
+                   + " Error starting Camel: 
org.apache.camel.FailedToCreateRouteException: " + duplicate + "\n");
+        out.append("org.apache.camel.FailedToCreateRouteException: 
").append(duplicate).append('\n');
+        out.append("\tat 
org.apache.camel.main.RoutesConfigurer.addDiscoveredRoutes(RoutesConfigurer.java:385)\n");
+        out.append("\tat main.CamelJBang.main(CamelJBang.java:38)\n");
+        out.append("ERROR: ").append(duplicate).append('\n');
+        out.append("org.apache.camel.FailedToCreateRouteException: 
").append(duplicate).append('\n');
+        out.append("\tat 
org.apache.camel.main.RoutesConfigurer.addDiscoveredRoutes(RoutesConfigurer.java:385)\n");
+        Path output = dir.resolve("camel-launch.log");
+        Files.writeString(output, out.toString());
+
+        
assertThat(IntegrationLauncher.failureCause(output)).isEqualTo(duplicate);
+    }
+
+    @Test
+    void failureCauseWithoutTheErrorLineIsTheFirstLoggedException(@TempDir 
Path dir) throws Exception {
+        Path output = dir.resolve("camel-launch.log");
+        Files.writeString(output, "2026-10-06 01:12:03.101  INFO 81990 --- [   
        main] o.a.c.main.MainSupport  :"
+                                  + " Apache Camel (JBang) 4.24.0-SNAPSHOT is 
starting\n"
+                                  + "2026-10-06 01:12:04.552 ERROR 81990 --- [ 
          main] o.a.c.main.MainSupport  :"
+                                  + " Failed to create route: 
order-generator\n"
+                                  + 
"org.apache.camel.FailedToCreateRouteException: Failed to create route: 
order-generator"
+                                  + " because of No bean could be found in the 
registry for: orderNumber\n"
+                                  + "\tat 
org.apache.camel.impl.engine.AbstractCamelContext.start(AbstractCamelContext.java:1)\n");
+        assertThat(IntegrationLauncher.failureCause(output))
+                .isEqualTo("Failed to create route: order-generator because of 
No bean could be found in the registry for:"
+                           + " orderNumber");
+
+        // an ERROR record without an exception in the lines after it: the 
cause is its message
+        Files.writeString(output, "2026-10-06 01:12:04.552 ERROR 81990 --- [   
        main] o.a.c.main.MainSupport  :"
+                                  + " Route start failed due to missing bean\n"
+                                  + "\tat some.Frame(Frame.java:1)\n");
+        assertThat(IntegrationLauncher.failureCause(output)).isEqualTo("Route 
start failed due to missing bean");
+
+        // nothing that names an error: no cause, the output says what it says
+        Files.writeString(output, "Downloading dependency 
org.example:missing:1.0\n");
+        assertThat(IntegrationLauncher.failureCause(output)).isNull();
+    }
+
     /**
      * CAMEL-25364: an exception message over several lines is kept, such as 
the diagnostics of a Java class that does
      * not compile: they are continuation lines in the same block as the 
frames, and this output is the only copy.
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorBeanRefsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorBeanRefsTest.java
index 9fae923e1f89..ee3feeccd0e3 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorBeanRefsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorBeanRefsTest.java
@@ -127,6 +127,46 @@ public class SourceValidatorBeanRefsTest {
                 .isEmpty();
     }
 
+    @Test
+    void beansDeclaredInTheMavenLayoutAreFound(@TempDir Path dir) throws 
IOException {
+        // camel run --source-dir loads the whole directory, 
src/main/resources included: a model that put the class in
+        // src/main/java wrote beans.yaml in src/main/resources, and each 
reference to the bean was refused as not declared
+        Path pkg = 
Files.createDirectories(dir.resolve("src/main/java/camel/example"));
+        Files.writeString(pkg.resolve("OrderNumber.java"), """
+                package camel.example;
+
+                public class OrderNumber {
+                    public String next() {
+                        return "ORD-1001";
+                    }
+                }
+                """);
+        String route = """
+                - from:
+                    uri: timer:tick
+                    steps:
+                      - setHeader:
+                          name: orderId
+                          simple: "${bean:orderNumber.next}"
+                """;
+        assertThat(SourceValidator.validateYamlBeanRefs(route,
+                SourceValidator.BeanDeclarations.scan(dir, 
"order-generator.camel.yaml"), CATALOG))
+                .anyMatch(m -> m.contains("bean 'orderNumber' is not 
declared"));
+
+        Path resources = 
Files.createDirectories(dir.resolve("src/main/resources"));
+        Files.writeString(resources.resolve("beans.yaml"), """
+                - beans:
+                    - name: orderNumber
+                      type: camel.example.OrderNumber
+                """);
+        assertThat(SourceValidator.validateYamlBeanRefs(route,
+                SourceValidator.BeanDeclarations.scan(dir, 
"order-generator.camel.yaml"), CATALOG))
+                .isEmpty();
+        // the file being validated is left out by its path: its own 
declarations come from its new content
+        assertThat(SourceValidator.BeanDeclarations.scan(dir, 
"src/main/resources/beans.yaml").names())
+                .doesNotContain("orderNumber");
+    }
+
     @Test
     void classReferencesPlaceholdersAndUnknownDirectoryAreLeftAlone(@TempDir 
Path dir) {
         String yaml = ROUTE.replace("aggregationStrategy: myAggregator",
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorRoutesTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorRoutesTest.java
index f28bcf158b74..38509988668a 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorRoutesTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorRoutesTest.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.ai;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -27,6 +30,7 @@ import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -110,4 +114,33 @@ class SourceValidatorRoutesTest {
         assertThat(notChecked).hasSize(1);
         assertThat(notChecked.getString(0)).startsWith("Line 10: not checked: 
").contains("options()");
     }
+
+    @Test
+    void aRouteIdThatAnotherFileOfTheProjectHasIsReported(@TempDir Path dir) 
throws IOException {
+        // a model asked to declare a bean in beans.yaml copied the route 
there too: Camel did not start
+        String route = """
+                - route:
+                    id: order-generator
+                    from:
+                      uri: timer:orders
+                      steps:
+                        - log: "New order"
+                """;
+        Files.writeString(dir.resolve("order-generator.camel.yaml"), route);
+        String beans = """
+                - beans:
+                    - name: orderNumber
+                      type: "#class:java.lang.StringBuilder"
+                """ + route;
+        assertThat(SourceValidator.validate("beans.yaml", beans, catalog, 
null, dir, null, false))
+                .containsExactly("Line 4: route id 'order-generator' is 
already the id of a route in"
+                                 + " order-generator.camel.yaml, and Camel 
does not start with two routes of one id (camel"
+                                 + " run loads every file of the directory): 
change the route in order-generator.camel.yaml"
+                                 + " rather than adding a copy here, or give 
this route another id");
+
+        // the file itself, written again, and a route of another id are fine
+        assertThat(SourceValidator.validate("order-generator.camel.yaml", 
route, catalog, null, dir, null, false)).isEmpty();
+        assertThat(SourceValidator.validate("beans.yaml", beans.replace("id: 
order-generator", "id: other"), catalog, null,
+                dir, null, false)).isEmpty();
+    }
 }

Reply via email to