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

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

commit 16d98373191ded5c69c06b5691d4b34f6ed02db5
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 2 10:39:38 2026 +0200

    CAMEL-25236: camel-jbang - camel run --example runs the example in its own 
folder, wherever camel run is started
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../ROOT/pages/camel-jbang-getting-started.adoc    |  3 +
 .../apache/camel/dsl/jbang/core/commands/Run.java  | 73 +++++++++++++---------
 .../camel/dsl/jbang/core/common/ExampleHelper.java | 24 +++++++
 .../dsl/jbang/core/common/ExampleHelperTest.java   | 25 ++++++++
 .../dsl/jbang/core/commands/tui/LaunchManager.java | 14 +----
 5 files changed, 96 insertions(+), 43 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-jbang-getting-started.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-getting-started.adoc
index 8b943bf42372..d97406348b16 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-getting-started.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-getting-started.adoc
@@ -119,6 +119,9 @@ camel run --example=rest-api --dev
 
 TIP: Combine with `--dev` for live reload while exploring the example.
 
+An example runs in a folder of its own, with its files, wherever you start 
`camel run`. A route that reads `orders`
+or writes to `out/` uses the folder of the example, which `camel run` prints 
when it starts.
+
 === Running multiple files
 
 You can run several files together, even mixing DSLs:
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 06cc2dade192..39f92ce7a567 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
@@ -536,31 +536,12 @@ public class Run extends CamelCommand {
     }
 
     private int runBundledExample(JsonObject entry) throws Exception {
-        String eName = entry.getString("name");
         Path tempDir = ExampleHelper.extractBundledExample(entry);
-        List<String> exampleFiles = ExampleHelper.getFiles(entry);
-
-        printer().println("Running example: " + eName);
-        for (String f : exampleFiles) {
-            files.add(tempDir.resolve(f).toString());
-        }
-        if ("CamelJBang".equals(name)) {
-            name = eName;
-        }
-
-        // use the temp dir as base so run() loads the example's 
application.properties
-        exportBaseDir = tempDir;
-
-        if (!exportRun) {
-            printConfigurationValues("Running integration with the following 
configuration:");
-        }
-        return run();
+        return runExampleIn(entry, tempDir);
     }
 
     private int runGithubExample(JsonObject entry) throws Exception {
         String eName = entry.getString("name");
-        List<String> exampleFiles = ExampleHelper.getFiles(entry);
-
         printer().println("Fetching example from GitHub: " + eName);
         if (ExampleHelper.requiresDocker(entry)) {
             printer().println("Note: this example requires Docker/Podman");
@@ -574,20 +555,52 @@ public class Run extends CamelCommand {
             printer().printErr("This example requires an internet 
connection.");
             return 1;
         }
+        return runExampleIn(entry, tempDir);
+    }
 
-        for (String f : exampleFiles) {
-            files.add(tempDir.resolve(f).toString());
-        }
-        if ("CamelJBang".equals(name)) {
-            name = eName;
+    /**
+     * Runs the example in its folder. A JVM cannot change its working 
directory, so the example runs in a JVM of its
+     * own started in that folder: its routes read and write files relative to 
it (file:orders, out/), wherever camel
+     * run is started (CAMEL-25236). Exporting and transforming do not run the 
routes, and stay in this JVM.
+     */
+    private int runExampleIn(JsonObject entry, Path dir) throws Exception {
+        String eName = entry.getString("name");
+        printer().println("Running example: " + eName);
+        if (exportRun || transformRun || spec == null) {
+            for (String f : ExampleHelper.getFiles(entry)) {
+                files.add(dir.resolve(f).toString());
+            }
+            if ("CamelJBang".equals(name)) {
+                name = ExampleHelper.getShortName(entry);
+            }
+            // use the folder as base so run() loads the example's 
application.properties
+            exportBaseDir = dir;
+            return run();
         }
 
-        exportBaseDir = tempDir;
-
-        if (!exportRun) {
-            printConfigurationValues("Running integration with the following 
configuration:");
+        printer().println("Example folder: " + dir + " (the files it reads and 
writes are there)");
+        List<String> cmds = 
ExampleHelper.runArgs(spec.commandLine().getParseResult().originalArgs(), 
entry);
+        RunHelper.addCamelCLICommand(cmds);
+        if (verbose) {
+            printer().println(String.join(" ", cmds));
+        }
+        ProcessBuilder pb = new ProcessBuilder(cmds);
+        pb.directory(dir.toFile());
+        pb.inheritIO(); // run in foreground (with IO so logs are visible)
+        Process p = pb.start();
+        this.spawnPid = p.pid();
+        // the example stops with this JVM (Ctrl+C reaches both, a kill of 
this one only this one)
+        Thread hook = new Thread(p::destroy, "CamelExampleStop");
+        Runtime.getRuntime().addShutdownHook(hook);
+        try {
+            return p.waitFor();
+        } finally {
+            try {
+                Runtime.getRuntime().removeShutdownHook(hook);
+            } catch (IllegalStateException e) {
+                // shutting down already
+            }
         }
-        return run();
     }
 
     // the logback configuration of an existing Spring Boot project run, in a 
temp file
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
index febdca5fd094..bd2449a2fce5 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
@@ -377,6 +377,30 @@ public final class ExampleHelper {
         return slash > 0 ? name.substring(slash + 1) : name != null ? name : 
"";
     }
 
+    /**
+     * The arguments of a camel run of an example, for a process started in 
the folder of the example: --example=name
+     * (or --example name) replaced by the files of the example, and its name 
unless one is given (CAMEL-25236).
+     */
+    public static List<String> runArgs(List<String> args, JsonObject example) {
+        List<String> answer = new ArrayList<>();
+        boolean named = args.stream().anyMatch(a -> a.equals("--name") || 
a.startsWith("--name="));
+        for (int i = 0; i < args.size(); i++) {
+            String a = args.get(i);
+            if (a.equals("--example") || a.startsWith("--example=")) {
+                if (a.equals("--example") && i + 1 < args.size()) {
+                    i++; // its value
+                }
+                answer.addAll(getFiles(example));
+                if (!named) {
+                    answer.add("--name=" + getShortName(example));
+                }
+            } else {
+                answer.add(a);
+            }
+        }
+        return answer;
+    }
+
     public static boolean isBundled(JsonObject entry) {
         Boolean bundled = entry.getBoolean("bundled");
         return bundled != null && bundled;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java
index bd6c3b626c8e..40a4e0fcfa60 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java
@@ -249,4 +249,29 @@ class ExampleHelperTest {
         assertTrue(names.contains("mqtt"));
         assertTrue(names.contains("aws-sqs"));
     }
+
+    @Test
+    void runArgsOfAnExample() {
+        JsonObject example = 
ExampleHelper.findExample(ExampleHelper.loadCatalog(), 
"route/content-based-router");
+        List<String> files = ExampleHelper.getFiles(example);
+
+        // --example=name: its files and its short name, the other options kept
+        List<String> args = ExampleHelper.runArgs(List.of("run", 
"--example=route/content-based-router", "--dev"), example);
+        assertEquals("run", args.get(0));
+        assertTrue(args.containsAll(files));
+        assertTrue(args.contains("--name=content-based-router"));
+        assertTrue(args.contains("--dev"));
+        assertFalse(args.stream().anyMatch(a -> a.startsWith("--example")));
+
+        // --example name: the value goes too
+        args = ExampleHelper.runArgs(List.of("run", "--example", 
"route/content-based-router", "--dev"), example);
+        assertFalse(args.contains("route/content-based-router"));
+        assertTrue(args.containsAll(files));
+        assertTrue(args.contains("--dev"));
+
+        // a name given is kept as the only one
+        args = ExampleHelper.runArgs(List.of("run", 
"--example=content-based-router", "--name", "cbr"), example);
+        assertTrue(args.contains("cbr"));
+        assertFalse(args.contains("--name=content-based-router"));
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
index 482b294ec597..d9a53829aa90 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
@@ -148,19 +148,7 @@ class LaunchManager {
 
     /** The arguments with --example=name replaced by the files of the 
example, and its name unless one is given. */
     static List<String> exampleArgs(List<String> args, JsonObject example) {
-        List<String> answer = new ArrayList<>();
-        boolean named = args.stream().anyMatch(a -> a.startsWith("--name"));
-        for (String a : args) {
-            if (a.startsWith("--example=")) {
-                answer.addAll(ExampleHelper.getFiles(example));
-                if (!named) {
-                    answer.add("--name=" + 
TuiHelper.stripCategory(example.getString("name")));
-                }
-            } else {
-                answer.add(a);
-            }
-        }
-        return answer;
+        return ExampleHelper.runArgs(args, example);
     }
 
     void addPendingLaunch(String name, Process process, Path outputFile) {

Reply via email to