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 86e04170f3a1 CAMEL-24726: camel-jbang-export - ProjectRunner runs the 
integration with Quarkus and Spring Boot, as camel run does (#28741)
86e04170f3a1 is described below

commit 86e04170f3a1f8310e8fc6ebea8a1bfbe824955a
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 30 22:36:51 2026 +0100

    CAMEL-24726: camel-jbang-export - ProjectRunner runs the integration with 
Quarkus and Spring Boot, as camel run does (#28741)
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/dsl/jbang/core/commands/Run.java  | 591 +--------------------
 .../jbang/core/commands/RunExportGoldenTest.java   |   7 +-
 .../core/commands/RunExportedProjectTest.java      | 103 ----
 .../camel/dsl/jbang/core/commands/RunTest.java     |  55 +-
 .../dsl/jbang/export/run/CamelMainRunner.java      |  81 +--
 .../dsl/jbang/export/run/MavenRunSupport.java      | 216 ++++++--
 .../camel/dsl/jbang/export/run/ProjectRunner.java  |  19 +-
 .../camel/dsl/jbang/export/run/QuarkusRunner.java  | 194 +++++++
 .../camel/dsl/jbang/export/run/RunPipeline.java    |  10 +-
 .../camel/dsl/jbang/export/run/RunRequest.java     |  14 +
 .../dsl/jbang/export/run/SpringBootRunner.java     | 212 ++++++++
 .../src/main/resources/spring-boot-logback.xml     |   0
 .../dsl/jbang/export/run/ProjectRunnerTest.java    | 116 +++-
 .../jbang/export/run}/RunExistingProjectTest.java  |  83 +--
 .../jbang/export/run/RunExportedProjectTest.java   | 175 ++++++
 .../run}/RunMavenRuntimePreviousLogTest.java       |  15 +-
 16 files changed, 1025 insertions(+), 866 deletions(-)

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 400176e87728..1bf17133364d 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
@@ -20,18 +20,14 @@ import java.awt.Toolkit;
 import java.awt.datatransfer.Clipboard;
 import java.awt.datatransfer.DataFlavor;
 import java.awt.datatransfer.UnsupportedFlavorException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.text.ParseException;
 import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -58,18 +54,13 @@ import 
org.apache.camel.dsl.jbang.core.common.ProfileCompletionCandidates;
 import org.apache.camel.dsl.jbang.core.common.RunRuntimeCompletionCandidates;
 import org.apache.camel.dsl.jbang.core.common.RunRuntimeTypeConverter;
 import org.apache.camel.dsl.jbang.core.common.TerminalWidthHelper;
-import org.apache.camel.dsl.jbang.export.ExportRequest;
-import org.apache.camel.dsl.jbang.export.ProjectExporter;
-import org.apache.camel.dsl.jbang.export.common.ChildProcess;
 import org.apache.camel.dsl.jbang.export.common.ExportHelper;
-import org.apache.camel.dsl.jbang.export.common.MavenCommand;
 import org.apache.camel.dsl.jbang.export.common.PreviousRun;
 import org.apache.camel.dsl.jbang.export.common.Printer;
 import org.apache.camel.dsl.jbang.export.common.RuntimeType;
 import org.apache.camel.dsl.jbang.export.common.RuntimeUtil;
 import org.apache.camel.dsl.jbang.export.common.TemplateHelper;
 import org.apache.camel.dsl.jbang.export.common.VersionHelper;
-import org.apache.camel.dsl.jbang.export.run.MavenRunSupport;
 import org.apache.camel.dsl.jbang.export.run.ProjectRunner;
 import org.apache.camel.dsl.jbang.export.run.RunContext;
 import org.apache.camel.dsl.jbang.export.run.RunPipeline;
@@ -88,10 +79,6 @@ import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.TimeUtils;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
-import org.apache.maven.model.Dependency;
-import org.apache.maven.model.Model;
-import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
-import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
 import picocli.CommandLine;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
@@ -99,11 +86,6 @@ import picocli.CommandLine.Parameters;
 
 import static 
org.apache.camel.dsl.jbang.core.common.CamelCommandHelper.extractState;
 import static org.apache.camel.dsl.jbang.export.common.CamelJBangConstants.*;
