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) {
