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 561ddc65ddfc CAMEL-26930: camel-jbang - dev mode watches the folder 
that has all the files, and reloads application.properties (#29946)
561ddc65ddfc is described below

commit 561ddc65ddfc7e1a0d05b3f516412a01996f7030
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 14 19:30:49 2026 +0100

    CAMEL-26930: camel-jbang - dev mode watches the folder that has all the 
files, and reloads application.properties (#29946)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../src/test/resources/golden/expected/dev.run.txt |  4 +-
 .../camel/dsl/jbang/run/CamelMainRunner.java       | 22 +++----
 .../apache/camel/dsl/jbang/run/RunPipeline.java    | 77 ++++++++++++++++------
 .../dsl/jbang/run/RunPipelineReloadFolderTest.java | 77 ++++++++++++++++++++++
 4 files changed, 144 insertions(+), 36 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/expected/dev.run.txt
 
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/expected/dev.run.txt
index 2065f48c2f4a..ead8946b3481 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/expected/dev.run.txt
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/expected/dev.run.txt
@@ -44,10 +44,10 @@ camel.main.durationMaxAction=stop
 camel.main.modeline=true
 camel.main.name=hello
 
camel.main.routesIncludePattern=file:src/test/resources/golden/yaml/hello.camel.yaml
-camel.main.routesReloadDirectory=src/test/resources/golden/yaml
+camel.main.routesReloadDirectory=src/test/resources/golden
 camel.main.routesReloadDirectoryRecursive=true
 camel.main.routesReloadEnabled=true
-camel.main.routesReloadPattern=src/test/resources/golden/yaml/hello.camel.yaml,src/test/resources/golden/groovy/Greeting.groovy
+camel.main.routesReloadPattern=yaml/hello.camel.yaml,groovy/Greeting.groovy
 camel.main.shutdownTimeout=5
 
 # override properties
diff --git 
a/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/CamelMainRunner.java
 
b/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/CamelMainRunner.java
index 0e2fbcdbdb31..03616347461d 100644
--- 
a/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/CamelMainRunner.java
+++ 
b/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/CamelMainRunner.java
@@ -22,7 +22,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.StringJoiner;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Stream;
 
@@ -32,7 +31,6 @@ import org.apache.camel.dsl.jbang.run.common.MavenCommand;
 import org.apache.camel.dsl.jbang.run.common.Printer;
 import org.apache.camel.dsl.jbang.run.common.RuntimeType;
 import org.apache.camel.dsl.jbang.run.export.ExportRequest;
-import org.apache.camel.util.FileUtil;
 import org.apache.maven.model.Model;
 
 import static 
org.apache.camel.dsl.jbang.run.MavenRunSupport.CAMEL_MAIN_RUN_LOG_CONFIG;
@@ -266,26 +264,22 @@ final class CamelMainRunner {
         List<String> args = new ArrayList<>();
         String reloadDir;
         String pattern;
+        boolean recursive;
         if (request.sourceDir != null) {
             reloadDir = request.sourceDir;
             pattern = RunPipeline.sourceDirReloadPattern(request.dataDir);
+            recursive = true;
         } else {
-            // use the directory of the first file that is in another folder 
than the current dir
-            reloadDir = ".";
-            StringJoiner sj = new StringJoiner(",");
-            for (String f : files) {
-                String path = FileUtil.onlyPath(f);
-                if (path != null && !path.equals(".camel-jbang") && 
".".equals(reloadDir)) {
-                    reloadDir = path;
-                }
-                sj.add(FileUtil.stripPath(f));
-            }
-            pattern = sj.toString();
+            // the folder that has all the files, not the one of the first 
file in another folder (CAMEL-26930)
+            RunPipeline.ReloadFolder reload = RunPipeline.reloadFolder(files);
+            reloadDir = reload.directory();
+            pattern = reload.pattern();
+            recursive = reload.recursive();
         }
         args.add("-Dcamel.main.routesReloadEnabled=true");
         args.add("-Dcamel.main.routesReloadDirectory=" + 
Paths.get(reloadDir).toAbsolutePath().normalize());
         args.add("-Dcamel.main.routesReloadPattern=" + pattern);
-        args.add("-Dcamel.main.routesReloadDirectoryRecursive=" + 
(request.sourceDir != null ? "true" : "false"));
+        args.add("-Dcamel.main.routesReloadDirectoryRecursive=" + recursive);
         args.add("-Dcamel.main.routesReloadRemoveAllRoutes=true");
         return args;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/RunPipeline.java
 
b/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/RunPipeline.java
index f77c29953d1b..08d35933ecbd 100644
--- 
a/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/RunPipeline.java
+++ 
b/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/RunPipeline.java
@@ -1168,20 +1168,10 @@ public class RunPipeline {
                 
main.addInitialProperty("camel.main.routesReloadDirectoryRecursive", "true");
             } else {
                 dataDirWithoutSourceDir.run();
-                String pattern = sjReload.toString();
-                String reloadDir = ".";
-                // use current dir, however if we run a file that are in 
another folder, then we should track that folder instead
-                for (String r : sjReload.toString().split(",")) {
-                    String path = FileUtil.onlyPath(r);
-                    if (path != null && !path.equals(".camel-jbang")) {
-                        reloadDir = path;
-                        break;
-                    }
-                }
-                main.addInitialProperty("camel.main.routesReloadDirectory", 
reloadDir);
-                main.addInitialProperty("camel.main.routesReloadPattern", 
pattern);
-                
main.addInitialProperty("camel.main.routesReloadDirectoryRecursive",
-                        isReloadRecursive(pattern) ? "true" : "false");
+                ReloadFolder reload = 
reloadFolder(List.of(sjReload.toString().split(",")));
+                main.addInitialProperty("camel.main.routesReloadDirectory", 
reload.directory());
+                main.addInitialProperty("camel.main.routesReloadPattern", 
reload.pattern());
+                
main.addInitialProperty("camel.main.routesReloadDirectoryRecursive", 
Boolean.toString(reload.recursive()));
             }
             if (request.maxMessages <= 0 && request.maxSeconds <= 0 && 
request.maxIdleSeconds <= 0) {
                 // do not shutdown the JVM but stop routes when max duration 
is triggered, so a reload can start them
@@ -1191,14 +1181,61 @@ public class RunPipeline {
         }
     }
 
-    private static boolean isReloadRecursive(String reload) {
-        for (String part : reload.split(",")) {
-            String dir = FileUtil.onlyPath(part);
-            if (dir != null) {
-                return true;
+    /**
+     * The folder dev mode watches for the files given on the command line, 
and the pattern of those files.
+     *
+     * @param directory the folder, relative to the current directory unless a 
file was given by its absolute path
+     * @param pattern   the files, relative to the folder
+     * @param recursive whether a file is in a sub folder of the folder
+     */
+    public record ReloadFolder(String directory, String pattern, boolean 
recursive) {
+    }
+
+    /**
+     * The folder to watch for the files given on the command line: the 
deepest folder that has all of them, with the
+     * files relative to it. The folder of the first file in another folder 
was watched, so a change of a file in the
+     * current directory, such as application.properties, was not seen, and 
./application.properties did not match the
+     * pattern of the files (CAMEL-26930). The files in .camel-jbang, which 
the watch leaves out, do not widen it.
+     */
+    public static ReloadFolder reloadFolder(Collection<String> files) {
+        Path cwd = Paths.get("").toAbsolutePath();
+        Path internal = cwd.resolve(".camel-jbang");
+        List<Path> paths = new ArrayList<>();
+        boolean absolute = false;
+        Path dir = null;
+        for (String file : files) {
+            if (file == null || file.isBlank()) {
+                continue;
             }
+            Path path = Paths.get(file.trim());
+            absolute |= path.isAbsolute();
+            path = cwd.resolve(path).normalize();
+            paths.add(path);
+            Path parent = path.getParent() != null ? path.getParent() : cwd;
+            if (!parent.startsWith(internal)) {
+                dir = dir == null ? parent : commonFolder(dir, parent);
+            }
+        }
+        if (dir == null) {
+            dir = cwd;
+        }
+        StringJoiner pattern = new StringJoiner(",");
+        boolean recursive = false;
+        for (Path path : paths) {
+            String relative = dir.relativize(path).toString().replace('\\', 
'/');
+            recursive |= relative.contains("/");
+            pattern.add(relative);
+        }
+        String directory = absolute ? dir.toString() : 
cwd.relativize(dir).toString();
+        return new ReloadFolder(directory.isEmpty() ? "." : directory, 
pattern.toString(), recursive);
+    }
+
+    private static Path commonFolder(Path a, Path b) {
+        Path answer = a;
+        while (answer != null && !b.startsWith(answer)) {
+            answer = answer.getParent();
         }
-        return false;
+        return answer != null ? answer : a.getRoot();
     }
 
     /**
diff --git 
a/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/RunPipelineReloadFolderTest.java
 
b/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/RunPipelineReloadFolderTest.java
new file mode 100644
index 000000000000..9b0a7c942c49
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/RunPipelineReloadFolderTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.run;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.run.RunPipeline.ReloadFolder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The folder dev mode watches for the files given on the command line, and 
their pattern (CAMEL-26930).
+ */
+class RunPipelineReloadFolderTest {
+
+    @TempDir
+    Path dir;
+
+    @Test
+    void filesInTheCurrentDirectory() {
+        assertThat(RunPipeline.reloadFolder(List.of("a.camel.yaml", 
"b.camel.yaml")))
+                .isEqualTo(new ReloadFolder(".", "a.camel.yaml,b.camel.yaml", 
false));
+    }
+
+    @Test
+    void theAutoDetectedPropertiesMatch() {
+        // camel run adds ./application.properties, which the pattern did not 
match
+        assertThat(RunPipeline.reloadFolder(List.of("a.camel.yaml", 
"./application.properties")))
+                .isEqualTo(new ReloadFolder(".", 
"a.camel.yaml,application.properties", false));
+    }
+
+    @Test
+    void filesInTwoFoldersWatchTheFolderThatHasBoth() {
+        // the folder of other/b.camel.yaml was watched, so a change of 
a.camel.yaml was not seen
+        assertThat(RunPipeline.reloadFolder(List.of("a.camel.yaml", 
"other/b.camel.yaml", "./application.properties")))
+                .isEqualTo(new ReloadFolder(".", 
"a.camel.yaml,other/b.camel.yaml,application.properties", true));
+        assertThat(RunPipeline.reloadFolder(List.of("../x/a.camel.yaml", 
"../y/b.camel.yaml")))
+                .isEqualTo(new ReloadFolder("..", 
"x/a.camel.yaml,y/b.camel.yaml", true));
+    }
+
+    @Test
+    void filesInOneOtherFolderWatchThatFolder() {
+        assertThat(RunPipeline.reloadFolder(List.of("routes/a.camel.yaml", 
"routes/b.camel.yaml")))
+                .isEqualTo(new ReloadFolder("routes", 
"a.camel.yaml,b.camel.yaml", false));
+    }
+
+    @Test
+    void theFilesOfTheCliDoNotWidenTheFolder() {
+        assertThat(RunPipeline.reloadFolder(List.of("routes/a.camel.yaml", 
".camel-jbang/generated.yaml")).directory())
+                .isEqualTo("routes");
+    }
+
+    @Test
+    void anAbsolutePathGivesAnAbsoluteFolder() {
+        String a = dir.resolve("x/a.camel.yaml").toString();
+        String b = dir.resolve("y/b.camel.yaml").toString();
+        assertThat(RunPipeline.reloadFolder(List.of(a, b)))
+                .isEqualTo(new ReloadFolder(dir.toString(), 
"x/a.camel.yaml,y/b.camel.yaml", true));
+    }
+}

Reply via email to