-import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.keepLeftoverRunLog;
-import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.keepRunLog;
-import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.resolveExistingProjectAppName;
-import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.resolveExportedAppName;
-import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.runLogFile;
 
 @Command(name = "run", description = "Run as local Camel integration", 
sortOptions = false, showDefaultValues = true,
          footer = {
@@ -122,13 +104,9 @@ public class Run extends CamelCommand {
     public static final String RUN_JAVA_SH = "classpath:templates/run-java.sh";
 
     public static final String RUN_SETTINGS_FILE = 
ExportHelper.RUN_SETTINGS_FILE;
-    public static final String RUN_PLATFORM_DIR = 
MavenRunSupport.RUN_PLATFORM_DIR;
+    public static final String RUN_PLATFORM_DIR = 
ProjectRunner.RUN_PLATFORM_DIR;
 
     private static final String CLIPBOARD_GENERATED_FILE = 
"generated-clipboard";
-    // the same layout as the camel-jbang console logging, so the log file 
reads the same for every runtime
-    static final String QUARKUS_LOG_FILE_FORMAT
-            = "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %i --- [%15.15t] %-40.40c{3.} : 
%s%e%n";
-
     private static final Pattern PACKAGE_PATTERN = Pattern.compile(
             "^\\s*package\\s+([a-zA-Z][.\\w]*)\\s*;.*$", Pattern.MULTILINE);
 
@@ -626,9 +604,6 @@ public class Run extends CamelCommand {
         }
     }
 
-    // the logback configuration of an existing Spring Boot project run, in a 
temp file
-    private Path springBootLogback;
-
     public Integer runTransform(boolean ignoreLoadingError) throws Exception {
         // just boot silently and exit
         this.transformRun = true;
@@ -876,168 +851,25 @@ public class Run extends CamelCommand {
     }
 
     boolean jfrEnabled() {
-        return MavenRunSupport.jfrEnabled(toRunRequest(exportBaseDir));
+        return toRunRequest(exportBaseDir).jfrEnabled();
     }
 
     String jfrFileName() {
-        return MavenRunSupport.jfrFileName(toRunRequest(exportBaseDir));
-    }
-
-    /**
-     * The JFR JVM arguments for runtimes that fork a subprocess (Quarkus, 
Spring Boot), or {@code null} when JFR was
-     * not requested.
-     */
-    String buildJfrJvmArgs() {
-        return MavenRunSupport.jfrJvmArgs(toRunRequest(exportBaseDir), 
printer());
-    }
-
-    /**
-     * The execution limits ({@code --max-seconds}, {@code --max-messages} and 
{@code --max-idle-seconds}) as
-     * {@code camel.main.durationMax*} system properties, which all the 
runtimes honour.
-     */
-    List<String> buildDurationLimitArgs() {
-        return MavenRunSupport.durationLimitArgs(toRunRequest(exportBaseDir));
-    }
-
-    /**
-     * The JVM arguments the exported Quarkus or Spring Boot project is run 
with ({@code jvm.args} and
-     * {@code spring-boot.run.jvmArguments}): {@code --jvm-args}, the flight 
recording and the execution limits. Returns
-     * null when there are none.
-     */
-    String buildExportedRunJvmArgs() {
-        return MavenRunSupport.exportedRunJvmArgs(toRunRequest(exportBaseDir), 
printer());
+        return toRunRequest(exportBaseDir).jfrFileName();
     }
 
     protected int runQuarkus() throws Exception {
         if (background) {
             throw CliException.usage("Run Camel Quarkus with --background is 
not supported");
         }
+        return runWithMaven();
+    }
 
-        AtomicReference<Process> processRef = new AtomicReference<>();
-
-        // existing Maven project: run directly without export
-        if (files != null && files.size() == 1 && 
files.get(0).endsWith("pom.xml")) {
-            return runExistingQuarkusProject(processRef);
-        }
-
-        AtomicReference<String> appNameRef = new AtomicReference<>();
-
-        // create temp run dir
-        Path runDirPath = Paths.get(RUN_PLATFORM_DIR, 
Long.toString(System.currentTimeMillis()));
-        if (!this.background) {
-            // Mark for deletion on exit
-            // the process (and those it started) stops with this JVM, then 
the files of the run are removed
-            ChildProcess.stopOnExit(processRef::get, () -> {
-                removeDir(runDirPath);
-                // the log is kept as the previous run of the app (CAMEL-25832)
-                keepRunLog(appNameRef.get(), processRef.get(), false);
-            });
-        }
-        Files.createDirectories(runDirPath);
-
-        // export to hidden folder
-        ExportRequest eq = new ExportRequest();
-        eq.baseDir = this.exportBaseDir;
-        eq.javaLiveReload = this.dev;
-        eq.symbolicLink = this.dev;
-        eq.mavenWrapper = true;
-        eq.quarkusPlatform = this.quarkusPlatform.toPlatform();
-        eq.javaVersion = javaVersion;
-        eq.camelVersion = this.camelVersion;
-        eq.javaVersion = this.javaVersion;
-        eq.kameletsVersion = this.kameletsVersion;
-        eq.exportDir = runDirPath.toString();
-        eq.localKameletDir = this.localKameletDir;
-        eq.excludes = this.excludes;
-        eq.files = this.files;
-        eq.name = this.name;
-        eq.verbose = this.verbose;
-        eq.port = this.serverOptions.port;
-        eq.managementPort = this.serverOptions.managementPort;
-        eq.gav = this.gav;
-        eq.runtime = this.runtime;
-        if (eq.gav == null) {
-            if (eq.name == null) {
-                eq.name = "jbang-run-dummy";
-            }
-            eq.gav = "org.example.project:" + eq.name + ":1.0-SNAPSHOT";
-        }
-        eq.dependencies = this.dependencies;
-        eq.dependencies.add("camel:cli-connector");
-        if (jfrEnabled()) {
-            eq.dependencies.add("camel:jfr");
-        }
-        eq.mavenResolver = this.mavenResolver.toResolver();
-        eq.skipPlugins = this.skipPlugins;
-        eq.packageScanJars = this.packageScanJars;
-        eq.quiet = true;
-        eq.logging = false;
-        eq.loggingLevel = "off";
-        eq.ignoreLoadingError = this.ignoreLoadingError;
-        eq.lazyBean = this.lazyBean;
-        eq.profile = this.profile;
-        eq.observe = this.serverOptions.observe;
-        eq.console = this.serverOptions.console;
-        eq.consoleForRun = true;
-        eq.applicationProperties = this.property;
-
-        printer().println("Running using Quarkus (preparing and downloading 
files)");
-
-        // run export
-        ExportBaseCommand.applySystemProperties(eq);
-        int exit = new ProjectExporter(CliExportContext.create(getMain(), 
getMain().getOut(), eq.yes))
-                .export(eq.baseDir, eq);
-        if (exit != 0) {
-            return exit;
-        }
-
-        // the exported project may have derived the application name from the 
source files, and the log file
-        // must be named after the name the application reports (which is what 
camel log and the TUI look for)
-        String appName = resolveExportedAppName(runDirPath, eq.name);
-        appNameRef.set(appName);
-        keepLeftoverRunLog(appName);
-
-        // prepare quarkus for logging to file
-        Path appProps = Paths.get(eq.exportDir, 
"src/main/resources/application.properties");
-        if (Files.exists(appProps)) {
-            String content = Files.readString(appProps);
-            // in the Camel directory of the CLI, where camel log looks for it 
(as for an existing Quarkus project)
-            String logFile = CommandLineHelper.getCamelDir().resolve(appName + 
".log").toAbsolutePath().toString()
-                    .replace("\\", "/");
-            content += "\n# logging to file\n"
-                       + "quarkus.log.file.enabled=true\n"
-                       + "quarkus.log.file.path=" + logFile + "\n"
-                       + "quarkus.log.file.format=" + QUARKUS_LOG_FILE_FORMAT 
+ "\n";
-            Files.writeString(appProps, content);
+    protected int runSpringBoot() throws Exception {
+        if (background) {
+            throw CliException.usage("Run Camel Spring Boot with --background 
is not supported");
         }
-
-        // run quarkus via maven
-        ProcessBuilder pb = new ProcessBuilder();
-        List<String> mvnCmd = new ArrayList<>();
-        mvnCmd.add(MavenCommand.wrapper(runDirPath).toString());
-        mvnCmd.add("--quiet");
-        mvnCmd.add("--file");
-        mvnCmd.add(runDirPath.toRealPath().resolve("pom.xml").toString());
-        String quarkusRunJvmArgs = buildExportedRunJvmArgs();
-        if (quarkusRunJvmArgs != null) {
-            mvnCmd.add("-Djvm.args=" + quarkusRunJvmArgs);
-        }
-        if (serverOptions.console) {
-            // the console extension only exposes the console in dev and test 
mode, and camel run without --dev is
-            // quarkus:run (prod mode). This is build-time configuration, and 
is set on the build of this temporary
-            // project only, so an exported project does not have the console 
exposed in prod mode
-            mvnCmd.add("-Dquarkus.camel.console.exposure-mode=ALL");
-        }
-        mvnCmd.add("package");
-        mvnCmd.add("quarkus:" + (dev ? "dev" : "run"));
-        pb.command(mvnCmd);
-
-        pb.inheritIO(); // run in foreground (with IO so logs are visible)
-        Process p = startProcess(pb);
-        processRef.set(p);
-        this.spawnPid = p.pid();
-        // wait for that process to exit as we run in foreground
-        return p.waitFor();
+        return runWithMaven();
     }
 
     protected int runCamelMain() throws Exception {
@@ -1056,64 +888,27 @@ public class Run extends CamelCommand {
             }
         }
 
+        return runWithMaven();
+    }
+
+    /**
+     * Runs with Maven (Camel Main, Quarkus or Spring Boot) through {@link 
ProjectRunner}, in the foreground.
+     */
+    private int runWithMaven() throws Exception {
         RunRequest request = toRunRequest(exportBaseDir);
         ExportBaseCommand.applySystemProperties(request);
-        RunContext context = CliExportContext.configure(new RunContext(), 
getMain(), printer(), false)
-                .withProcessStarter(this::startProcess);
-        RunningIntegration integration = new 
ProjectRunner(context).start(request);
+        RunningIntegration integration = new 
ProjectRunner(runContext()).start(request);
         this.spawnPid = integration.pid();
         // wait for the application to exit as we run in foreground
         return integration.waitFor();
     }
 
     /**
-     * Starts a process of a run with Maven: the build of the project, or the 
application. Tests that check what such a
-     * run does replace it, so they run neither Maven nor the application.
+     * The context of a run with Maven: the printer, the plugins and the 
dependency resolver of the CLI. Tests that
+     * check what such a run does set a process starter, so they run neither 
Maven nor the application.
      */
-    protected Process startProcess(ProcessBuilder pb) throws IOException {
-        return pb.start();
-    }
-
-    /**
-     * The {@code -D} system properties an existing Maven project is run with: 
the HTTP port ({@code --port}, as the
-     * given property), the execution limits ({@code --max-seconds}, {@code 
--max-messages} and
-     * {@code --max-idle-seconds}) and the additional properties ({@code 
--prop}).
-     */
-    List<String> buildExistingProjectSystemProperties(String portKey) {
-        return 
MavenRunSupport.existingProjectSystemProperties(toRunRequest(exportBaseDir), 
portKey);
-    }
-
-    /**
-     * The Quarkus logging properties that make an existing Quarkus project 
also log to the {@code <name>.log} file in
-     * the {@code ~/.camel} directory (the same as the exported project does 
via its application.properties).
-     */
-    static List<String> buildQuarkusLogFileJvmArgs(String appName) {
-        Path logFile = CommandLineHelper.getCamelDir().resolve(appName + 
".log");
-        String fileName = logFile.toAbsolutePath().toString().replace("\\", 
"/");
-        List<String> args = new ArrayList<>();
-        args.add("-Dquarkus.log.file.enabled=true");
-        args.add("-Dquarkus.log.file.path=" + fileName);
-        // quoted as the format has spaces (quarkus:dev parses jvm.args as a 
command line)
-        args.add("-Dquarkus.log.file.format=\"" + QUARKUS_LOG_FILE_FORMAT + 
"\"");
-        return args;
-    }
-
-    /**
-     * Writes the logback configuration used by {@code camel run 
--runtime=spring-boot} to the given file. It logs to
-     * the console, and to the {@code <name>.log} file in the Camel directory, 
as the other runtimes do, so the log can
-     * be kept as the previous run of the app (CAMEL-25832).
-     */
-    static void writeSpringBootLogbackConfig(Path target, String appName) 
throws IOException {
-        String fileName = 
runLogFile(appName).toAbsolutePath().toString().replace("\\", "/")
-                .replace("&", "&amp;").replace("<", "&lt;").replace("\"", 
"&quot;");
-        try (InputStream is = 
Run.class.getClassLoader().getResourceAsStream("spring-boot-logback.xml")) {
-            if (is == null) {
-                throw new IOException("spring-boot-logback.xml not found");
-            }
-            String content = new String(is.readAllBytes(), 
StandardCharsets.UTF_8).replace("{{logFile}}", fileName);
-            Files.createDirectories(target.getParent());
-            Files.writeString(target, content);
-        }
+    protected RunContext runContext() {
+        return CliExportContext.configure(new RunContext(), getMain(), 
printer(), false);
     }
 
     /**
@@ -1131,349 +926,6 @@ public class Run extends CamelCommand {
         }
     }
 
-    private int runExistingQuarkusProject(AtomicReference<Process> processRef) 
throws Exception {
-        Path pomPath = Path.of(files.get(0)).toAbsolutePath();
-        Path projectDir = pomPath.getParent();
-
-        // read the pom.xml and add camel-quarkus-cli-connector dependency
-        MavenXpp3Reader mavenReader = new MavenXpp3Reader();
-        Model model;
-        try (Reader reader = Files.newBufferedReader(pomPath)) {
-            model = mavenReader.read(reader);
-        }
-
-        boolean hasCliConnector = model.getDependencies().stream()
-                .anyMatch(d -> 
"camel-quarkus-cli-connector".equals(d.getArtifactId()));
-        if (!hasCliConnector) {
-            Dependency d = new Dependency();
-            d.setGroupId("org.apache.camel.quarkus");
-            d.setArtifactId("camel-quarkus-cli-connector");
-            model.getDependencies().add(d);
-        }
-        if (jfrEnabled()) {
-            boolean hasJfr = model.getDependencies().stream()
-                    .anyMatch(d -> 
"camel-quarkus-jfr".equals(d.getArtifactId()));
-            if (!hasJfr) {
-                Dependency d = new Dependency();
-                d.setGroupId("org.apache.camel.quarkus");
-                d.setArtifactId("camel-quarkus-jfr");
-                model.getDependencies().add(d);
-            }
-        }
-
-        // write temporary pom with cli-connector injected
-        Path tempPom = projectDir.resolve("camel-jbang-run-pom.xml");
-        MavenXpp3Writer w = new MavenXpp3Writer();
-        try (FileOutputStream fos = new FileOutputStream(tempPom.toFile())) {
-            w.write(fos, model);
-        }
-
-        // log to console and to file in ~/.camel (so camel log and the TUI 
can tail the logs)
-        String appName = resolveExistingProjectAppName(projectDir, model);
-        keepLeftoverRunLog(appName);
-
-        // shutdown hook to clean up temp files
-        // the process (and those it started) stops with this JVM, then the 
files of the run are removed
-        ChildProcess.stopOnExit(processRef::get, () -> {
-            Files.deleteIfExists(tempPom);
-            // the log is kept as the previous run of the app (CAMEL-25832)
-            keepRunLog(appName, processRef.get(), false);
-        });
-
-        // find mvnw or fall back to mvn
-        String mvnCmd = MavenCommand.of(projectDir);
-
-        List<String> cmd = new ArrayList<>();
-        cmd.add(mvnCmd);
-        cmd.add("--quiet");
-        cmd.add("--file");
-        cmd.add(tempPom.toString());
-        cmd.add("-Djvm.args=" + String.join(" ", 
buildExistingQuarkusJvmArgs(appName)));
-        cmd.add("-DskipTests");
-        cmd.add("package");
-        cmd.add("quarkus:" + (dev ? "dev" : "run"));
-
-        printer().println("Running Quarkus project: " + projectDir);
-
-        ProcessBuilder pb = new ProcessBuilder();
-        pb.command(cmd);
-        pb.directory(projectDir.toFile());
-        pb.inheritIO();
-        Process p = startProcess(pb);
-        processRef.set(p);
-        this.spawnPid = p.pid();
-        return p.waitFor();
-    }
-
-    /**
-     * The JVM arguments ({@code jvm.args}) an existing Quarkus project is run 
with: logging to file, the profile, the
-     * name, the port and properties, the flight recording and {@code 
--jvm-args}.
-     */
-    List<String> buildExistingQuarkusJvmArgs(String appName) {
-        List<String> args = new 
ArrayList<>(buildQuarkusLogFileJvmArgs(appName));
-        if (profile != null && !"prod".equals(profile)) {
-            args.add("-Dcamel.main.profile=" + profile);
-        }
-        args.add("-Dcamel.main.name=" + appName);
-        args.addAll(buildExistingProjectSystemProperties("quarkus.http.port"));
-        String jfrArg = buildJfrJvmArgs();
-        if (jfrArg != null) {
-            args.add(jfrArg);
-        }
-        if (jvmArgs != null && !jvmArgs.isBlank()) {
-            args.add(jvmArgs.trim());
-        }
-        return args;
-    }
-
-    protected int runSpringBoot() throws Exception {
-        if (background) {
-            throw CliException.usage("Run Camel Spring Boot with --background 
is not supported");
-        }
-
-        AtomicReference<Process> processRef = new AtomicReference<>();
-
-        // existing Maven project: run directly without export
-        if (files != null && files.size() == 1 && 
files.get(0).endsWith("pom.xml")) {
-            return runExistingSpringBootProject(processRef);
-        }
-
-        AtomicReference<String> appNameRef = new AtomicReference<>();
-
-        // create temp run dir
-        Path runDirPath = Paths.get(RUN_PLATFORM_DIR, 
Long.toString(System.currentTimeMillis()));
-        if (!this.background) {
-            // Mark for deletion on exit
-            // the process (and those it started) stops with this JVM, then 
the files of the run are removed
-            ChildProcess.stopOnExit(processRef::get, () -> {
-                removeDir(runDirPath);
-                // the log is kept as the previous run of the app (CAMEL-25832)
-                keepRunLog(appNameRef.get(), processRef.get(), false);
-            });
-        }
-        Files.createDirectories(runDirPath);
-
-        // export to hidden folder
-        ExportRequest eq = new ExportRequest();
-        eq.baseDir = this.exportBaseDir;
-        // the code reload is not supported, since we use symlink, spring-boot 
devtools doesn't support symlink
-        if (this.dev) {
-            printer().printWarn("Code reload is not supported with Spring 
Boot.");
-        }
-        eq.javaLiveReload = false;
-        eq.symbolicLink = this.dev;
-        eq.mavenWrapper = true;
-        eq.springBootVersion = this.springBootVersion;
-        eq.javaVersion = this.javaVersion;
-        eq.camelVersion = this.camelVersion;
-        eq.camelSpringBootVersion = this.camelSpringBootVersion != null ? 
this.camelSpringBootVersion : this.camelVersion;
-        eq.javaVersion = this.javaVersion;
-        eq.kameletsVersion = this.kameletsVersion;
-        eq.exportDir = runDirPath.toString();
-        eq.localKameletDir = this.localKameletDir;
-        eq.excludes = this.excludes;
-        eq.files = this.files;
-        eq.name = this.name;
-        eq.verbose = this.verbose;
-        eq.port = this.serverOptions.port;
-        eq.managementPort = this.serverOptions.managementPort;
-        eq.gav = this.gav;
-        eq.mavenResolver = this.mavenResolver.toResolver();
-        eq.runtime = this.runtime;
-        if (eq.gav == null) {
-            if (eq.name == null) {
-                eq.name = "jbang-run-dummy";
-            }
-            eq.gav = "org.example.project:" + eq.name + ":1.0-SNAPSHOT";
-        }
-        eq.dependencies.addAll(this.dependencies);
-        eq.dependencies.add("camel:cli-connector");
-        if (jfrEnabled()) {
-            eq.dependencies.add("camel:jfr");
-        }
-        if (this.dev) {
-            // hot-reload of spring-boot
-            
eq.dependencies.add("mvn:org.springframework.boot:spring-boot-devtools");
-        }
-        eq.skipPlugins = this.skipPlugins;
-        eq.packageScanJars = this.packageScanJars;
-        eq.quiet = true;
-        eq.logging = false;
-        eq.loggingLevel = "off";
-        eq.ignoreLoadingError = this.ignoreLoadingError;
-        eq.lazyBean = this.lazyBean;
-        eq.profile = this.profile;
-        eq.observe = this.serverOptions.observe;
-        eq.console = this.serverOptions.console;
-        eq.consoleForRun = true;
-        eq.applicationProperties = this.property;
-
-        printer().println("Running using Spring Boot (preparing and 
downloading files)");
-
-        // run export
-        ExportBaseCommand.applySystemProperties(eq);
-        int exit = new ProjectExporter(CliExportContext.create(getMain(), 
getMain().getOut(), eq.yes))
-                .export(eq.baseDir, eq);
-        if (exit != 0) {
-            return exit;
-        }
-
-        // the exported project may have derived the application name from the 
source files, and the log file is
-        // named after the name the application reports (what camel log and 
the TUI look for)
-        String appName = resolveExportedAppName(runDirPath, eq.name);
-        appNameRef.set(appName);
-        keepLeftoverRunLog(appName);
-
-        // prepare spring-boot for logging to file
-        writeSpringBootLogbackConfig(Paths.get(eq.exportDir, 
"src/main/resources/logback.xml"), appName);
-
-        // run spring-boot via maven
-        ProcessBuilder pb = new ProcessBuilder();
-        List<String> mvnCmd = new ArrayList<>();
-        mvnCmd.add(MavenCommand.wrapper(runDirPath).toString());
-        mvnCmd.add("--quiet");
-        mvnCmd.add("--file");
-        mvnCmd.add(runDirPath.toRealPath().resolve("pom.xml").toString());
-        String springBootRunJvmArgs = buildExportedRunJvmArgs();
-        if (springBootRunJvmArgs != null) {
-            mvnCmd.add("-Dspring-boot.run.jvmArguments=" + 
springBootRunJvmArgs);
-        }
-        // run in the current directory, not the export folder, so the routes 
read and write files relative to where
-        // camel run is started (CAMEL-25423)
-        mvnCmd.add("-Dspring-boot.run.workingDirectory=" + 
Paths.get(".").toAbsolutePath().normalize());
-        mvnCmd.add("spring-boot:run");
-        pb.command(mvnCmd);
-
-        pb.inheritIO(); // run in foreground (with IO so logs are visible)
-        Process p = startProcess(pb);
-        processRef.set(p);
-        this.spawnPid = p.pid();
-        // wait for that process to exit as we run in foreground
-        return p.waitFor();
-    }
-
-    /**
-     * The logback configuration an existing Spring Boot project runs with, 
logging to the {@code <name>.log} file so
-     * the TUI can read the log: a temp file, so the project is not changed 
and needs no src/main/resources. Null when
-     * it cannot be written.
-     */
-    Path springBootLogbackConfig(String appName) {
-        if (springBootLogback == null) {
-            try {
-                Path file = Files.createTempFile("camel-jbang-logback-", 
".xml");
-                file.toFile().deleteOnExit();
-                writeSpringBootLogbackConfig(file, appName);
-                springBootLogback = file;
-            } catch (IOException e) {
-                return null;
-            }
-        }
-        return springBootLogback;
-    }
-
-    /**
-     * The JVM arguments ({@code spring-boot.run.jvmArguments}) an existing 
Spring Boot project is run with: logging to
-     * file, the profile, the name (so the app reports the name of its log 
file), the port and properties, the flight
-     * recording and {@code --jvm-args}.
-     */
-    List<String> buildExistingSpringBootJvmArgs(String appName) {
-        List<String> args = new ArrayList<>();
-        Path logback = springBootLogbackConfig(appName);
-        if (logback != null) {
-            args.add("-Dlogging.config=" + logback.toUri());
-        }
-        if (profile != null && !"prod".equals(profile)) {
-            args.add("-Dcamel.main.profile=" + profile);
-        }
-        args.add("-Dcamel.main.name=" + appName);
-        args.addAll(buildExistingProjectSystemProperties("server.port"));
-        String jfrArg = buildJfrJvmArgs();
-        if (jfrArg != null) {
-            args.add(jfrArg);
-        }
-        if (jvmArgs != null && !jvmArgs.isBlank()) {
-            args.add(jvmArgs.trim());
-        }
-        return args;
-    }
-
-    private int runExistingSpringBootProject(AtomicReference<Process> 
processRef) throws Exception {
-        Path pomPath = Path.of(files.get(0)).toAbsolutePath();
-        Path projectDir = pomPath.getParent();
-
-        // read the pom.xml and add camel-cli-connector-starter dependency
-        MavenXpp3Reader mavenReader = new MavenXpp3Reader();
-        Model model;
-        try (Reader reader = Files.newBufferedReader(pomPath)) {
-            model = mavenReader.read(reader);
-        }
-
-        boolean hasCliConnector = model.getDependencies().stream()
-                .anyMatch(d -> 
"camel-cli-connector-starter".equals(d.getArtifactId()));
-        if (!hasCliConnector) {
-            Dependency d = new Dependency();
-            d.setGroupId("org.apache.camel.springboot");
-            d.setArtifactId("camel-cli-connector-starter");
-            model.getDependencies().add(d);
-        }
-        if (jfrEnabled()) {
-            boolean hasJfr = model.getDependencies().stream()
-                    .anyMatch(d -> 
"camel-jfr-starter".equals(d.getArtifactId()));
-            if (!hasJfr) {
-                Dependency d = new Dependency();
-                d.setGroupId("org.apache.camel.springboot");
-                d.setArtifactId("camel-jfr-starter");
-                model.getDependencies().add(d);
-            }
-        }
-
-        // write temporary pom with cli-connector injected
-        Path tempPom = projectDir.resolve("camel-jbang-run-pom.xml");
-        MavenXpp3Writer w = new MavenXpp3Writer();
-        try (FileOutputStream fos = new FileOutputStream(tempPom.toFile())) {
-            w.write(fos, model);
-        }
-
-        // log to console and to file in ~/.camel (so camel log and the TUI 
can tail the logs)
-        String appName = resolveExistingProjectAppName(projectDir, model);
-        keepLeftoverRunLog(appName);
-
-        // shutdown hook to clean up temp files
-        // the process (and those it started) stops with this JVM, then the 
files of the run are removed
-        ChildProcess.stopOnExit(processRef::get, () -> {
-            Files.deleteIfExists(tempPom);
-            if (springBootLogback != null) {
-                Files.deleteIfExists(springBootLogback);
-            }
-            // the log is kept as the previous run of the app (CAMEL-25832)
-            keepRunLog(appName, processRef.get(), false);
-        });
-
-        // find mvnw or fall back to mvn
-        String mvnCmd = MavenCommand.of(projectDir);
-
-        List<String> cmd = new ArrayList<>();
-        cmd.add(mvnCmd);
-        cmd.add("--quiet");
-        cmd.add("--file");
-        cmd.add(tempPom.toString());
-        cmd.add("-Dspring-boot.run.jvmArguments=" + String.join(" ", 
buildExistingSpringBootJvmArgs(appName)));
-        cmd.add("-DskipTests");
-        cmd.add("package");
-        cmd.add("spring-boot:run");
-
-        printer().println("Running Spring Boot project: " + projectDir);
-
-        ProcessBuilder pb = new ProcessBuilder();
-        pb.command(cmd);
-        pb.directory(projectDir.toFile());
-        pb.inheritIO();
-        Process p = startProcess(pb);
-        processRef.set(p);
-        this.spawnPid = p.pid();
-        return p.waitFor();
-    }
-
     protected void doAddInitialProperty(KameletMain main) {
         // noop
     }
@@ -1521,6 +973,7 @@ public class Run extends CamelCommand {
         request.packageScanJars = packageScanJars;
         request.camelVersion = camelVersion;
         request.springBootVersion = springBootVersion;
+        request.camelSpringBootVersion = camelSpringBootVersion;
         request.quarkusPlatform = quarkusPlatform != null ? 
quarkusPlatform.toPlatform() : null;
         request.kameletsVersion = kameletsVersion;
         request.javaVersion = javaVersion;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportGoldenTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportGoldenTest.java
index f72614b6f2a8..35c72be61875 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportGoldenTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportGoldenTest.java
@@ -39,6 +39,7 @@ import 
org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
 import org.apache.camel.dsl.jbang.export.common.ExportHelper;
 import org.apache.camel.dsl.jbang.export.common.PathUtils;
 import org.apache.camel.dsl.jbang.export.common.RuntimeType;
+import org.apache.camel.dsl.jbang.export.run.RunContext;
 import org.apache.camel.main.KameletMain;
 import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Model;
@@ -370,7 +371,11 @@ class RunExportGoldenTest {
         }
 
         @Override
-        protected Process startProcess(ProcessBuilder pb) throws IOException {
+        protected RunContext runContext() {
+            return super.runContext().withProcessStarter(this::start);
+        }
+
+        private Process start(ProcessBuilder pb) throws IOException {
             started.add(pb);
             Path project = exportedProject(pb);
             if (project != null && pb.command().contains("package") && 
pb.command().contains("-DskipTests")
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportedProjectTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportedProjectTest.java
deleted file mode 100644
index 4da89a7cce6a..000000000000
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExportedProjectTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.core.commands;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-import org.apache.camel.dsl.jbang.export.run.MavenRunSupport;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-import picocli.CommandLine;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Verifies how {@code camel run --runtime=quarkus|spring-boot} runs the 
exported project: the execution limits are
- * passed to the child JVM like for Camel Main, and the log file is named 
after the name the application reports.
- */
-class RunExportedProjectTest extends CamelCommandBaseTestSupport {
-
-    @TempDir
-    Path dir;
-
-    private static Run run(String... args) {
-        Run command = new Run(new CamelJBangMain());
-        CommandLine.populateCommand(command, args);
-        return command;
-    }
-
-    @Test
-    void durationLimitsArePassedToExportedRun() {
-        String args = run("--max-seconds=30", "--max-messages=5", 
"--max-idle-seconds=10", "route.yaml")
-                .buildExportedRunJvmArgs();
-
-        assertThat(args).isEqualTo("-Dcamel.main.durationMaxSeconds=30 
-Dcamel.main.durationMaxMessages=5"
-                                   + " 
-Dcamel.main.durationMaxIdleSeconds=10");
-    }
-
-    @Test
-    void durationLimitsAreMergedWithJvmArgs() {
-        String args = run("--jvm-args=-Xmx512m", "--max-seconds=30", 
"route.yaml").buildExportedRunJvmArgs();
-
-        assertThat(args).isEqualTo("-Xmx512m 
-Dcamel.main.durationMaxSeconds=30");
-    }
-
-    @Test
-    void noJvmArgsWhenNothingToPass() {
-        assertThat(run("route.yaml").buildExportedRunJvmArgs()).isNull();
-    }
-
-    @Test
-    void jfrIsPassedAsJvmArgument() {
-        String args = run("--jfr", "route.yaml").buildExportedRunJvmArgs();
-
-        assertThat(args).contains("-XX:StartFlightRecording");
-    }
-
-    @Test
-    void durationLimitsAreTheSameForAllRuntimes() {
-        Run run = run("--max-seconds=30", "route.yaml");
-
-        List<String> limits = run.buildDurationLimitArgs();
-        
assertThat(limits).containsExactly("-Dcamel.main.durationMaxSeconds=30");
-        // exported project (camel run foo.yaml --runtime=quarkus|spring-boot)
-        assertThat(run.buildExportedRunJvmArgs()).isEqualTo(String.join(" ", 
limits));
-        // existing project (camel run pom.xml), per runtime
-        
assertThat(run.buildExistingQuarkusJvmArgs("my-app")).containsAll(limits);
-        
assertThat(run.buildExistingSpringBootJvmArgs("my-app")).containsAll(limits);
-        
assertThat(MavenRunSupport.existingCamelMainSystemProperties(run.toRunRequest(null),
 "my-app",
-                dir.resolve("log4j2.properties")))
-                .containsAll(limits);
-    }
-
-    @Test
-    void appNameIsTheExportedCamelMainName() throws Exception {
-        Path resources = dir.resolve("src/main/resources");
-        Files.createDirectories(resources);
-        Files.writeString(resources.resolve("application.properties"), 
"camel.main.name=my-route\n");
-
-        assertThat(MavenRunSupport.resolveExportedAppName(dir, 
"CamelJBang")).isEqualTo("my-route");
-    }
-
-    @Test
-    void appNameFallsBackWhenNotExported() {
-        assertThat(MavenRunSupport.resolveExportedAppName(dir, 
"CamelJBang")).isEqualTo("CamelJBang");
-    }
-
-}
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 4c8a436b2ffe..aa11d92f46a3 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
@@ -20,8 +20,8 @@ package org.apache.camel.dsl.jbang.core.commands;
 import java.nio.file.Path;
 
 import org.apache.camel.dsl.jbang.export.common.RuntimeType;
-import org.apache.camel.dsl.jbang.export.run.MavenRunSupport;
 import org.apache.camel.dsl.jbang.export.run.RunPipeline;
+import org.apache.camel.dsl.jbang.export.run.RunRequest;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.main.KameletMain;
 import org.junit.jupiter.api.Assertions;
@@ -195,46 +195,37 @@ class RunTest extends CamelCommandBaseTestSupport {
     }
 
     @Test
-    void jfrDisabledByDefaultBuildsNoJvmArgs() throws Exception {
+    void jfrIsDisabledByDefault() throws Exception {
         Run command = new Run(new CamelJBangMain());
         CommandLine.populateCommand(command, "route.yaml");
 
         assertThat(command.jfrEnabled()).isFalse();
-        assertThat(command.buildJfrJvmArgs()).isNull();
     }
 
     @Test
-    void jfrFlagBuildsStartFlightRecordingArg() throws Exception {
+    void jfrFlagEnablesJfr() throws Exception {
         Run command = new Run(new CamelJBangMain());
         CommandLine.populateCommand(command, "--jfr", "route.yaml");
 
         assertThat(command.jfrEnabled()).isTrue();
-        assertThat(command.buildJfrJvmArgs())
-                .isEqualTo("-XX:StartFlightRecording=filename=CamelJBang.jfr "
-                           + 
"-Dcamel.main.startupRecorderRuntimeEnabled=true");
     }
 
     @Test
-    void jfrProfileImpliesJfrAndIsAppendedAsSettings() throws Exception {
+    void jfrProfileImpliesJfr() throws Exception {
         Run command = new Run(new CamelJBangMain());
         CommandLine.populateCommand(command, "--jfr-profile=profile", 
"route.yaml");
 
         assertThat(command.jfrEnabled()).isTrue();
-        assertThat(command.buildJfrJvmArgs())
-                
.isEqualTo("-XX:StartFlightRecording=filename=CamelJBang.jfr,settings=profile "
-                           + 
"-Dcamel.main.startupRecorderRuntimeEnabled=true");
     }
 
     @Test
-    void explicitStartFlightRecordingInJvmArgsKeepsRuntimeRecorderEnabled() 
throws Exception {
+    void explicitStartFlightRecordingInJvmArgsKeepsJfrEnabled() throws 
Exception {
         // the explicit recording wins, but --jfr must still enable Camel 
runtime events in forked projects
         Run command = new Run(new CamelJBangMain());
         CommandLine.populateCommand(command, "--jfr",
                 "--jvm-args=-XX:StartFlightRecording=filename=mine.jfr", 
"route.yaml");
 
         assertThat(command.jfrEnabled()).isTrue();
-        assertThat(command.buildJfrJvmArgs())
-                .isEqualTo("-Dcamel.main.startupRecorderRuntimeEnabled=true");
     }
 
     @Test
@@ -274,23 +265,25 @@ class RunTest extends CamelCommandBaseTestSupport {
     }
 
     @Test
-    void mergeJvmArgsReturnsExistingWhenExtraIsNull() {
-        assertThat(MavenRunSupport.mergeJvmArgs("-Xmx256m", 
null)).isEqualTo("-Xmx256m");
-        assertThat(MavenRunSupport.mergeJvmArgs(null, null)).isNull();
-    }
-
-    @Test
-    void mergeJvmArgsReturnsExtraWhenExistingIsBlank() {
-        assertThat(MavenRunSupport.mergeJvmArgs(null, 
"-XX:StartFlightRecording=filename=camel.jfr"))
-                .isEqualTo("-XX:StartFlightRecording=filename=camel.jfr");
-        assertThat(MavenRunSupport.mergeJvmArgs("  ", 
"-XX:StartFlightRecording=filename=camel.jfr"))
-                .isEqualTo("-XX:StartFlightRecording=filename=camel.jfr");
-    }
-
-    @Test
-    void mergeJvmArgsConcatenatesBothWithSpace() {
-        assertThat(MavenRunSupport.mergeJvmArgs("-Xmx256m", 
"-XX:StartFlightRecording=filename=camel.jfr"))
-                .isEqualTo("-Xmx256m 
-XX:StartFlightRecording=filename=camel.jfr");
+    void theOptionsOfARunWithMavenAreInTheRequest() {
+        Run command = new Run(new CamelJBangMain());
+        CommandLine.populateCommand(command, "--runtime=spring-boot", 
"--camel-spring-boot-version=4.20.0",
+                "--jvm-args=-Xmx512m", "--jvm-debug=5005", "--port=9090", 
"--max-seconds=30", "--prop=foo=bar",
+                "--jfr", "--dev", "route.yaml");
+
+        RunRequest request = command.toRunRequest(null);
+
+        // the default profile of camel run is dev
+        assertThat(request.profile).isEqualTo("dev");
+        assertThat(request.runtime).isEqualTo(RuntimeType.springBoot);
+        assertThat(request.camelSpringBootVersion).isEqualTo("4.20.0");
+        assertThat(request.jvmArgs).isEqualTo("-Xmx512m");
+        assertThat(request.jvmDebugPort).isEqualTo(5005);
+        assertThat(request.port).isEqualTo(9090);
+        assertThat(request.maxSeconds).isEqualTo(30);
+        assertThat(request.applicationProperties).containsExactly("foo=bar");
+        assertThat(request.jfrEnabled()).isTrue();
+        assertThat(request.dev).isTrue();
     }
 
     @Test
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/CamelMainRunner.java
 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/CamelMainRunner.java
index 3a195d965801..67c2d8e7ae82 100644
--- 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/CamelMainRunner.java
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/CamelMainRunner.java
@@ -16,16 +16,13 @@
  */
 package org.apache.camel.dsl.jbang.export.run;
 
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.Reader;
 import java.nio.file.Files;
 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.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Stream;
 
@@ -36,22 +33,22 @@ import 
org.apache.camel.dsl.jbang.export.common.MavenCommand;
 import org.apache.camel.dsl.jbang.export.common.Printer;
 import org.apache.camel.dsl.jbang.export.common.RuntimeType;
 import org.apache.camel.util.FileUtil;
-import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Model;
-import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
-import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
 
 import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.CAMEL_MAIN_RUN_LOG_CONFIG;
-import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.RUN_PLATFORM_DIR;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.addDependency;
 import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.durationLimitArgs;
 import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.existingCamelMainSystemProperties;
-import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.jfrEnabled;
 import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.jfrJvmArgs;
 import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.keepRunLog;
 import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.mergeJvmArgs;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.once;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.readPom;
 import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.resolveExistingProjectAppName;
 import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.resolveExportedAppName;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.runExportRequest;
 import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.writeCamelMainRunLogConfig;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.writeRunPom;
 
 /**
  * Runs with Camel Main: the files are exported to a Camel Main project in a 
directory of the run, which is packaged
@@ -104,7 +101,7 @@ final class CamelMainRunner {
         AtomicReference<Process> appRef = new AtomicReference<>();
 
         // create temp run dir
-        Path runDirPath = Paths.get(RUN_PLATFORM_DIR, 
Long.toString(System.currentTimeMillis()));
+        Path runDirPath = Paths.get(ProjectRunner.RUN_PLATFORM_DIR, 
Long.toString(System.currentTimeMillis()));
         // the process (and those it started) stops with this JVM, then the 
files of the run are removed
         ChildProcess.Cleanup cleanup = once(() -> {
             RunPipeline.removeDir(runDirPath);
@@ -117,28 +114,10 @@ final class CamelMainRunner {
         Files.createDirectories(runDirPath);
 
         // export to hidden folder
-        ExportRequest eq = new ExportRequest(request);
-        eq.files = files;
+        ExportRequest eq = runExportRequest(request, runDirPath, files);
         eq.javaLiveReload = false;
         eq.symbolicLink = false;
-        eq.mavenWrapper = true;
-        eq.exportDir = runDirPath.toString();
         eq.runtime = RuntimeType.main;
-        if (eq.gav == null) {
-            if (eq.name == null) {
-                eq.name = "jbang-run-dummy";
-            }
-            eq.gav = "org.example.project:" + eq.name + ":1.0-SNAPSHOT";
-        }
-        eq.dependencies = new ArrayList<>(request.dependencies);
-        eq.dependencies.add("camel:cli-connector");
-        if (jfrEnabled(request)) {
-            eq.dependencies.add("camel:jfr");
-        }
-        eq.quiet = true;
-        eq.logging = false;
-        eq.loggingLevel = "off";
-        eq.consoleForRun = true;
 
         printer().println("Running using Camel Main (preparing and downloading 
files)");
 
@@ -218,37 +197,13 @@ final class CamelMainRunner {
         Path projectDir = pomPath.getParent();
 
         // read the pom.xml and add camel-cli-connector dependency
-        MavenXpp3Reader mavenReader = new MavenXpp3Reader();
-        Model model;
-        try (Reader reader = Files.newBufferedReader(pomPath)) {
-            model = mavenReader.read(reader);
-        }
-
-        boolean hasCliConnector = model.getDependencies().stream()
-                .anyMatch(d -> 
"camel-cli-connector".equals(d.getArtifactId()));
-        if (!hasCliConnector) {
-            Dependency d = new Dependency();
-            d.setGroupId("org.apache.camel");
-            d.setArtifactId("camel-cli-connector");
-            model.getDependencies().add(d);
-        }
-        if (jfrEnabled(request)) {
-            boolean hasJfr = model.getDependencies().stream()
-                    .anyMatch(d -> "camel-jfr".equals(d.getArtifactId()));
-            if (!hasJfr) {
-                Dependency d = new Dependency();
-                d.setGroupId("org.apache.camel");
-                d.setArtifactId("camel-jfr");
-                model.getDependencies().add(d);
-            }
+        Model model = readPom(pomPath);
+        addDependency(model, "org.apache.camel", "camel-cli-connector");
+        if (request.jfrEnabled()) {
+            addDependency(model, "org.apache.camel", "camel-jfr");
         }
-
         // write temporary pom with cli-connector injected
-        Path tempPom = projectDir.resolve("camel-jbang-run-pom.xml");
-        MavenXpp3Writer w = new MavenXpp3Writer();
-        try (FileOutputStream fos = new FileOutputStream(tempPom.toFile())) {
-            w.write(fos, model);
-        }
+        Path tempPom = writeRunPom(projectDir, model);
 
         // log to console and to file in ~/.camel (so camel log and the TUI 
can tail the logs)
         String appName = resolveExistingProjectAppName(projectDir, model);
@@ -339,18 +294,6 @@ final class CamelMainRunner {
         }
     }
 
-    /**
-     * The cleanup runs once: when the integration is stopped, or when this 
JVM exits.
-     */
-    private static ChildProcess.Cleanup once(ChildProcess.Cleanup cleanup) {
-        AtomicBoolean done = new AtomicBoolean();
-        return () -> {
-            if (done.compareAndSet(false, true)) {
-                cleanup.run();
-            }
-        };
-    }
-
     private Printer printer() {
         return runner.getContext().getPrinter();
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/MavenRunSupport.java
 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/MavenRunSupport.java
index 11296a922fb5..b3bdad0a67d8 100644
--- 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/MavenRunSupport.java
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/MavenRunSupport.java
@@ -16,40 +16,48 @@
  */
 package org.apache.camel.dsl.jbang.export.run;
 
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.Reader;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.camel.dsl.jbang.export.ExportRequest;
 import org.apache.camel.dsl.jbang.export.common.CamelHome;
+import org.apache.camel.dsl.jbang.export.common.ChildProcess;
 import org.apache.camel.dsl.jbang.export.common.LogFileReader;
 import org.apache.camel.dsl.jbang.export.common.PreviousRun;
 import org.apache.camel.dsl.jbang.export.common.Printer;
 import org.apache.camel.util.json.JsonObject;
+import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
 
 /**
  * What the runs with Maven (Camel Main, Quarkus and Spring Boot) share: the 
directory of the run, the name of the
  * application, the JVM arguments of the options of the run, and the log of 
the run, which is kept as the previous run
  * of the application when it ends.
- *
- * @since 4.24
  */
-public final class MavenRunSupport {
+final class MavenRunSupport {
 
     /**
-     * The directory with the projects that are exported to be run, one 
directory per run.
+     * The file with the log4j2 configuration that an existing Camel Main 
project is run with.
      */
-    public static final String RUN_PLATFORM_DIR = ".camel-jbang-run";
+    static final String CAMEL_MAIN_RUN_LOG_CONFIG = 
"camel-jbang-run-log4j2.properties";
 
     /**
-     * The file with the log4j2 configuration that an existing Camel Main 
project is run with.
+     * The format of the log file of a Quarkus run: the same layout as the 
camel-jbang console logging, so the log file
+     * reads the same for every runtime.
      */
-    public static final String CAMEL_MAIN_RUN_LOG_CONFIG = 
"camel-jbang-run-log4j2.properties";
+    static final String QUARKUS_LOG_FILE_FORMAT
+            = "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %i --- [%15.15t] %-40.40c{3.} : 
%s%e%n";
 
     /**
      * At most this many records at the end of the log are looked at for why a 
run failed.
@@ -63,7 +71,7 @@ public final class MavenRunSupport {
      * The application name as configured in the exported project ({@code 
camel.main.name}), which is what the running
      * application reports to the CLI and the TUI.
      */
-    public static String resolveExportedAppName(Path runDirPath, String 
fallback) {
+    static String resolveExportedAppName(Path runDirPath, String fallback) {
         Path props = 
runDirPath.resolve("src/main/resources/application.properties");
         if (Files.exists(props)) {
             try (InputStream is = Files.newInputStream(props)) {
@@ -86,7 +94,7 @@ public final class MavenRunSupport {
      * {@code camel.main.name}, so the name it reports to the CLI and the TUI 
matches the {@code <name>.log} file that
      * {@code camel log} and the TUI read when there is no {@code <pid>.log}.
      */
-    public static String resolveExistingProjectAppName(Path projectDir, Model 
model) {
+    static String resolveExistingProjectAppName(Path projectDir, Model model) {
         String name = resolveExportedAppName(projectDir, null);
         if (name == null && model != null && model.getArtifactId() != null && 
!model.getArtifactId().isBlank()) {
             name = model.getArtifactId().trim();
@@ -97,25 +105,11 @@ public final class MavenRunSupport {
         return name;
     }
 
-    /**
-     * Whether the run records a flight recording ({@code --jfr} or {@code 
--jfr-profile}).
-     */
-    public static boolean jfrEnabled(RunRequest request) {
-        return request.jfr || request.jfrProfile != null;
-    }
-
-    /**
-     * The file of the flight recording.
-     */
-    public static String jfrFileName(RunRequest request) {
-        return (request.name != null ? request.name : "camel") + ".jfr";
-    }
-
     /**
      * The JFR JVM arguments for runtimes that fork a subprocess, or {@code 
null} when JFR was not requested.
      */
-    public static String jfrJvmArgs(RunRequest request, Printer printer) {
-        if (!jfrEnabled(request)) {
+    static String jfrJvmArgs(RunRequest request, Printer printer) {
+        if (!request.jfrEnabled()) {
             return null;
         }
         StringBuilder arg = new StringBuilder();
@@ -125,7 +119,7 @@ public final class MavenRunSupport {
                     "--jvm-args already starts a flight recording, skipping 
-XX:StartFlightRecording from --jfr"
                               + " (Camel runtime instrumentation is still 
enabled)");
         } else {
-            
arg.append("-XX:StartFlightRecording=filename=").append(jfrFileName(request));
+            
arg.append("-XX:StartFlightRecording=filename=").append(request.jfrFileName());
             if (request.jfrProfile != null) {
                 arg.append(",settings=").append(request.jfrProfile);
             }
@@ -137,7 +131,7 @@ public final class MavenRunSupport {
         return arg.toString();
     }
 
-    public static String mergeJvmArgs(String existing, String extra) {
+    static String mergeJvmArgs(String existing, String extra) {
         if (extra == null) {
             return existing;
         }
@@ -151,7 +145,7 @@ public final class MavenRunSupport {
      * The execution limits ({@code --max-seconds}, {@code --max-messages} and 
{@code --max-idle-seconds}) as
      * {@code camel.main.durationMax*} system properties, which all the 
runtimes honour.
      */
-    public static List<String> durationLimitArgs(RunRequest request) {
+    static List<String> durationLimitArgs(RunRequest request) {
         List<String> args = new ArrayList<>();
         if (request.maxSeconds > 0) {
             args.add("-Dcamel.main.durationMaxSeconds=" + request.maxSeconds);
@@ -170,7 +164,7 @@ public final class MavenRunSupport {
      * {@code spring-boot.run.jvmArguments}): {@code --jvm-args}, the flight 
recording and the execution limits. Returns
      * null when there are none.
      */
-    public static String exportedRunJvmArgs(RunRequest request, Printer 
printer) {
+    static String exportedRunJvmArgs(RunRequest request, Printer printer) {
         String args = mergeJvmArgs(request.jvmArgs, jfrJvmArgs(request, 
printer));
         List<String> limits = durationLimitArgs(request);
         if (!limits.isEmpty()) {
@@ -184,7 +178,7 @@ public final class MavenRunSupport {
      * given property), the execution limits ({@code --max-seconds}, {@code 
--max-messages} and
      * {@code --max-idle-seconds}) and the additional properties ({@code 
--prop}).
      */
-    public static List<String> existingProjectSystemProperties(RunRequest 
request, String portKey) {
+    static List<String> existingProjectSystemProperties(RunRequest request, 
String portKey) {
         List<String> args = new ArrayList<>();
         if (request.port != -1) {
             args.add("-D" + portKey + "=" + request.port);
@@ -205,7 +199,7 @@ public final class MavenRunSupport {
      * The {@code -D} system properties an existing Camel Main project is run 
with (Camel runs inside the Maven JVM so
      * they go on the Maven command line): the log4j2 configuration, the 
profile, the name, the port and properties.
      */
-    public static List<String> existingCamelMainSystemProperties(RunRequest 
request, String appName, Path logConfig) {
+    static List<String> existingCamelMainSystemProperties(RunRequest request, 
String appName, Path logConfig) {
         List<String> args = new ArrayList<>();
         args.add("-Dlog4j2.configurationFile=" + logConfig.toAbsolutePath());
         if (request.profile != null && !"prod".equals(request.profile)) {
@@ -221,7 +215,7 @@ public final class MavenRunSupport {
      * console, and to the {@code <name>.log} file in the {@code ~/.camel} 
directory. A log a run of that name left
      * behind is kept as its previous run first, so the new log does not start 
with the output of that run.
      */
-    public static void writeCamelMainRunLogConfig(Path target, String appName, 
String loggingLevel) throws IOException {
+    static void writeCamelMainRunLogConfig(Path target, String appName, String 
loggingLevel) throws IOException {
         Path logFile = runLogFile(appName);
         keepLeftoverRunLog(appName);
         String fileName = logFile.toAbsolutePath().toString().replace("\\", 
"/");
@@ -239,7 +233,7 @@ public final class MavenRunSupport {
      * The log a run on a Maven runtime writes in the Camel directory: {@code 
<name>.log}, which {@code camel log} and
      * the TUI read when there is no {@code <pid>.log}.
      */
-    public static Path runLogFile(String appName) {
+    static Path runLogFile(String appName) {
         return CamelHome.getCamelDir().resolve(appName + ".log");
     }
 
@@ -247,7 +241,7 @@ public final class MavenRunSupport {
      * A log that a run of this name left behind, such as a run that was 
killed so it could not keep its log, is kept as
      * the previous run of the name before the new run writes its own 
(CAMEL-25832).
      */
-    public static void keepLeftoverRunLog(String appName) {
+    static void keepLeftoverRunLog(String appName) {
         PreviousRun.keepLeftover(runLogFile(appName), appName);
     }
 
@@ -262,12 +256,12 @@ public final class MavenRunSupport {
      *                JVM), or Maven that runs the app in a process of its own 
(quarkus:run, quarkus:dev,
      *                spring-boot:run), whose pid and exit code are not those 
of the app
      */
-    public static void keepRunLog(String appName, Process process, boolean 
app) {
+    static void keepRunLog(String appName, Process process, boolean app) {
         Integer code = process != null && !process.isAlive() ? 
process.exitValue() : null;
         keepRunLog(appName, app && process != null ? process.pid() : 0, code, 
app);
     }
 
-    public static Path keepRunLog(String appName, long pid, Integer code, 
boolean app) {
+    static Path keepRunLog(String appName, long pid, Integer code, boolean 
app) {
         if (appName == null) {
             return null;
         }
@@ -293,7 +287,7 @@ public final class MavenRunSupport {
      * (Camel Main). The first, as an error that follows it is about the same 
failure in other words: Quarkus logs the
      * failure of Camel, then that Quarkus failed to start. Null when the end 
of the log has no ERROR.
      */
-    public static String failureInLog(Path log) {
+    static String failureInLog(Path log) {
         if (!Files.isRegularFile(log)) {
             return null;
         }
@@ -315,7 +309,7 @@ public final class MavenRunSupport {
      * The signal a process stopped by it exits with, as 128 and the number of 
the signal: 130 for SIGINT (Ctrl+C), 137
      * for SIGKILL and 143 for SIGTERM; null for another exit code.
      */
-    public static String signalOf(int exitCode) {
+    static String signalOf(int exitCode) {
         return switch (exitCode) {
             case 130 -> "SIGINT";
             case 137 -> "SIGKILL";
@@ -323,4 +317,150 @@ public final class MavenRunSupport {
             default -> null;
         };
     }
+
+    /**
+     * The request of the export of the project that a run builds and runs, to 
the given directory of the run: the
+     * options of the run, the CLI connector (and the flight recorder) for the 
tools, and no logging of the export.
+     */
+    static ExportRequest runExportRequest(RunRequest request, Path runDir, 
List<String> files) {
+        ExportRequest eq = new ExportRequest(request);
+        eq.files = files;
+        eq.mavenWrapper = true;
+        eq.exportDir = runDir.toString();
+        if (eq.gav == null) {
+            if (eq.name == null) {
+                eq.name = "jbang-run-dummy";
+            }
+            eq.gav = "org.example.project:" + eq.name + ":1.0-SNAPSHOT";
+        }
+        eq.dependencies = new ArrayList<>(request.dependencies);
+        eq.dependencies.add("camel:cli-connector");
+        if (request.jfrEnabled()) {
+            eq.dependencies.add("camel:jfr");
+        }
+        eq.quiet = true;
+        eq.logging = false;
+        eq.loggingLevel = "off";
+        eq.consoleForRun = true;
+        return eq;
+    }
+
+    /**
+     * Reads the pom.xml of an existing Maven project.
+     */
+    static Model readPom(Path pom) throws Exception {
+        try (Reader reader = Files.newBufferedReader(pom)) {
+            return new MavenXpp3Reader().read(reader);
+        }
+    }
+
+    /**
+     * Adds the dependency to the project, unless it has it already.
+     */
+    static void addDependency(Model model, String groupId, String artifactId) {
+        boolean found = model.getDependencies().stream().anyMatch(d -> 
artifactId.equals(d.getArtifactId()));
+        if (!found) {
+            Dependency d = new Dependency();
+            d.setGroupId(groupId);
+            d.setArtifactId(artifactId);
+            model.getDependencies().add(d);
+        }
+    }
+
+    /**
+     * Writes the pom.xml that an existing Maven project is run with, next to 
its own, which is not changed.
+     */
+    static Path writeRunPom(Path projectDir, Model model) throws IOException {
+        Path tempPom = projectDir.resolve("camel-jbang-run-pom.xml");
+        try (FileOutputStream fos = new FileOutputStream(tempPom.toFile())) {
+            new MavenXpp3Writer().write(fos, model);
+        }
+        return tempPom;
+    }
+
+    /**
+     * The Quarkus logging properties that make an existing Quarkus project 
also log to the {@code <name>.log} file in
+     * the Camel directory (the same as the exported project does via its 
application.properties).
+     */
+    static List<String> quarkusLogFileJvmArgs(String appName) {
+        String fileName = 
runLogFile(appName).toAbsolutePath().toString().replace("\\", "/");
+        List<String> args = new ArrayList<>();
+        args.add("-Dquarkus.log.file.enabled=true");
+        args.add("-Dquarkus.log.file.path=" + fileName);
+        // quoted as the format has spaces (quarkus:dev parses jvm.args as a 
command line)
+        args.add("-Dquarkus.log.file.format=\"" + QUARKUS_LOG_FILE_FORMAT + 
"\"");
+        return args;
+    }
+
+    /**
+     * The JVM arguments ({@code jvm.args}) an existing Quarkus project is run 
with: logging to file, the profile, the
+     * name, the port and properties, the flight recording and {@code 
--jvm-args}.
+     */
+    static List<String> existingQuarkusJvmArgs(RunRequest request, String 
appName, Printer printer) {
+        List<String> args = new ArrayList<>(quarkusLogFileJvmArgs(appName));
+        args.addAll(existingProjectJvmArgs(request, appName, 
"quarkus.http.port", printer));
+        return args;
+    }
+
+    /**
+     * The JVM arguments ({@code spring-boot.run.jvmArguments}) an existing 
Spring Boot project is run with: logging to
+     * file (the given logback configuration, unless null), the profile, the 
name (so the app reports the name of its
+     * log file), the port and properties, the flight recording and {@code 
--jvm-args}.
+     */
+    static List<String> existingSpringBootJvmArgs(RunRequest request, String 
appName, Path logback, Printer printer) {
+        List<String> args = new ArrayList<>();
+        if (logback != null) {
+            args.add("-Dlogging.config=" + logback.toUri());
+        }
+        args.addAll(existingProjectJvmArgs(request, appName, "server.port", 
printer));
+        return args;
+    }
+
+    private static List<String> existingProjectJvmArgs(
+            RunRequest request, String appName, String portKey, Printer 
printer) {
+        List<String> args = new ArrayList<>();
+        if (request.profile != null && !"prod".equals(request.profile)) {
+            args.add("-Dcamel.main.profile=" + request.profile);
+        }
+        args.add("-Dcamel.main.name=" + appName);
+        args.addAll(existingProjectSystemProperties(request, portKey));
+        String jfrArg = jfrJvmArgs(request, printer);
+        if (jfrArg != null) {
+            args.add(jfrArg);
+        }
+        if (request.jvmArgs != null && !request.jvmArgs.isBlank()) {
+            args.add(request.jvmArgs.trim());
+        }
+        return args;
+    }
+
+    /**
+     * Writes the logback configuration used by {@code camel run 
--runtime=spring-boot} to the given file. It logs to
+     * the console, and to the {@code <name>.log} file in the Camel directory, 
as the other runtimes do, so the log can
+     * be kept as the previous run of the app (CAMEL-25832).
+     */
+    static void writeSpringBootLogbackConfig(Path target, String appName) 
throws IOException {
+        String fileName = 
runLogFile(appName).toAbsolutePath().toString().replace("\\", "/")
+                .replace("&", "&amp;").replace("<", "&lt;").replace("\"", 
"&quot;");
+        try (InputStream is = 
MavenRunSupport.class.getClassLoader().getResourceAsStream("spring-boot-logback.xml"))
 {
+            if (is == null) {
+                throw new IOException("spring-boot-logback.xml not found");
+            }
+            String content = new String(is.readAllBytes(), 
StandardCharsets.UTF_8).replace("{{logFile}}", fileName);
+            Files.createDirectories(target.getParent());
+            Files.writeString(target, content);
+        }
+    }
+
+    /**
+     * The cleanup runs once: when the integration is stopped, or when this 
JVM exits.
+     */
+    static ChildProcess.Cleanup once(ChildProcess.Cleanup cleanup) {
+        AtomicBoolean done = new AtomicBoolean();
+        return () -> {
+            if (done.compareAndSet(false, true)) {
+                cleanup.run();
+            }
+        };
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/ProjectRunner.java
 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/ProjectRunner.java
index 814dd0c94696..c69e2bd7a835 100644
--- 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/ProjectRunner.java
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/ProjectRunner.java
@@ -20,8 +20,9 @@ import org.apache.camel.dsl.jbang.export.ProjectExporter;
 import org.apache.camel.dsl.jbang.export.common.RuntimeType;
 
 /**
- * Runs Camel files, the Java API of camel run. A run is an export with a run 
on top: with Camel Main, the files are
- * exported to a Maven project in a directory of the run, which is built with 
Maven and started in a JVM of its own; an
+ * Runs Camel files, the Java API of camel run. A run is an export with a run 
on top: with Camel Main, Quarkus and
+ * Spring Boot, the files are exported to a Maven project in a directory of 
the run, which is built and started with
+ * Maven (Camel Main in a JVM of its own, Quarkus with quarkus:run or 
quarkus:dev, Spring Boot with spring-boot:run); an
  * existing Maven project (a pom.xml as the file to run) is run with Maven as 
it is.
  *
  * <pre>
@@ -32,12 +33,17 @@ import org.apache.camel.dsl.jbang.export.common.RuntimeType;
  * int exit = integration.waitFor();
  * </pre>
  *
- * Only the Camel Main runtime is run so far; camel run runs the others itself.
+ * Only the runtimes that run with Maven so far: camel run runs the jbang 
runtime in its own JVM itself.
  *
  * @since 4.24
  */
 public class ProjectRunner extends ProjectExporter {
 
+    /**
+     * The directory with the projects that are exported to be run, one 
directory per run.
+     */
+    public static final String RUN_PLATFORM_DIR = ".camel-jbang-run";
+
     public ProjectRunner() {
         this(new RunContext());
     }
@@ -53,7 +59,8 @@ public class ProjectRunner extends ProjectExporter {
 
     /**
      * Starts the integration: exports and builds the project (the output of 
Maven goes to the console of this JVM), and
-     * starts the application.
+     * starts the application. With Quarkus and Spring Boot, and an existing 
project, the process of the integration is
+     * Maven, which runs the application; stopping it stops the application 
too.
      *
      * @param  request the request, which is not changed
      * @return         the running integration, or a run that ended with the 
exit code of the failure (such as a build
@@ -62,6 +69,10 @@ public class ProjectRunner extends ProjectExporter {
     public RunningIntegration start(RunRequest request) throws Exception {
         if (request.runtime == RuntimeType.main) {
             return new CamelMainRunner(this, request).start();
+        } else if (request.runtime == RuntimeType.quarkus) {
+            return new QuarkusRunner(this, request).start();
+        } else if (request.runtime == RuntimeType.springBoot) {
+            return new SpringBootRunner(this, request).start();
         }
         throw new IllegalArgumentException("Cannot run the runtime " + 
request.runtime + " with ProjectRunner");
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/QuarkusRunner.java
 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/QuarkusRunner.java
new file mode 100644
index 000000000000..b862b927eb6b
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/QuarkusRunner.java
@@ -0,0 +1,194 @@
+/*
+ * 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.export.run;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.camel.dsl.jbang.export.ExportRequest;
+import org.apache.camel.dsl.jbang.export.common.ChildProcess;
+import org.apache.camel.dsl.jbang.export.common.MavenCommand;
+import org.apache.camel.dsl.jbang.export.common.Printer;
+import org.apache.camel.dsl.jbang.export.common.RuntimeType;
+import org.apache.maven.model.Model;
+
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.QUARKUS_LOG_FILE_FORMAT;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.addDependency;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.existingQuarkusJvmArgs;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.exportedRunJvmArgs;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.keepLeftoverRunLog;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.keepRunLog;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.once;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.readPom;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.resolveExistingProjectAppName;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.resolveExportedAppName;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.runExportRequest;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.runLogFile;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.writeRunPom;
+
+/**
+ * Runs with Quarkus: the files are exported to a Quarkus project in a 
directory of the run, which is run with mvn
+ * quarkus:run (quarkus:dev in dev mode); an existing Quarkus project is run 
the same way.
+ */
+final class QuarkusRunner {
+
+    private final ProjectRunner runner;
+    private final RunRequest request;
+
+    QuarkusRunner(ProjectRunner runner, RunRequest request) {
+        this.runner = runner;
+        this.request = request;
+    }
+
+    RunningIntegration start() throws Exception {
+        // existing Maven project: run directly without export
+        if (request.files != null && request.files.size() == 1 && 
request.files.get(0).endsWith("pom.xml")) {
+            return startExistingProject();
+        }
+
+        AtomicReference<Process> processRef = new AtomicReference<>();
+        AtomicReference<String> appNameRef = new AtomicReference<>();
+
+        // create temp run dir
+        Path runDirPath = Paths.get(ProjectRunner.RUN_PLATFORM_DIR, 
Long.toString(System.currentTimeMillis()));
+        // the process (and those it started) stops with this JVM, then the 
files of the run are removed
+        ChildProcess.Cleanup cleanup = once(() -> {
+            RunPipeline.removeDir(runDirPath);
+            // the log is kept as the previous run of the app (CAMEL-25832)
+            keepRunLog(appNameRef.get(), processRef.get(), false);
+        });
+        ChildProcess.stopOnExit(processRef::get, cleanup);
+        Files.createDirectories(runDirPath);
+
+        // export to hidden folder
+        ExportRequest eq = runExportRequest(request, runDirPath, 
request.files);
+        eq.javaLiveReload = request.dev;
+        eq.symbolicLink = request.dev;
+        eq.runtime = RuntimeType.quarkus;
+
+        printer().println("Running using Quarkus (preparing and downloading 
files)");
+
+        // run export
+        int exit = runner.export(eq.baseDir, eq);
+        if (exit != 0) {
+            return new EndedIntegration(exit);
+        }
+
+        // the exported project may have derived the application name from the 
source files, and the log file
+        // must be named after the name the application reports (which is what 
camel log and the TUI look for)
+        String appName = resolveExportedAppName(runDirPath, eq.name);
+        appNameRef.set(appName);
+        keepLeftoverRunLog(appName);
+
+        // prepare quarkus for logging to file
+        Path appProps = Paths.get(eq.exportDir, 
"src/main/resources/application.properties");
+        if (Files.exists(appProps)) {
+            String content = Files.readString(appProps);
+            // in the Camel directory, where camel log looks for it (as for an 
existing Quarkus project)
+            String logFile = 
runLogFile(appName).toAbsolutePath().toString().replace("\\", "/");
+            content += "\n# logging to file\n"
+                       + "quarkus.log.file.enabled=true\n"
+                       + "quarkus.log.file.path=" + logFile + "\n"
+                       + "quarkus.log.file.format=" + QUARKUS_LOG_FILE_FORMAT 
+ "\n";
+            Files.writeString(appProps, content);
+        }
+
+        // run quarkus via maven
+        List<String> mvnCmd = new ArrayList<>();
+        mvnCmd.add(MavenCommand.wrapper(runDirPath).toString());
+        mvnCmd.add("--quiet");
+        mvnCmd.add("--file");
+        mvnCmd.add(runDirPath.toRealPath().resolve("pom.xml").toString());
+        String quarkusRunJvmArgs = exportedRunJvmArgs(request, printer());
+        if (quarkusRunJvmArgs != null) {
+            mvnCmd.add("-Djvm.args=" + quarkusRunJvmArgs);
+        }
+        if (request.console) {
+            // the console extension only exposes the console in dev and test 
mode, and camel run without --dev is
+            // quarkus:run (prod mode). This is build-time configuration, and 
is set on the build of this temporary
+            // project only, so an exported project does not have the console 
exposed in prod mode
+            mvnCmd.add("-Dquarkus.camel.console.exposure-mode=ALL");
+        }
+        mvnCmd.add("package");
+        mvnCmd.add("quarkus:" + (request.dev ? "dev" : "run"));
+
+        ProcessBuilder pb = new ProcessBuilder();
+        pb.command(mvnCmd);
+        pb.inheritIO(); // run in foreground (with IO so logs are visible)
+        Process p = runner.getContext().startProcess(pb);
+        processRef.set(p);
+        return new ProcessIntegration(p, cleanup);
+    }
+
+    private RunningIntegration startExistingProject() throws Exception {
+        AtomicReference<Process> processRef = new AtomicReference<>();
+        Path pomPath = Path.of(request.files.get(0)).toAbsolutePath();
+        Path projectDir = pomPath.getParent();
+
+        // read the pom.xml and add camel-quarkus-cli-connector dependency
+        Model model = readPom(pomPath);
+        addDependency(model, "org.apache.camel.quarkus", 
"camel-quarkus-cli-connector");
+        if (request.jfrEnabled()) {
+            addDependency(model, "org.apache.camel.quarkus", 
"camel-quarkus-jfr");
+        }
+        // write temporary pom with cli-connector injected
+        Path tempPom = writeRunPom(projectDir, model);
+
+        // log to console and to file in ~/.camel (so camel log and the TUI 
can tail the logs)
+        String appName = resolveExistingProjectAppName(projectDir, model);
+        keepLeftoverRunLog(appName);
+
+        // the process (and those it started) stops with this JVM, then the 
files of the run are removed
+        ChildProcess.Cleanup cleanup = once(() -> {
+            Files.deleteIfExists(tempPom);
+            // the log is kept as the previous run of the app (CAMEL-25832)
+            keepRunLog(appName, processRef.get(), false);
+        });
+        ChildProcess.stopOnExit(processRef::get, cleanup);
+
+        // find mvnw or fall back to mvn
+        String mvnCmd = MavenCommand.of(projectDir);
+
+        List<String> cmd = new ArrayList<>();
+        cmd.add(mvnCmd);
+        cmd.add("--quiet");
+        cmd.add("--file");
+        cmd.add(tempPom.toString());
+        cmd.add("-Djvm.args=" + String.join(" ", 
existingQuarkusJvmArgs(request, appName, printer())));
+        cmd.add("-DskipTests");
+        cmd.add("package");
+        cmd.add("quarkus:" + (request.dev ? "dev" : "run"));
+
+        printer().println("Running Quarkus project: " + projectDir);
+
+        ProcessBuilder pb = new ProcessBuilder();
+        pb.command(cmd);
+        pb.directory(projectDir.toFile());
+        pb.inheritIO();
+        Process p = runner.getContext().startProcess(pb);
+        processRef.set(p);
+        return new ProcessIntegration(p, cleanup);
+    }
+
+    private Printer printer() {
+        return runner.getContext().getPrinter();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/RunPipeline.java
 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/RunPipeline.java
index cc22012b6624..0eab47148578 100644
--- 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/RunPipeline.java
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/RunPipeline.java
@@ -500,10 +500,10 @@ public class RunPipeline {
             writeSetting(main, profileProperties, "camel.management.port",
                     () -> String.valueOf(request.managementPort));
         }
-        writeSetting(main, profileProperties, JFR, jfrEnabled() ? "jfr" : 
null);
+        writeSetting(main, profileProperties, JFR, request.jfrEnabled() ? 
"jfr" : null);
         writeSetting(main, profileProperties, JFR_PROFILE,
                 request.jfrProfile != null ? request.jfrProfile : null);
-        if (jfrEnabled()) {
+        if (request.jfrEnabled()) {
             // camel-jfr is always on the classpath, but its runtime 
instrumentation is opt-in
             writeSetting(main, profileProperties, 
"camel.main.startupRecorderRuntimeEnabled", "true");
         }
@@ -859,7 +859,7 @@ public class RunPipeline {
                 writeSetting(main, profileProperties, "camel.server.enabled", 
"true");
             }
         }
-        if (jfrEnabled()) {
+        if (request.jfrEnabled()) {
             request.dependencies.add("camel:jfr");
         }
         if (!request.dependencies.isEmpty()) {
@@ -884,10 +884,6 @@ public class RunPipeline {
         settings.write(main, existing, key, value);
     }
 
-    private boolean jfrEnabled() {
-        return request.jfr || request.jfrProfile != null;
-    }
-
     private boolean isMcpEnabled(Properties profileProperties) {
         String val = profileProperties != null
                 ? profileProperties.getProperty(MCP, request.mcp ? "true" : 
"false")
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/RunRequest.java
 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/RunRequest.java
index f5df523b4332..0cf5c9d69db5 100644
--- 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/RunRequest.java
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/RunRequest.java
@@ -214,4 +214,18 @@ public class RunRequest extends ExportRequest {
     public RunRequest(ExportRequest export) {
         super(export);
     }
+
+    /**
+     * Whether the run records a flight recording ({@link #jfr} or {@link 
#jfrProfile}).
+     */
+    public boolean jfrEnabled() {
+        return jfr || jfrProfile != null;
+    }
+
+    /**
+     * The file of the flight recording: the name of the integration, else 
camel.
+     */
+    public String jfrFileName() {
+        return (name != null ? name : "camel") + ".jfr";
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/SpringBootRunner.java
 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/SpringBootRunner.java
new file mode 100644
index 000000000000..b2f75deefb64
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/run/SpringBootRunner.java
@@ -0,0 +1,212 @@
+/*
+ * 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.export.run;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.camel.dsl.jbang.export.ExportRequest;
+import org.apache.camel.dsl.jbang.export.common.ChildProcess;
+import org.apache.camel.dsl.jbang.export.common.MavenCommand;
+import org.apache.camel.dsl.jbang.export.common.Printer;
+import org.apache.camel.dsl.jbang.export.common.RuntimeType;
+import org.apache.maven.model.Model;
+
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.addDependency;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.existingSpringBootJvmArgs;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.exportedRunJvmArgs;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.keepLeftoverRunLog;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.keepRunLog;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.once;
+import static org.apache.camel.dsl.jbang.export.run.MavenRunSupport.readPom;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.resolveExistingProjectAppName;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.resolveExportedAppName;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.runExportRequest;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.writeRunPom;
+import static 
org.apache.camel.dsl.jbang.export.run.MavenRunSupport.writeSpringBootLogbackConfig;
+
+/**
+ * Runs with Spring Boot: the files are exported to a Spring Boot project in a 
directory of the run, which is run with
+ * mvn spring-boot:run; an existing Spring Boot project is run the same way.
+ */
+final class SpringBootRunner {
+
+    private final ProjectRunner runner;
+    private final RunRequest request;
+
+    SpringBootRunner(ProjectRunner runner, RunRequest request) {
+        this.runner = runner;
+        this.request = request;
+    }
+
+    RunningIntegration start() throws Exception {
+        // existing Maven project: run directly without export
+        if (request.files != null && request.files.size() == 1 && 
request.files.get(0).endsWith("pom.xml")) {
+            return startExistingProject();
+        }
+
+        AtomicReference<Process> processRef = new AtomicReference<>();
+        AtomicReference<String> appNameRef = new AtomicReference<>();
+
+        // create temp run dir
+        Path runDirPath = Paths.get(ProjectRunner.RUN_PLATFORM_DIR, 
Long.toString(System.currentTimeMillis()));
+        // the process (and those it started) stops with this JVM, then the 
files of the run are removed
+        ChildProcess.Cleanup cleanup = once(() -> {
+            RunPipeline.removeDir(runDirPath);
+            // the log is kept as the previous run of the app (CAMEL-25832)
+            keepRunLog(appNameRef.get(), processRef.get(), false);
+        });
+        ChildProcess.stopOnExit(processRef::get, cleanup);
+        Files.createDirectories(runDirPath);
+
+        // the code reload is not supported, since we use symlink, spring-boot 
devtools doesn't support symlink
+        if (request.dev) {
+            printer().printWarn("Code reload is not supported with Spring 
Boot.");
+        }
+
+        // export to hidden folder
+        ExportRequest eq = runExportRequest(request, runDirPath, 
request.files);
+        eq.javaLiveReload = false;
+        eq.symbolicLink = request.dev;
+        eq.runtime = RuntimeType.springBoot;
+        eq.camelSpringBootVersion
+                = request.camelSpringBootVersion != null ? 
request.camelSpringBootVersion : request.camelVersion;
+        if (request.dev) {
+            // hot-reload of spring-boot
+            
eq.dependencies.add("mvn:org.springframework.boot:spring-boot-devtools");
+        }
+
+        printer().println("Running using Spring Boot (preparing and 
downloading files)");
+
+        // run export
+        int exit = runner.export(eq.baseDir, eq);
+        if (exit != 0) {
+            return new EndedIntegration(exit);
+        }
+
+        // the exported project may have derived the application name from the 
source files, and the log file is
+        // named after the name the application reports (what camel log and 
the TUI look for)
+        String appName = resolveExportedAppName(runDirPath, eq.name);
+        appNameRef.set(appName);
+        keepLeftoverRunLog(appName);
+
+        // prepare spring-boot for logging to file
+        writeSpringBootLogbackConfig(Paths.get(eq.exportDir, 
"src/main/resources/logback.xml"), appName);
+
+        // run spring-boot via maven
+        List<String> mvnCmd = new ArrayList<>();
+        mvnCmd.add(MavenCommand.wrapper(runDirPath).toString());
+        mvnCmd.add("--quiet");
+        mvnCmd.add("--file");
+        mvnCmd.add(runDirPath.toRealPath().resolve("pom.xml").toString());
+        String springBootRunJvmArgs = exportedRunJvmArgs(request, printer());
+        if (springBootRunJvmArgs != null) {
+            mvnCmd.add("-Dspring-boot.run.jvmArguments=" + 
springBootRunJvmArgs);
+        }
+        // run in the current directory, not the export folder, so the routes 
read and write files relative to where
+        // camel run is started (CAMEL-25423)
+        mvnCmd.add("-Dspring-boot.run.workingDirectory=" + 
Paths.get(".").toAbsolutePath().normalize());
+        mvnCmd.add("spring-boot:run");
+
+        ProcessBuilder pb = new ProcessBuilder();
+        pb.command(mvnCmd);
+        pb.inheritIO(); // run in foreground (with IO so logs are visible)
+        Process p = runner.getContext().startProcess(pb);
+        processRef.set(p);
+        return new ProcessIntegration(p, cleanup);
+    }
+
+    private RunningIntegration startExistingProject() throws Exception {
+        AtomicReference<Process> processRef = new AtomicReference<>();
+        Path pomPath = Path.of(request.files.get(0)).toAbsolutePath();
+        Path projectDir = pomPath.getParent();
+
+        // read the pom.xml and add camel-cli-connector-starter dependency
+        Model model = readPom(pomPath);
+        addDependency(model, "org.apache.camel.springboot", 
"camel-cli-connector-starter");
+        if (request.jfrEnabled()) {
+            addDependency(model, "org.apache.camel.springboot", 
"camel-jfr-starter");
+        }
+        // write temporary pom with cli-connector injected
+        Path tempPom = writeRunPom(projectDir, model);
+
+        // log to console and to file in ~/.camel (so camel log and the TUI 
can tail the logs)
+        String appName = resolveExistingProjectAppName(projectDir, model);
+        keepLeftoverRunLog(appName);
+        Path logback = logbackConfig(appName);
+
+        // the process (and those it started) stops with this JVM, then the 
files of the run are removed
+        ChildProcess.Cleanup cleanup = once(() -> {
+            Files.deleteIfExists(tempPom);
+            if (logback != null) {
+                Files.deleteIfExists(logback);
+            }
+            // the log is kept as the previous run of the app (CAMEL-25832)
+            keepRunLog(appName, processRef.get(), false);
+        });
+        ChildProcess.stopOnExit(processRef::get, cleanup);
+
+        // find mvnw or fall back to mvn
+        String mvnCmd = MavenCommand.of(projectDir);
+
+        List<String> cmd = new ArrayList<>();
+        cmd.add(mvnCmd);
+        cmd.add("--quiet");
+        cmd.add("--file");
+        cmd.add(tempPom.toString());
+        cmd.add("-Dspring-boot.run.jvmArguments="
+                + String.join(" ", existingSpringBootJvmArgs(request, appName, 
logback, printer())));
+        cmd.add("-DskipTests");
+        cmd.add("package");
+        cmd.add("spring-boot:run");
+
+        printer().println("Running Spring Boot project: " + projectDir);
+
+        ProcessBuilder pb = new ProcessBuilder();
+        pb.command(cmd);
+        pb.directory(projectDir.toFile());
+        pb.inheritIO();
+        Process p = runner.getContext().startProcess(pb);
+        processRef.set(p);
+        return new ProcessIntegration(p, cleanup);
+    }
+
+    /**
+     * The logback configuration an existing Spring Boot project runs with, 
logging to the {@code <name>.log} file so
+     * the TUI can read the log: a temp file, so the project is not changed 
and needs no src/main/resources. Null when
+     * it cannot be written.
+     */
+    static Path logbackConfig(String appName) {
+        try {
+            Path file = Files.createTempFile("camel-jbang-logback-", ".xml");
+            file.toFile().deleteOnExit();
+            writeSpringBootLogbackConfig(file, appName);
+            return file;
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private Printer printer() {
+        return runner.getContext().getPrinter();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/spring-boot-logback.xml 
b/dsl/camel-jbang/camel-jbang-export/src/main/resources/spring-boot-logback.xml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/spring-boot-logback.xml
rename to 
dsl/camel-jbang/camel-jbang-export/src/main/resources/spring-boot-logback.xml
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/ProjectRunnerTest.java
 
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/ProjectRunnerTest.java
index 5e5848033634..c1e999869952 100644
--- 
a/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/ProjectRunnerTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/ProjectRunnerTest.java
@@ -64,7 +64,8 @@ class ProjectRunnerTest {
             .withJavaHome(Path.of("/opt/jdk"))
             .withProcessStarter(pb -> {
                 started.add(pb);
-                if (pb.command().contains("package")) {
+                if (pb.command().contains("package")
+                        && pb.command().stream().noneMatch(a -> 
a.startsWith("quarkus:") || a.startsWith("spring-boot:"))) {
                     // the runner JAR that the build of a Camel Main project 
makes
                     Path project = 
Path.of(pb.command().get(pb.command().indexOf("--file") + 1)).getParent();
                     Files.createDirectories(project.resolve("target"));
@@ -88,7 +89,7 @@ class ProjectRunnerTest {
             List<String> build = started.get(0).command();
             assertThat(build).endsWith("--quiet", "--file", build.get(3), 
"-DskipTests", "package");
             Path project = Path.of(build.get(3)).getParent();
-            
assertThat(project.getParent().getFileName()).hasToString(MavenRunSupport.RUN_PLATFORM_DIR);
+            
assertThat(project.getParent().getFileName()).hasToString(ProjectRunner.RUN_PLATFORM_DIR);
             assertThat(project.resolve("pom.xml")).exists();
             
assertThat(Files.readString(project.resolve("pom.xml"))).contains("camel-cli-connector");
 
@@ -159,14 +160,121 @@ class ProjectRunnerTest {
     }
 
     @Test
-    void theOtherRuntimesAreNotRunYet() {
+    void aSpringBootRunExportsTheProjectAndRunsItWithMaven() throws Exception {
         RunRequest request = request();
-        request.runtime = RuntimeType.quarkus;
+        request.runtime = RuntimeType.springBoot;
+        request.jvmArgs = "-Xmx256m";
+        request.maxSeconds = 30;
+
+        RunningIntegration integration = new 
ProjectRunner(context).start(request);
+        try {
+            // spring-boot:run builds and runs the application: the process of 
the integration is Maven
+            assertThat(started).hasSize(1);
+            List<String> cmd = started.get(0).command();
+            Path project = Path.of(cmd.get(3)).getParent();
+            
assertThat(project.getParent().getFileName()).hasToString(ProjectRunner.RUN_PLATFORM_DIR);
+            assertThat(cmd.subList(1, cmd.size())).containsExactly(
+                    "--quiet", "--file", cmd.get(3),
+                    "-Dspring-boot.run.jvmArguments=-Xmx256m 
-Dcamel.main.durationMaxSeconds=30",
+                    "-Dspring-boot.run.workingDirectory=" + 
Path.of(".").toAbsolutePath().normalize(),
+                    "spring-boot:run");
+            
assertThat(project.resolve("pom.xml")).content().contains("camel-cli-connector-starter");
+            // the application logs to the log of its name, which is kept as 
its previous run
+            
assertThat(project.resolve("src/main/resources/logback.xml")).exists();
+            assertThat(printer.getOutput()).contains("Running using Spring 
Boot (preparing and downloading files)");
+
+            assertThat(integration.pid()).isEqualTo(4242);
+            assertThat(integration.waitFor()).isEqualTo(7);
+            integration.stop();
+            assertThat(project).doesNotExist();
+        } finally {
+            integration.stop();
+        }
+    }
+
+    @Test
+    void anExistingQuarkusProjectRunsWithMaven() throws Exception {
+        RunRequest request = existingProject(RuntimeType.quarkus);
+        request.dev = true;
+        request.port = 9090;
+        request.jvmArgs = "-Xmx256m";
+
+        RunningIntegration integration = new 
ProjectRunner(context).start(request);
+        try {
+            assertThat(started).hasSize(1);
+            ProcessBuilder pb = started.get(0);
+            Path project = dir.resolve("project");
+            assertThat(pb.directory()).isEqualTo(project.toFile());
+            List<String> cmd = pb.command();
+            assertThat(cmd.subList(1, 4)).containsExactly("--quiet", "--file",
+                    project.resolve("camel-jbang-run-pom.xml").toString());
+            
assertThat(cmd.get(4)).startsWith("-Djvm.args=-Dquarkus.log.file.enabled=true ")
+                    .contains("-Dcamel.main.name=orders", 
"-Dquarkus.http.port=9090").endsWith(" -Xmx256m");
+            assertThat(cmd.subList(5, 
cmd.size())).containsExactly("-DskipTests", "package", "quarkus:dev");
+            // the project is run with a pom of its own, which adds the CLI 
connector
+            
assertThat(project.resolve("camel-jbang-run-pom.xml")).content().contains("camel-quarkus-cli-connector");
+            assertThat(printer.getOutput()).contains("Running Quarkus project: 
" + project);
+
+            assertThat(integration.pid()).isEqualTo(4242);
+            integration.stop();
+            
assertThat(project.resolve("camel-jbang-run-pom.xml")).doesNotExist();
+            assertThat(project.resolve("pom.xml")).exists();
+        } finally {
+            integration.stop();
+        }
+    }
+
+    @Test
+    void anExistingSpringBootProjectRunsWithMaven() throws Exception {
+        RunRequest request = existingProject(RuntimeType.springBoot);
+        request.jfr = true;
+
+        RunningIntegration integration = new 
ProjectRunner(context).start(request);
+        try {
+            assertThat(started).hasSize(1);
+            Path project = dir.resolve("project");
+            List<String> cmd = started.get(0).command();
+            
assertThat(cmd.get(4)).startsWith("-Dspring-boot.run.jvmArguments=-Dlogging.config=file:")
+                    .contains("-Dcamel.main.name=orders", 
"-XX:StartFlightRecording=filename=CamelJBang.jfr");
+            assertThat(cmd.subList(5, 
cmd.size())).containsExactly("-DskipTests", "package", "spring-boot:run");
+            assertThat(project.resolve("camel-jbang-run-pom.xml")).content()
+                    .contains("camel-cli-connector-starter", 
"camel-jfr-starter");
+            assertThat(printer.getOutput()).contains("Running Spring Boot 
project: " + project);
+
+            integration.stop();
+            
assertThat(project.resolve("camel-jbang-run-pom.xml")).doesNotExist();
+        } finally {
+            integration.stop();
+        }
+    }
+
+    @Test
+    void theJBangRuntimeIsNotRunYet() {
+        RunRequest request = request();
+        request.runtime = RuntimeType.jbang;
 
         assertThatThrownBy(() -> new ProjectRunner(context).start(request))
                 .isInstanceOf(IllegalArgumentException.class);
     }
 
+    private RunRequest existingProject(RuntimeType runtime) throws Exception {
+        Path project = dir.resolve("project");
+        Files.createDirectories(project);
+        Files.writeString(project.resolve("pom.xml"), """
+                <project>
+                  <modelVersion>4.0.0</modelVersion>
+                  <groupId>com.acme</groupId>
+                  <artifactId>orders</artifactId>
+                  <version>1.0</version>
+                </project>
+                """);
+        RunRequest request = new RunRequest();
+        request.files.add(project.resolve("pom.xml").toString());
+        request.runtime = runtime;
+        request.workDir = dir.resolve("work");
+        return request;
+    }
+
     private RunRequest request() {
         RunRequest request = new RunRequest();
         Path route = dir.resolve("hello.camel.yaml");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExistingProjectTest.java
 
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunExistingProjectTest.java
similarity index 71%
rename from 
dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExistingProjectTest.java
rename to 
dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunExistingProjectTest.java
index 86ee990b903e..d4fa9f948429 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunExistingProjectTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunExistingProjectTest.java
@@ -14,32 +14,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.dsl.jbang.core.commands;
+package org.apache.camel.dsl.jbang.export.run;
 
 import java.net.URI;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
 
-import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
-import org.apache.camel.dsl.jbang.export.run.MavenRunSupport;
+import org.apache.camel.dsl.jbang.export.common.CamelHome;
+import org.apache.camel.dsl.jbang.export.common.StringPrinter;
 import org.apache.maven.model.Model;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.api.parallel.Isolated;
-import picocli.CommandLine;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
  * Verifies how {@code camel run pom.xml} runs an existing Maven project: the 
application logs to a file in
  * {@code ~/.camel} that {@code camel log} and the TUI can read, and the 
options are passed to the right JVM for each
- * runtime.
+ * runtime. Isolated, as it changes the home directory of the JVM, which the 
other tests use.
  */
 @Isolated
-class RunExistingProjectTest extends CamelCommandBaseTestSupport {
+class RunExistingProjectTest {
 
     @TempDir
     Path home;
@@ -48,19 +47,25 @@ class RunExistingProjectTest extends 
CamelCommandBaseTestSupport {
 
     @BeforeEach
     void useTempHome() {
-        originalHome = CommandLineHelper.getHomeDir().toString();
-        CommandLineHelper.useHomeDir(home.toString());
+        originalHome = CamelHome.getHomeDir().toString();
+        CamelHome.useHomeDir(home.toString());
     }
 
     @AfterEach
     void restoreHome() {
-        CommandLineHelper.useHomeDir(originalHome);
+        CamelHome.useHomeDir(originalHome);
     }
 
-    private static Run run(String... args) {
-        Run command = new Run(new CamelJBangMain());
-        CommandLine.populateCommand(command, args);
-        return command;
+    private final StringPrinter printer = new StringPrinter();
+
+    /**
+     * The request of camel run pom.xml with the dev profile, the default of 
camel run.
+     */
+    private static RunRequest request() {
+        RunRequest request = new RunRequest();
+        request.files.add("pom.xml");
+        request.profile = "dev";
+        return request;
     }
 
     private static Model model(String artifactId) {
@@ -89,7 +94,7 @@ class RunExistingProjectTest extends 
CamelCommandBaseTestSupport {
     @Test
     void 
camelMainLogsToFileViaLog4j2ConfigurationOnTheMavenCommandLine(@TempDir Path 
project) throws Exception {
         Path logConfig = 
project.resolve("target").resolve(MavenRunSupport.CAMEL_MAIN_RUN_LOG_CONFIG);
-        Path logFile = CommandLineHelper.getCamelDir().resolve("my-app.log");
+        Path logFile = CamelHome.getCamelDir().resolve("my-app.log");
         Files.createDirectories(logFile.getParent());
         Files.writeString(logFile, "stale output of a previous run");
 
@@ -97,16 +102,19 @@ class RunExistingProjectTest extends 
CamelCommandBaseTestSupport {
 
         // the log of the last run is kept as its previous run (CAMEL-25832), 
so the readers do not show stale lines
         assertThat(logFile).doesNotExist();
-        
assertThat(CommandLineHelper.getCamelDir().resolve("my-app-previous.log")).hasContent("stale
 output of a previous run");
+        
assertThat(CamelHome.getCamelDir().resolve("my-app-previous.log")).hasContent("stale
 output of a previous run");
         String content = Files.readString(logConfig);
         assertThat(content).contains("appender.file.fileName = " + 
logFile.toAbsolutePath().toString().replace("\\", "/"));
         assertThat(content).contains("rootLogger.level = debug");
         assertThat(content).doesNotContain("{{");
 
-        List<String> args = MavenRunSupport.existingCamelMainSystemProperties(
-                run("--profile=dev", "--port=9090", "--max-seconds=30", 
"--max-messages=5",
-                        "--max-idle-seconds=10", "--prop=foo=bar", 
"--prop=-Dbar=baz", "pom.xml").toRunRequest(null),
-                "my-app", logConfig);
+        RunRequest request = request();
+        request.port = 9090;
+        request.maxSeconds = 30;
+        request.maxMessages = 5;
+        request.maxIdleSeconds = 10;
+        request.applicationProperties = new String[] { "foo=bar", "-Dbar=baz" 
};
+        List<String> args = 
MavenRunSupport.existingCamelMainSystemProperties(request, "my-app", logConfig);
         assertThat(args).containsExactly(
                 "-Dlog4j2.configurationFile=" + logConfig.toAbsolutePath(),
                 "-Dcamel.main.profile=dev",
@@ -126,15 +134,15 @@ class RunExistingProjectTest extends 
CamelCommandBaseTestSupport {
         assertThat(Files.readString(logConfig)).contains("rootLogger.level = 
info");
 
         // the default profile (dev) is passed, prod is not as it is the 
default of a Maven project
-        List<String> args
-                = 
MavenRunSupport.existingCamelMainSystemProperties(run("pom.xml").toRunRequest(null),
 "my-app", logConfig);
+        List<String> args = 
MavenRunSupport.existingCamelMainSystemProperties(request(), "my-app", 
logConfig);
         assertThat(args).containsExactly(
                 "-Dlog4j2.configurationFile=" + logConfig.toAbsolutePath(),
                 "-Dcamel.main.profile=dev",
                 "-Dcamel.main.name=my-app");
 
-        args = 
MavenRunSupport.existingCamelMainSystemProperties(run("--profile=prod", 
"pom.xml").toRunRequest(null), "my-app",
-                logConfig);
+        RunRequest prod = request();
+        prod.profile = "prod";
+        args = MavenRunSupport.existingCamelMainSystemProperties(prod, 
"my-app", logConfig);
         assertThat(args).containsExactly(
                 "-Dlog4j2.configurationFile=" + logConfig.toAbsolutePath(),
                 "-Dcamel.main.name=my-app");
@@ -142,16 +150,15 @@ class RunExistingProjectTest extends 
CamelCommandBaseTestSupport {
 
     @Test
     void quarkusLogsToFileViaJvmArgs() {
-        String logFile = 
CommandLineHelper.getCamelDir().resolve("my-app.log").toAbsolutePath().toString()
+        String logFile = 
CamelHome.getCamelDir().resolve("my-app.log").toAbsolutePath().toString()
                 .replace("\\", "/");
 
-        List<String> args = run("--profile=dev", "--port=9090", 
"--prop=foo=bar", "--jvm-args=-Xmx512m", "pom.xml")
-                .buildExistingQuarkusJvmArgs("my-app");
+        List<String> args = MavenRunSupport.existingQuarkusJvmArgs(options(), 
"my-app", printer);
 
         assertThat(args).containsExactly(
                 "-Dquarkus.log.file.enabled=true",
                 "-Dquarkus.log.file.path=" + logFile,
-                "-Dquarkus.log.file.format=\"" + Run.QUARKUS_LOG_FILE_FORMAT + 
"\"",
+                "-Dquarkus.log.file.format=\"" + 
MavenRunSupport.QUARKUS_LOG_FILE_FORMAT + "\"",
                 "-Dcamel.main.profile=dev",
                 "-Dcamel.main.name=my-app",
                 "-Dquarkus.http.port=9090",
@@ -161,11 +168,11 @@ class RunExistingProjectTest extends 
CamelCommandBaseTestSupport {
 
     @Test
     void springBootLogsToFileViaLogbackConfiguration() {
-        String logFile = 
CommandLineHelper.getCamelDir().resolve("my-app.log").toAbsolutePath().toString()
+        String logFile = 
CamelHome.getCamelDir().resolve("my-app.log").toAbsolutePath().toString()
                 .replace("\\", "/");
 
-        List<String> args = run("--profile=dev", "--port=9090", 
"--prop=foo=bar", "--jvm-args=-Xmx512m", "pom.xml")
-                .buildExistingSpringBootJvmArgs("my-app");
+        List<String> args = 
MavenRunSupport.existingSpringBootJvmArgs(options(), "my-app",
+                SpringBootRunner.logbackConfig("my-app"), printer);
 
         // the logback configuration is a temp file: the project needs no 
src/main/resources and is not changed; it logs
         // to <name>.log, as the other runtimes do, so the log is kept as the 
previous run (CAMEL-25832)
@@ -183,11 +190,23 @@ class RunExistingProjectTest extends 
CamelCommandBaseTestSupport {
 
     @Test
     void jfrIsPassedAsJvmArgument() {
-        List<String> args = run("--jfr", 
"pom.xml").buildExistingQuarkusJvmArgs("my-app");
+        RunRequest request = request();
+        request.jfr = true;
+        List<String> args = MavenRunSupport.existingQuarkusJvmArgs(request, 
"my-app", printer);
         assertThat(args).anyMatch(a -> 
a.startsWith("-XX:StartFlightRecording"));
 
-        args = run("--jfr", 
"pom.xml").buildExistingSpringBootJvmArgs("my-app");
+        args = MavenRunSupport.existingSpringBootJvmArgs(request, "my-app", 
null, printer);
         assertThat(args).anyMatch(a -> 
a.startsWith("-XX:StartFlightRecording"));
     }
 
+    /**
+     * The options of camel run --profile=dev --port=9090 --prop=foo=bar 
--jvm-args=-Xmx512m pom.xml.
+     */
+    private static RunRequest options() {
+        RunRequest request = request();
+        request.port = 9090;
+        request.applicationProperties = new String[] { "foo=bar" };
+        request.jvmArgs = "-Xmx512m";
+        return request;
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunExportedProjectTest.java
 
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunExportedProjectTest.java
new file mode 100644
index 000000000000..7de1f949eee5
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunExportedProjectTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.export.run;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.export.common.StringPrinter;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies how {@code camel run --runtime=quarkus|spring-boot} runs the 
exported project: the execution limits are
+ * passed to the child JVM like for Camel Main, and the log file is named 
after the name the application reports.
+ */
+class RunExportedProjectTest {
+
+    @TempDir
+    Path dir;
+
+    private final StringPrinter printer = new StringPrinter();
+
+    private String exportedRunJvmArgs(RunRequest request) {
+        return MavenRunSupport.exportedRunJvmArgs(request, printer);
+    }
+
+    private static RunRequest limits(int maxSeconds, int maxMessages, int 
maxIdleSeconds) {
+        RunRequest request = new RunRequest();
+        request.maxSeconds = maxSeconds;
+        request.maxMessages = maxMessages;
+        request.maxIdleSeconds = maxIdleSeconds;
+        return request;
+    }
+
+    @Test
+    void durationLimitsArePassedToExportedRun() {
+        String args = exportedRunJvmArgs(limits(30, 5, 10));
+
+        assertThat(args).isEqualTo("-Dcamel.main.durationMaxSeconds=30 
-Dcamel.main.durationMaxMessages=5"
+                                   + " 
-Dcamel.main.durationMaxIdleSeconds=10");
+    }
+
+    @Test
+    void durationLimitsAreMergedWithJvmArgs() {
+        RunRequest request = limits(30, 0, 0);
+        request.jvmArgs = "-Xmx512m";
+        String args = exportedRunJvmArgs(request);
+
+        assertThat(args).isEqualTo("-Xmx512m 
-Dcamel.main.durationMaxSeconds=30");
+    }
+
+    @Test
+    void noJvmArgsWhenNothingToPass() {
+        assertThat(exportedRunJvmArgs(new RunRequest())).isNull();
+    }
+
+    @Test
+    void jfrIsPassedAsJvmArgument() {
+        RunRequest request = new RunRequest();
+        request.jfr = true;
+        String args = exportedRunJvmArgs(request);
+
+        assertThat(args).contains("-XX:StartFlightRecording");
+    }
+
+    @Test
+    void durationLimitsAreTheSameForAllRuntimes() {
+        RunRequest request = limits(30, 0, 0);
+
+        List<String> limits = MavenRunSupport.durationLimitArgs(request);
+        
assertThat(limits).containsExactly("-Dcamel.main.durationMaxSeconds=30");
+        // exported project (camel run foo.yaml --runtime=quarkus|spring-boot)
+        assertThat(exportedRunJvmArgs(request)).isEqualTo(String.join(" ", 
limits));
+        // existing project (camel run pom.xml), per runtime
+        assertThat(MavenRunSupport.existingQuarkusJvmArgs(request, "my-app", 
printer)).containsAll(limits);
+        assertThat(MavenRunSupport.existingSpringBootJvmArgs(request, 
"my-app", null, printer)).containsAll(limits);
+        assertThat(MavenRunSupport.existingCamelMainSystemProperties(request, 
"my-app", dir.resolve("log4j2.properties")))
+                .containsAll(limits);
+    }
+
+    @Test
+    void appNameIsTheExportedCamelMainName() throws Exception {
+        Path resources = dir.resolve("src/main/resources");
+        Files.createDirectories(resources);
+        Files.writeString(resources.resolve("application.properties"), 
"camel.main.name=my-route\n");
+
+        assertThat(MavenRunSupport.resolveExportedAppName(dir, 
"CamelJBang")).isEqualTo("my-route");
+    }
+
+    @Test
+    void appNameFallsBackWhenNotExported() {
+        assertThat(MavenRunSupport.resolveExportedAppName(dir, 
"CamelJBang")).isEqualTo("CamelJBang");
+    }
+
+    @Test
+    void noFlightRecordingWithoutJfr() {
+        RunRequest request = new RunRequest();
+
+        assertThat(request.jfrEnabled()).isFalse();
+        assertThat(MavenRunSupport.jfrJvmArgs(request, printer)).isNull();
+    }
+
+    @Test
+    void jfrStartsAFlightRecording() {
+        RunRequest request = new RunRequest();
+        request.jfr = true;
+
+        assertThat(request.jfrEnabled()).isTrue();
+        assertThat(MavenRunSupport.jfrJvmArgs(request, printer))
+                .isEqualTo("-XX:StartFlightRecording=filename=CamelJBang.jfr "
+                           + 
"-Dcamel.main.startupRecorderRuntimeEnabled=true");
+    }
+
+    @Test
+    void aJfrProfileImpliesJfrAndIsTheSettingsOfTheRecording() {
+        RunRequest request = new RunRequest();
+        request.jfrProfile = "profile";
+
+        assertThat(request.jfrEnabled()).isTrue();
+        assertThat(MavenRunSupport.jfrJvmArgs(request, printer))
+                
.isEqualTo("-XX:StartFlightRecording=filename=CamelJBang.jfr,settings=profile "
+                           + 
"-Dcamel.main.startupRecorderRuntimeEnabled=true");
+    }
+
+    @Test
+    void aRecordingInTheJvmArgsWinsAndKeepsTheRuntimeRecorderEnabled() {
+        // the explicit recording wins, but --jfr must still enable Camel 
runtime events in forked projects
+        RunRequest request = new RunRequest();
+        request.jfr = true;
+        request.jvmArgs = "-XX:StartFlightRecording=filename=mine.jfr";
+
+        assertThat(MavenRunSupport.jfrJvmArgs(request, printer))
+                .isEqualTo("-Dcamel.main.startupRecorderRuntimeEnabled=true");
+        assertThat(printer.getOutput()).contains("--jvm-args already starts a 
flight recording");
+    }
+
+    @Test
+    void theRecordingIsNamedAfterTheIntegration() {
+        RunRequest request = new RunRequest();
+        request.name = "my-app";
+        assertThat(request.jfrFileName()).isEqualTo("my-app.jfr");
+
+        request.name = null;
+        assertThat(request.jfrFileName()).isEqualTo("camel.jfr");
+    }
+
+    @Test
+    void mergeJvmArgs() {
+        assertThat(MavenRunSupport.mergeJvmArgs("-Xmx256m", 
null)).isEqualTo("-Xmx256m");
+        assertThat(MavenRunSupport.mergeJvmArgs(null, null)).isNull();
+        assertThat(MavenRunSupport.mergeJvmArgs(null, 
"-XX:StartFlightRecording=filename=camel.jfr"))
+                .isEqualTo("-XX:StartFlightRecording=filename=camel.jfr");
+        assertThat(MavenRunSupport.mergeJvmArgs("  ", 
"-XX:StartFlightRecording=filename=camel.jfr"))
+                .isEqualTo("-XX:StartFlightRecording=filename=camel.jfr");
+        assertThat(MavenRunSupport.mergeJvmArgs("-Xmx256m", 
"-XX:StartFlightRecording=filename=camel.jfr"))
+                .isEqualTo("-Xmx256m 
-XX:StartFlightRecording=filename=camel.jfr");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunMavenRuntimePreviousLogTest.java
 
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunMavenRuntimePreviousLogTest.java
similarity index 96%
rename from 
dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunMavenRuntimePreviousLogTest.java
rename to 
dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunMavenRuntimePreviousLogTest.java
index cfcf2caca57f..9bb539f25136 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunMavenRuntimePreviousLogTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/run/RunMavenRuntimePreviousLogTest.java
@@ -14,16 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.dsl.jbang.core.commands;
+package org.apache.camel.dsl.jbang.export.run;
 
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.FileTime;
 import java.util.List;
 
-import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.export.common.CamelHome;
 import org.apache.camel.dsl.jbang.export.common.PreviousRun;
-import org.apache.camel.dsl.jbang.export.run.MavenRunSupport;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -47,15 +46,15 @@ class RunMavenRuntimePreviousLogTest {
 
     @BeforeEach
     void useTempHome() throws Exception {
-        originalHome = CommandLineHelper.getHomeDir().toString();
-        CommandLineHelper.useHomeDir(home.toString());
-        camelDir = CommandLineHelper.getCamelDir();
+        originalHome = CamelHome.getHomeDir().toString();
+        CamelHome.useHomeDir(home.toString());
+        camelDir = CamelHome.getCamelDir();
         Files.createDirectories(camelDir);
     }
 
     @AfterEach
     void restoreHome() {
-        CommandLineHelper.useHomeDir(originalHome);
+        CamelHome.useHomeDir(originalHome);
     }
 
     private Path log(String name, String content) throws Exception {
@@ -243,7 +242,7 @@ class RunMavenRuntimePreviousLogTest {
     void springBootLogsToTheLogOfItsName() throws Exception {
         Path logback = home.resolve("export/src/main/resources/logback.xml");
 
-        Run.writeSpringBootLogbackConfig(logback, "orders");
+        MavenRunSupport.writeSpringBootLogbackConfig(logback, "orders");
 
         String logFile = 
camelDir.resolve("orders.log").toAbsolutePath().toString().replace("\\", "/");
         assertThat(logback).content().contains("<property name=\"LOG_FILE\" 
value=\"" + logFile + "\"/>")

Reply via email to