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 bbcc09964562 CAMEL-22544: jbang dependency update supports multi-file
and --scan-routes (#22208)
bbcc09964562 is described below
commit bbcc09964562838d1526bea5b45f3b65d1fd131f
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri May 8 17:01:05 2026 +0200
CAMEL-22544: jbang dependency update supports multi-file and --scan-routes
(#22208)
* CAMEL-22544: jbang dependency update supports multi-file and --scan-routes
- Accept multiple target files (pom.xml or Java source files with //DEPS)
- Add --scan-routes flag to sync dependencies from route definitions:
- Manages only org.apache.camel dependencies
- Preserves non-Camel dependencies
- Removes unused Camel dependencies
- Idempotent on re-execution
- Route files (YAML, XML) passed as arguments are used as source files
for the export pipeline dependency resolution
- In scan-routes mode, Camel //DEPS in Java target files are stripped
before export to prevent stale deps from being resolved
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* CAMEL-22544: Address review feedback
- Merge DependencyUpdateJBangTest into DependencyUpdateTest (single test
class)
- Use fixture files from src/test/resources for scan-routes Maven tests
instead of calling Init+Export at runtime
- Add input validation preventing mixed pom.xml and Java targets
- Guard addMavenDeps against targetLineNumber == -1
- Clean up blank lines left by removeMavenDeps
- Clarify isCamelDependency javadoc on group prefix matching
- Fix PrepareCamelJBangCommandsMojo regex to match 'this' (not just 'main')
as constructor arg, fixing stale metadata generation
- Regenerate all jbang command metadata and docs
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* CAMEL-22544: Regenerate jbang command metadata after rebase
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* CAMEL-22544: Fix docs gulp race with test temp directories
Add strict: false to gulp.src glob options so that ENOENT errors
during directory traversal are warnings rather than failures. This
prevents race conditions when the docs gulp build runs in parallel
with tests that create and delete temporary directories.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* CAMEL-22544: Fix docs gulp race with test temp directories
Use system temp directory instead of target/ for test working dirs
and exclude .camel-jbang directories from gulpfile source glob
pattern to avoid race condition with docs gulp build.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* CAMEL-22544: Regenerate jbang metadata and fix formatting
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
docs/gulpfile.js | 2 +-
.../camel-jbang-dependency-update.adoc | 3 +-
.../jbang-commands/camel-jbang-dependency.adoc | 2 +-
.../META-INF/camel-jbang-commands-metadata.json | 2 +-
.../dsl/jbang/core/commands/DependencyUpdate.java | 501 +++++++++++++++++----
.../jbang/core/commands/DependencyUpdateTest.java | 282 +++++++++++-
.../test/resources/dependency-update/main-pom.xml | 62 +++
.../resources/dependency-update/quarkus-pom.xml | 68 +++
.../test/resources/dependency-update/route.yaml | 26 ++
.../resources/dependency-update/springboot-pom.xml | 58 +++
.../packaging/PrepareCamelJBangCommandsMojo.java | 7 +-
11 files changed, 906 insertions(+), 107 deletions(-)
diff --git a/docs/gulpfile.js b/docs/gulpfile.js
index e73414e8b842..0b70d4c3bf06 100644
--- a/docs/gulpfile.js
+++ b/docs/gulpfile.js
@@ -208,7 +208,7 @@ const sources = {
'../components/{*,*/*}/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc',
'../dsl/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc',
'../dsl/*/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc',
-
'../dsl/*/*/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc',
+
'../dsl/*/!(target|.camel-jbang*)/src/main/docs/!(*-component|*-language|*-dataformat|*-summary).adoc',
],
destination: 'components/modules/others/pages',
keep: [
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-update.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-update.adoc
index 24880ad6c54d..beb8a4f4d367 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-update.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-update.adoc
@@ -2,7 +2,7 @@
// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
= camel dependency update
-Updates dependencies in Maven pom.xml or Java source file (JBang style)
+Updates dependencies in Maven pom.xml or Java source files (JBang style)
== Usage
@@ -64,6 +64,7 @@ camel dependency update [options]
| `--quiet` | Will be quiet, only print when error occurs | false | boolean
| `--repo,--repos` | Additional maven repositories (Use commas to separate
multiple repositories) | | String
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | | RuntimeType
+| `--scan-routes` | Sync dependencies from route definitions. Only manages
org.apache.camel dependencies, preserving non-Camel dependencies. Removes
unused Camel dependencies. | | boolean
| `--skip-plugins` | Skip plugins during export | false | boolean
| `--spring-boot-version` | Spring Boot version |
RuntimeType.SPRING_BOOT_VERSION | String
| `--verbose` | Verbose output of startup activity (dependency resolution and
downloading | false | boolean
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency.adoc
index 63e3f40f6cd9..33ab125c3fa8 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency.adoc
@@ -21,7 +21,7 @@ camel dependency [options]
| xref:jbang-commands/camel-jbang-dependency-copy.adoc[copy] | Copies all
Camel dependencies required to run to a specific directory
| xref:jbang-commands/camel-jbang-dependency-list.adoc[list] | Displays all
Camel dependencies required to run
| xref:jbang-commands/camel-jbang-dependency-runtime.adoc[runtime] | Display
Camel runtime and version for given Maven project
-| xref:jbang-commands/camel-jbang-dependency-update.adoc[update] | Updates
dependencies in Maven pom.xml or Java source file (JBang style)
+| xref:jbang-commands/camel-jbang-dependency-update.adoc[update] | Updates
dependencies in Maven pom.xml or Java source files (JBang style)
|===
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 94f962fd266c..a4040d23eb5e 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -6,7 +6,7 @@
{ "name": "completion", "fullName": "completion", "description": "Generate
completion script for bash\/zsh", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
{ "name": "config", "fullName": "config", "description": "Get and set user
configuration values", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get",
"fullName": "config get", "description": "Display user configuration value",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
{ "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To [...]
- { "name": "dependency", "fullName": "dependency", "description": "Displays
all Camel dependencies required to run", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"copy", "fullName": "dependency copy", "description": "Copies all Camel
dependencies required to run to a specific directory", "sourc [...]
+ { "name": "dependency", "fullName": "dependency", "description": "Displays
all Camel dependencies required to run", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"copy", "fullName": "dependency copy", "description": "Copies all Camel
dependencies required to run to a specific directory", "sourc [...]
{ "name": "dirty", "fullName": "dirty", "description": "Check if there are
dirty files from previous Camel runs that did not terminate gracefully",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty",
"options": [ { "names": "--clean", "description": "Clean dirty files which are
no longer in use", "defaultValue": "false", "javaType": "boolean", "type":
"boolean" }, { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", " [...]
{ "name": "doc", "fullName": "doc", "description": "Shows documentation
for kamelet, component, and other Camel resources", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ {
"names": "--camel-version", "description": "To use a different Camel version
than the default version", "javaType": "java.lang.String", "type": "string" },
{ "names": "--download", "description": "Whether to allow automatic downloading
JAR dependencies (over the internet [...]
{ "name": "eval", "fullName": "eval", "description": "Evaluate Camel
expressions and scripts", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression",
"fullName": "eval expression", "description": "Evaluates Camel expression",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdate.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdate.java
index 5c96cc3fbc5d..6e08c30c4ee4 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdate.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdate.java
@@ -20,12 +20,13 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.StringJoiner;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.apache.camel.dsl.jbang.core.common.RuntimeType;
@@ -34,25 +35,33 @@ import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.StringHelper;
import org.apache.camel.util.xml.XmlLineNumberParser;
-import org.apache.logging.log4j.util.Strings;
import picocli.CommandLine;
@CommandLine.Command(name = "update",
- description = "Updates dependencies in Maven pom.xml or
Java source file (JBang style)",
+ description = "Updates dependencies in Maven pom.xml or
Java source files (JBang style)",
sortOptions = false,
showDefaultValues = true)
public class DependencyUpdate extends DependencyList {
- @CommandLine.Parameters(description = "Maven pom.xml or Java source files
(JBang Style with //DEPS) to have dependencies updated",
- arity = "1")
- public Path file;
+ @CommandLine.Parameters(description = "Maven pom.xml or Java source files
(JBang Style with //DEPS) to have dependencies updated."
+ + " Route definition files (YAML,
XML) can also be included and will be used as source files"
+ + " for dependency resolution.",
+ arity = "1..*")
+ public List<Path> targetFiles;
@CommandLine.Option(names = { "--clean" },
description = "Regenerate list of dependencies (do not
keep existing dependencies). Not supported for pom.xml")
protected boolean clean;
+ @CommandLine.Option(names = { "--scan-routes" },
+ description = "Sync dependencies from route
definitions. Only manages org.apache.camel dependencies,"
+ + " preserving non-Camel dependencies.
Removes unused Camel dependencies.")
+ protected boolean scanRoutes;
+
private final List<String> deps = new ArrayList<>();
private final List<MavenGav> gavs = new ArrayList<>();
+ // actual target files to update (pom.xml or Java files with //DEPS)
+ private final List<Path> updateTargets = new ArrayList<>();
public DependencyUpdate(CamelJBangMain main) {
super(main);
@@ -60,22 +69,65 @@ public class DependencyUpdate extends DependencyList {
@Override
public Integer doCall() throws Exception {
- // source file must exist
- if (!Files.exists(file)) {
- printer().printErr("Source file does not exist: " + file);
+ // clear state from any previous invocation
+ deps.clear();
+ gavs.clear();
+ updateTargets.clear();
+
+ if (clean && scanRoutes) {
+ printer().printErr("Options --clean and --scan-routes are mutually
exclusive");
+ return -1;
+ }
+
+ for (Path file : targetFiles) {
+ if (!Files.exists(file)) {
+ printer().printErr("Source file does not exist: " + file);
+ return -1;
+ }
+ String name = file.getFileName().toString();
+ String ext = FileUtil.onlyExt(name, true);
+ if ("pom.xml".equals(name) || "java".equals(ext)) {
+ updateTargets.add(file);
+ }
+ }
+
+ if (scanRoutes) {
+ // in scan-routes mode, strip existing //DEPS from Java target
files before the export pipeline
+ // runs, so only actual route-based dependencies are resolved (not
stale //DEPS)
+ for (Path file : updateTargets) {
+ String ext = FileUtil.onlyExt(file.getFileName().toString(),
true);
+ if ("java".equals(ext)) {
+ stripJBangDeps(file);
+ }
+ }
+ }
+
+ if (updateTargets.isEmpty()) {
+ printer().printErr("No target files (pom.xml or Java source files)
specified");
+ return -1;
+ }
+
+ // validate that all update targets are of the same type (all Maven or
all JBang)
+ boolean hasMaven = updateTargets.stream().anyMatch(f ->
"pom.xml".equals(f.getFileName().toString()));
+ boolean hasJava = updateTargets.stream().anyMatch(f ->
!"pom.xml".equals(f.getFileName().toString()));
+ if (hasMaven && hasJava) {
+ printer().printErr("Cannot mix pom.xml and Java source files as
update targets");
return -1;
}
- boolean maven = "pom.xml".equals(file.getFileName().toString());
+ Path firstTarget = updateTargets.get(0);
+ boolean maven = hasMaven;
if (clean && !maven) {
- // remove DEPS in source file first
- updateJBangSource();
+ // in clean mode: remove all DEPS first
+ for (Path file : updateTargets) {
+ updateJBangSource(file);
+ }
}
if (maven && this.runtime == null) {
// Basic heuristic to determine if the project is a Quarkus or
Spring Boot one.
- String pomContent = new String(Files.readAllBytes(file));
+ String pomContent = new String(Files.readAllBytes(firstTarget));
if (pomContent.contains("quarkus")) {
runtime = RuntimeType.quarkus;
} else if (pomContent.contains("spring-boot")) {
@@ -93,7 +145,8 @@ public class DependencyUpdate extends DependencyList {
@Override
protected void outputGav(MavenGav gav, int index, int total) {
try {
- boolean maven = "pom.xml".equals(file.getFileName().toString());
+ Path firstTarget = updateTargets.get(0);
+ boolean maven =
"pom.xml".equals(firstTarget.getFileName().toString());
if (maven) {
outputGavMaven(gav, index, total);
} else {
@@ -109,7 +162,9 @@ public class DependencyUpdate extends DependencyList {
boolean last = total - index <= 1;
if (last) {
- updateMavenSource();
+ for (Path file : updateTargets) {
+ updateMavenSource(file);
+ }
}
}
@@ -127,11 +182,17 @@ public class DependencyUpdate extends DependencyList {
}
boolean last = total - index <= 1;
if (last) {
- updateJBangSource();
+ for (Path file : updateTargets) {
+ if (scanRoutes) {
+ syncJBangSource(file);
+ } else {
+ updateJBangSource(file);
+ }
+ }
}
}
- private void updateJBangSource() {
+ private void updateJBangSource(Path file) {
try {
List<String> lines = Files.readAllLines(file);
List<String> answer = new ArrayList<>();
@@ -155,7 +216,7 @@ public class DependencyUpdate extends DependencyList {
}
// add after shebang in top
if (pos == -1) {
- if (answer.get(0).trim().startsWith("///usr/bin/env jbang")) {
+ if (!answer.isEmpty() &&
answer.get(0).trim().startsWith("///usr/bin/env jbang")) {
pos = 1;
}
}
@@ -164,13 +225,80 @@ public class DependencyUpdate extends DependencyList {
}
// reverse collection as we insert pos based
- Collections.reverse(deps);
+ List<String> depsToInsert = new ArrayList<>(deps);
+ Collections.reverse(depsToInsert);
+ for (String dep : depsToInsert) {
+ answer.add(pos, dep);
+ }
+
+ String text = String.join(System.lineSeparator(), answer);
+ Files.writeString(file, text);
+ } catch (Exception e) {
+ printer().printErr("Error updating source file: " + file + " due
to: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Syncs JBang source file dependencies: preserves non-Camel //DEPS,
replaces Camel //DEPS with the resolved set.
+ */
+ private void syncJBangSource(Path file) {
+ try {
+ List<String> lines = Files.readAllLines(file);
+ List<String> answer = new ArrayList<>();
+
+ // collect non-Camel DEPS and find insertion position
+ List<String> nonCamelDeps = new ArrayList<>();
+ int pos = -1;
+ for (int i = 0; i < lines.size(); i++) {
+ String o = lines.get(i);
+ // remove leading comment chars to inspect
+ String l = o.trim();
+ while (l.startsWith("#")) {
+ l = l.substring(1);
+ }
+ if (l.startsWith("//DEPS ")) {
+ if (pos == -1) {
+ pos = i;
+ }
+ // check if this is a Camel dependency
+ String depPart = l.substring("//DEPS ".length()).trim();
+ if (!isCamelDependency(depPart)) {
+ nonCamelDeps.add(o);
+ }
+ } else {
+ answer.add(o);
+ }
+ }
+ // add after shebang in top
+ if (pos == -1) {
+ if (!answer.isEmpty() &&
answer.get(0).trim().startsWith("///usr/bin/env jbang")) {
+ pos = 1;
+ }
+ }
+ if (pos == -1) {
+ pos = 0;
+ }
+
+ // build combined deps: Camel deps (from resolved) + non-Camel
deps (preserved)
+ Set<String> seen = new LinkedHashSet<>();
+ List<String> allDeps = new ArrayList<>();
+
+ // add resolved Camel deps first
for (String dep : deps) {
- // is this XML or YAML
- String ext = FileUtil.onlyExt(file.getFileName().toString(),
true);
- if ("yaml".equals(ext)) {
- dep = "#" + dep;
+ if (seen.add(dep)) {
+ allDeps.add(dep);
+ }
+ }
+ // add preserved non-Camel deps
+ for (String dep : nonCamelDeps) {
+ if (seen.add(dep)) {
+ allDeps.add(dep);
}
+ }
+
+ // reverse collection as we insert pos based
+ Collections.reverse(allDeps);
+ for (String dep : allDeps) {
answer.add(pos, dep);
}
@@ -181,16 +309,58 @@ public class DependencyUpdate extends DependencyList {
}
}
- private void updateMavenSource() throws Exception {
+ /**
+ * Strips Camel //DEPS lines from a JBang-style source file (in-place),
preserving non-Camel //DEPS. This is used in
+ * scan-routes mode to prevent stale Camel //DEPS from being picked up by
the export pipeline.
+ */
+ private void stripJBangDeps(Path file) {
+ try {
+ List<String> lines = Files.readAllLines(file);
+ List<String> answer = new ArrayList<>();
+ for (String line : lines) {
+ String l = line.trim();
+ while (l.startsWith("#")) {
+ l = l.substring(1);
+ }
+ if (l.startsWith("//DEPS ")) {
+ String depPart = l.substring("//DEPS ".length()).trim();
+ if (isCamelDependency(depPart)) {
+ // skip Camel deps — they will be re-added from route
resolution
+ continue;
+ }
+ }
+ answer.add(line);
+ }
+ String text = String.join(System.lineSeparator(), answer);
+ Files.writeString(file, text);
+ } catch (Exception e) {
+ printer().printErr("Error stripping //DEPS from: " + file + " due
to: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Check if a JBang dependency GAV string is a Camel dependency. Uses
exact prefix "org.apache.camel:" (with colon)
+ * because JBang files only use the org.apache.camel group (not
springboot/quarkus variants). For Maven pom.xml, see
+ * syncMavenSource which uses startsWith("org.apache.camel") without colon
to also match org.apache.camel.springboot
+ * and org.apache.camel.quarkus groups.
+ */
+ private static boolean isCamelDependency(String dep) {
+ // handle @pom suffix
+ String d = dep;
+ if (d.endsWith("@pom")) {
+ d = d.substring(0, d.length() - 4);
+ }
+ return d.startsWith("org.apache.camel:");
+ }
+
+ private void updateMavenSource(Path file) throws Exception {
List<MavenGav> existingGavs = new ArrayList<>();
- Node camelClone = null;
int targetLineNumber = -1;
- Path pom = file;
- if (Files.exists(pom)) {
+ if (Files.exists(file)) {
// use line number parser as we want to find where to add new
Camel JARs after the existing Camel JARs
- Document dom =
XmlLineNumberParser.parseXml(Files.newInputStream(pom));
+ Document dom =
XmlLineNumberParser.parseXml(Files.newInputStream(file));
String camelVersion = null;
NodeList nl = dom.getElementsByTagName("dependency");
for (int i = 0; i < nl.getLength(); i++) {
@@ -222,17 +392,10 @@ public class DependencyUpdate extends DependencyList {
if ("org.apache.camel".equals(g)) {
camelVersion = v;
- if (camelClone == null && !"camel-bom".equals(a)) {
- camelClone = node.cloneNode(true);
- }
String num = (String)
node.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
if (num != null) {
targetLineNumber = Integer.parseInt(num);
}
- num = (String)
node.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
- if (num != null) {
- targetLineNumber = Integer.parseInt(num);
- }
}
if ("org.apache.camel.springboot".equals(g)) {
camelVersion = v;
@@ -270,62 +433,236 @@ public class DependencyUpdate extends DependencyList {
}
// sort the new JARs being added
updates.sort(mavenGavComparator());
- List<MavenGav> toBeUpdated = new ArrayList<>();
- int changes = 0;
- for (MavenGav update : updates) {
- if (!existingGavs.contains(update)) {
- toBeUpdated.add(update);
- changes++;
+
+ if (scanRoutes) {
+ // in scan-routes mode, sync Camel deps: add missing, remove
unused
+ syncMavenSource(file, dom, existingGavs, updates);
+ } else {
+ // default mode: only add new deps
+ addMavenDeps(file, existingGavs, updates, targetLineNumber);
+ }
+ } else {
+ outPrinter().println("pom.xml not found " + file.toAbsolutePath());
+ }
+ }
+
+ private void addMavenDeps(Path file, List<MavenGav> existingGavs,
List<MavenGav> updates, int targetLineNumber)
+ throws Exception {
+ List<MavenGav> toBeUpdated = new ArrayList<>();
+ for (MavenGav update : updates) {
+ if (!existingGavs.contains(update)) {
+ toBeUpdated.add(update);
+ }
+ }
+
+ if (!toBeUpdated.isEmpty() && targetLineNumber > 0) {
+ String content = IOHelper.loadText(Files.newInputStream(file));
+ String[] lines = content.split("\n");
+ content = insertMavenDeps(lines, toBeUpdated, targetLineNumber);
+ Files.writeString(file, content);
+ int changes = toBeUpdated.size();
+ if (changes > 1) {
+ outPrinter().println("Updating pom.xml with " + changes + "
dependencies added");
+ } else {
+ outPrinter().println("Updating pom.xml with 1 dependency
added");
+ }
+ } else {
+ outPrinter().println("No updates to pom.xml");
+ }
+ }
+
+ private void syncMavenSource(
+ Path file, Document dom, List<MavenGav> existingGavs,
List<MavenGav> resolvedGavs)
+ throws Exception {
+
+ // determine which existing Camel deps are no longer needed
+ Set<String> resolvedGAs = new LinkedHashSet<>();
+ for (MavenGav gav : resolvedGavs) {
+ resolvedGAs.add(gav.getGroupId() + ":" + gav.getArtifactId());
+ }
+ Set<String> existingGAs = new LinkedHashSet<>();
+ for (MavenGav gav : existingGavs) {
+ existingGAs.add(gav.getGroupId() + ":" + gav.getArtifactId());
+ }
+
+ // find Camel deps to remove (exist in pom but not in resolved set)
+ List<String> toRemove = new ArrayList<>();
+ for (MavenGav gav : existingGavs) {
+ String ga = gav.getGroupId() + ":" + gav.getArtifactId();
+ boolean isCamel = gav.getGroupId().startsWith("org.apache.camel");
+ if (isCamel && !resolvedGAs.contains(ga)) {
+ // skip BOM entries
+ if (!"camel-bom".equals(gav.getArtifactId())
+ &&
!"camel-spring-boot-bom".equals(gav.getArtifactId())) {
+ toRemove.add(ga);
}
}
+ }
- if (changes > 0) {
- // respect indent from existing GAVs
- String line =
IOHelper.loadTextLine(Files.newInputStream(file), targetLineNumber);
- line = StringHelper.before(line, "<");
- int indent = StringHelper.countChar(line, ' ');
- String pad = Strings.repeat(" ", indent);
- line = IOHelper.loadTextLine(Files.newInputStream(file),
targetLineNumber - 1);
- line = StringHelper.before(line, "<");
- int indent2 = StringHelper.countChar(line, ' ');
- String pad2 = Strings.repeat(" ", indent2);
-
- // build GAVs to be added to pom.xml
- StringJoiner sj = new StringJoiner("");
- for (MavenGav gav : toBeUpdated) {
- sj.add("\n").add(pad).add("<dependency>\n");
- sj.add(pad2).add("<groupId>" + gav.getGroupId() +
"</groupId>\n");
- sj.add(pad2).add("<artifactId>" + gav.getArtifactId() +
"</artifactId>\n");
- if (gav.getVersion() != null) {
- sj.add(pad2).add("<version>" + gav.getVersion() +
"</version>\n");
- }
- if (gav.getScope() != null) {
- sj.add(pad2).add("<scope>" + gav.getScope() +
"</scope>\n");
- }
- sj.add(pad).add("</dependency>");
+ // find Camel deps to add (in resolved but not in pom)
+ List<MavenGav> toAdd = new ArrayList<>();
+ for (MavenGav gav : resolvedGavs) {
+ String ga = gav.getGroupId() + ":" + gav.getArtifactId();
+ if (!existingGAs.contains(ga)) {
+ toAdd.add(gav);
+ }
+ }
+
+ int added = toAdd.size();
+ int removed = toRemove.size();
+
+ if (added == 0 && removed == 0) {
+ outPrinter().println("No updates to pom.xml");
+ return;
+ }
+
+ // process file: remove unused Camel deps and add new ones
+ String content = IOHelper.loadText(Files.newInputStream(file));
+ String[] lines = content.split("\n");
+
+ if (removed > 0) {
+ content = removeMavenDeps(lines, dom, toRemove);
+ }
+
+ if (added > 0) {
+ // re-parse to get updated line numbers after removals
+ if (removed > 0) {
+ lines = content.split("\n");
+ Document updatedDom = XmlLineNumberParser.parseXml(
+ new
java.io.ByteArrayInputStream(content.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
+ int insertLine = findLastCamelDependencyLine(updatedDom);
+ if (insertLine > 0) {
+ content = insertMavenDeps(lines, toAdd, insertLine);
+ }
+ } else {
+ int insertLine = findLastCamelDependencyLine(dom);
+ if (insertLine > 0) {
+ content = insertMavenDeps(lines, toAdd, insertLine);
}
+ }
+ }
- StringJoiner out = new StringJoiner("\n");
- String[] lines =
IOHelper.loadText(Files.newInputStream(file)).split("\n");
- for (int i = 0; i < lines.length; i++) {
- String txt = lines[i];
- out.add(txt);
- if (i == targetLineNumber - 1) {
- out.add(sj.toString());
- }
+ Files.writeString(file, content);
+
+ StringBuilder msg = new StringBuilder("Updating pom.xml with ");
+ if (added > 0) {
+ msg.append(added).append(added == 1 ? " dependency added" : "
dependencies added");
+ }
+ if (removed > 0) {
+ if (added > 0) {
+ msg.append(" and ");
+ }
+ msg.append(removed).append(removed == 1 ? " dependency removed" :
" dependencies removed");
+ }
+ outPrinter().println(msg.toString());
+ }
+
+ private int findLastCamelDependencyLine(Document dom) {
+ int targetLineNumber = -1;
+ NodeList nl = dom.getElementsByTagName("dependency");
+ for (int i = 0; i < nl.getLength(); i++) {
+ Element node = (Element) nl.item(i);
+ String p = node.getParentNode().getNodeName();
+ String p2 = node.getParentNode().getParentNode().getNodeName();
+ boolean accept = ("dependencyManagement".equals(p2) ||
"project".equals(p2)) && (p.equals("dependencies"));
+ if (!accept) {
+ continue;
+ }
+ String g =
node.getElementsByTagName("groupId").item(0).getTextContent();
+ if (g.startsWith("org.apache.camel")) {
+ String num = (String)
node.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
+ if (num != null) {
+ targetLineNumber = Integer.parseInt(num);
}
- if (changes > 1) {
- outPrinter().println("Updating pom.xml with " + changes +
" dependencies added");
- } else {
- outPrinter().println("Updating pom.xml with 1 dependency
added");
+ }
+ }
+ return targetLineNumber;
+ }
+
+ private String removeMavenDeps(String[] lines, Document dom, List<String>
toRemove) {
+ // find line ranges of dependencies to remove
+ List<int[]> rangesToRemove = new ArrayList<>();
+ NodeList nl = dom.getElementsByTagName("dependency");
+ for (int i = 0; i < nl.getLength(); i++) {
+ Element node = (Element) nl.item(i);
+ String p = node.getParentNode().getNodeName();
+ String p2 = node.getParentNode().getParentNode().getNodeName();
+ boolean accept = ("dependencyManagement".equals(p2) ||
"project".equals(p2)) && (p.equals("dependencies"));
+ if (!accept) {
+ continue;
+ }
+ String g =
node.getElementsByTagName("groupId").item(0).getTextContent();
+ String a =
node.getElementsByTagName("artifactId").item(0).getTextContent();
+ String ga = g + ":" + a;
+ if (toRemove.contains(ga)) {
+ String startNum = (String)
node.getUserData(XmlLineNumberParser.LINE_NUMBER);
+ String endNum = (String)
node.getUserData(XmlLineNumberParser.LINE_NUMBER_END);
+ if (startNum != null && endNum != null) {
+ rangesToRemove.add(new int[] { Integer.parseInt(startNum),
Integer.parseInt(endNum) });
}
- Files.writeString(file, out.toString());
- } else {
- outPrinter().println("No updates to pom.xml");
}
- } else {
- outPrinter().println("pom.xml not found " + pom.toAbsolutePath());
}
+
+ StringBuilder sb = new StringBuilder();
+ boolean previousSkipped = false;
+ for (int i = 0; i < lines.length; i++) {
+ int lineNum = i + 1; // 1-based
+ boolean skip = false;
+ for (int[] range : rangesToRemove) {
+ if (lineNum >= range[0] && lineNum <= range[1]) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ // skip blank line immediately following a removed block to
avoid double-blank-lines
+ if (previousSkipped && lines[i].trim().isEmpty()) {
+ previousSkipped = false;
+ continue;
+ }
+ if (sb.length() > 0) {
+ sb.append("\n");
+ }
+ sb.append(lines[i]);
+ }
+ previousSkipped = skip;
+ }
+ return sb.toString();
+ }
+
+ private String insertMavenDeps(String[] lines, List<MavenGav> toAdd, int
targetLineNumber) {
+ // respect indent from existing lines
+ String line = lines[targetLineNumber - 1];
+ String beforeTag = StringHelper.before(line, "<");
+ int indent = beforeTag != null ? StringHelper.countChar(beforeTag, '
') : 8;
+ String pad = " ".repeat(indent);
+ String line2 = targetLineNumber >= 2 ? lines[targetLineNumber - 2] :
line;
+ String beforeTag2 = StringHelper.before(line2, "<");
+ int indent2 = beforeTag2 != null ? StringHelper.countChar(beforeTag2,
' ') : indent + 4;
+ String pad2 = " ".repeat(indent2);
+
+ StringJoiner sj = new StringJoiner("");
+ for (MavenGav gav : toAdd) {
+ sj.add("\n").add(pad).add("<dependency>\n");
+ sj.add(pad2).add("<groupId>" + gav.getGroupId() + "</groupId>\n");
+ sj.add(pad2).add("<artifactId>" + gav.getArtifactId() +
"</artifactId>\n");
+ if (gav.getVersion() != null) {
+ sj.add(pad2).add("<version>" + gav.getVersion() +
"</version>\n");
+ }
+ if (gav.getScope() != null) {
+ sj.add(pad2).add("<scope>" + gav.getScope() + "</scope>\n");
+ }
+ sj.add(pad).add("</dependency>");
+ }
+
+ StringJoiner out = new StringJoiner("\n");
+ for (int i = 0; i < lines.length; i++) {
+ out.add(lines[i]);
+ if (i == targetLineNumber - 1) {
+ out.add(sj.toString());
+ }
+ }
+ return out.toString();
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdateTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdateTest.java
index 4475d190267b..f1ce75e02ea5 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdateTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/DependencyUpdateTest.java
@@ -17,10 +17,10 @@
package org.apache.camel.dsl.jbang.core.commands;
import java.io.File;
-import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.stream.Stream;
@@ -30,6 +30,7 @@ import org.apache.camel.util.FileUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -45,8 +46,7 @@ class DependencyUpdateTest extends
CamelCommandBaseTestSupport {
@Override
public void setup() throws Exception {
super.setup();
- Path base = Paths.get("target");
- workingDir = Files.createTempDirectory(base,
"camel-dependency-update-tests").toFile();
+ workingDir =
Files.createTempDirectory("camel-dependency-update-tests").toFile();
}
@AfterEach
@@ -55,6 +55,8 @@ class DependencyUpdateTest extends
CamelCommandBaseTestSupport {
FileUtil.removeDir(workingDir);
}
+ // ==================== Maven dependency update (existing export-based
tests) ====================
+
@ParameterizedTest
@MethodSource("runtimeProvider")
void shouldDependencyUpdate(RuntimeType rt) throws Exception {
@@ -64,7 +66,7 @@ class DependencyUpdateTest extends
CamelCommandBaseTestSupport {
checkOneDependencyAddedForArangoDb(rt);
}
- private void checkOneDependencyAddedForArangoDb(RuntimeType rt) throws
Exception, IOException {
+ private void checkOneDependencyAddedForArangoDb(RuntimeType rt) throws
Exception {
StringPrinter secondUpdateCommandPrinter = new StringPrinter();
DependencyUpdate command = new DependencyUpdate(new
CamelJBangMain().withPrinter(secondUpdateCommandPrinter));
CommandLine.populateCommand(command,
@@ -94,18 +96,6 @@ class DependencyUpdateTest extends
CamelCommandBaseTestSupport {
}
}
- private void addArangodbToCamelFile() throws IOException {
- File camelFile = new File(workingDir,
"src/main/resources/camel/my.camel.yaml");
- String camelFileContent = new
String(Files.readAllBytes(camelFile.toPath()));
- camelFileContent = camelFileContent.replace("- log: ${body}", """
- - to:
- uri: arangodb
- parameters:
- database: demo
- """);
- Files.writeString(camelFile.toPath(), camelFileContent);
- }
-
private void checkNoUpdateOnFreshlyGeneratedproject() throws Exception {
DependencyUpdate command = new DependencyUpdate(new
CamelJBangMain().withPrinter(printer));
CommandLine.populateCommand(command,
@@ -137,6 +127,264 @@ class DependencyUpdateTest extends
CamelCommandBaseTestSupport {
Assertions.assertEquals(0, exportCommand.doCall(),
exportCommandPrinter.getLines().toString());
}
+ // ==================== scan-routes with Maven (fixture-based tests)
====================
+
+ @ParameterizedTest
+ @MethodSource("runtimeProvider")
+ void shouldScanRoutesAddDependency(RuntimeType rt) throws Exception {
+ prepareFixtureProject(rt);
+
+ // add arangodb to the route
+ addArangodbToCamelFile();
+
+ // run with --scan-routes
+ StringPrinter updatePrinter = new StringPrinter();
+ DependencyUpdate command = new DependencyUpdate(new
CamelJBangMain().withPrinter(updatePrinter));
+ CommandLine.populateCommand(command,
+ "--scan-routes",
+ "--dir=" + workingDir,
+ new File(workingDir, "pom.xml").getAbsolutePath());
+ int exit = command.doCall();
+ Assertions.assertEquals(0, exit, updatePrinter.getLines().toString());
+
+ String pomContent = Files.readString(new File(workingDir,
"pom.xml").toPath());
+ switch (rt) {
+ case quarkus:
+ assertThat(pomContent).contains("camel-quarkus-arangodb");
+ break;
+ case springBoot:
+ assertThat(pomContent).contains("camel-arangodb-starter");
+ break;
+ case main:
+ assertThat(pomContent).contains("camel-arangodb<");
+ break;
+ }
+ }
+
+ @Test
+ void shouldScanRoutesRemoveUnusedMavenDependency() throws Exception {
+ prepareFixtureProject(RuntimeType.main);
+
+ // manually add camel-kafka to the pom.xml (which is not used by the
route)
+ File pomFile = new File(workingDir, "pom.xml");
+ String pomContent = Files.readString(pomFile.toPath());
+ // insert before the closing </dependencies> tag
+ String kafkaDep = " <dependency>\n"
+ + " <groupId>org.apache.camel</groupId>\n"
+ + "
<artifactId>camel-kafka</artifactId>\n"
+ + " </dependency>\n ";
+ pomContent = pomContent.replaceFirst("(</dependencies>)", kafkaDep +
"$1");
+ Files.writeString(pomFile.toPath(), pomContent);
+
+ // verify kafka is there
+ assertThat(Files.readString(pomFile.toPath())).contains("camel-kafka");
+
+ // run with --scan-routes to sync
+ StringPrinter updatePrinter = new StringPrinter();
+ DependencyUpdate command = new DependencyUpdate(new
CamelJBangMain().withPrinter(updatePrinter));
+ CommandLine.populateCommand(command,
+ "--scan-routes",
+ "--dir=" + workingDir,
+ pomFile.getAbsolutePath());
+ int exit = command.doCall();
+ Assertions.assertEquals(0, exit, updatePrinter.getLines().toString());
+
+ // camel-kafka should be removed (not used in routes)
+ String updatedPom = Files.readString(pomFile.toPath());
+ assertThat(updatedPom).doesNotContain("camel-kafka");
+ // should still have camel-yaml-dsl (used for the route file)
+ assertThat(updatedPom).contains("camel-yaml-dsl");
+ }
+
+ @Test
+ void shouldScanRoutesAddAndRemoveMavenDependency() throws Exception {
+ prepareFixtureProject(RuntimeType.main);
+
+ // manually add camel-kafka to the pom.xml (which is not used by the
route)
+ File pomFile = new File(workingDir, "pom.xml");
+ String pomContent = Files.readString(pomFile.toPath());
+ String kafkaDep = " <dependency>\n"
+ + " <groupId>org.apache.camel</groupId>\n"
+ + "
<artifactId>camel-kafka</artifactId>\n"
+ + " </dependency>\n ";
+ pomContent = pomContent.replaceFirst("(</dependencies>)", kafkaDep +
"$1");
+ Files.writeString(pomFile.toPath(), pomContent);
+
+ // add arangodb to the route
+ addArangodbToCamelFile();
+
+ // run with --scan-routes to sync
+ StringPrinter updatePrinter = new StringPrinter();
+ DependencyUpdate command = new DependencyUpdate(new
CamelJBangMain().withPrinter(updatePrinter));
+ CommandLine.populateCommand(command,
+ "--scan-routes",
+ "--dir=" + workingDir,
+ pomFile.getAbsolutePath());
+ int exit = command.doCall();
+ Assertions.assertEquals(0, exit, updatePrinter.getLines().toString());
+
+ String updatedPom = Files.readString(pomFile.toPath());
+ // camel-kafka should be removed (not used in routes)
+ assertThat(updatedPom).doesNotContain("camel-kafka");
+ // camel-arangodb should be added (used in the route)
+ assertThat(updatedPom).contains("camel-arangodb<");
+ // should still have camel-yaml-dsl (used for the route file)
+ assertThat(updatedPom).contains("camel-yaml-dsl");
+ }
+
+ // ==================== JBang file tests (inline fixtures)
====================
+
+ @Test
+ void shouldPreserveNonCamelDepsInJBangFile() throws Exception {
+ // create a Java file with mixed Camel and non-Camel //DEPS
+ Path javaFile = createFile("MyRoute.java", """
+ ///usr/bin/env jbang
+ //DEPS org.apache.camel:camel-bom:4.13.0@pom
+ //DEPS org.apache.camel:camel-kafka
+ //DEPS com.google.guava:guava:33.0.0-jre
+ //DEPS io.netty:netty-all:4.1.100.Final
+ import org.apache.camel.builder.RouteBuilder;
+ public class MyRoute extends RouteBuilder {
+ public void configure() {
+ from("timer:tick").to("log:info");
+ }
+ }
+ """);
+
+ // run scan-routes with just the Java file (no separate route files)
+ StringPrinter p = new StringPrinter();
+ DependencyUpdate command = new DependencyUpdate(new
CamelJBangMain().withPrinter(p));
+ CommandLine.populateCommand(command,
+ "--scan-routes",
+ "--dir=" + workingDir,
+ javaFile.toAbsolutePath().toString());
+ int exit = command.doCall();
+ Assertions.assertEquals(0, exit, p.getLines().toString());
+
+ String content = Files.readString(javaFile);
+ // non-Camel deps should be preserved
+ assertThat(content).contains("//DEPS
com.google.guava:guava:33.0.0-jre");
+ assertThat(content).contains("//DEPS
io.netty:netty-all:4.1.100.Final");
+ // should have camel-bom (resolved fresh)
+ assertThat(content).contains("//DEPS org.apache.camel:camel-bom:");
+ // kafka should be removed (not used in the route definition)
+ assertThat(content).doesNotContain("camel-kafka");
+ }
+
+ @Test
+ void shouldScanRoutesIdempotentJBang() throws Exception {
+ Path javaFile = createFile("MyRoute.java", """
+ ///usr/bin/env jbang
+ //DEPS com.google.guava:guava:33.0.0-jre
+ import org.apache.camel.builder.RouteBuilder;
+ public class MyRoute extends RouteBuilder {
+ public void configure() {
+ from("timer:tick").to("log:info");
+ }
+ }
+ """);
+
+ // run scan-routes twice
+ for (int run = 0; run < 2; run++) {
+ StringPrinter p = new StringPrinter();
+ DependencyUpdate command = new DependencyUpdate(new
CamelJBangMain().withPrinter(p));
+ CommandLine.populateCommand(command,
+ "--scan-routes",
+ "--dir=" + workingDir,
+ javaFile.toAbsolutePath().toString());
+ int exit = command.doCall();
+ Assertions.assertEquals(0, exit, p.getLines().toString());
+ }
+
+ String content = Files.readString(javaFile);
+ // should have exactly one camel-bom line
+ long bomCount = content.lines().filter(l ->
l.contains("camel-bom")).count();
+ assertThat(bomCount).isEqualTo(1);
+ // non-Camel deps should still be there
+ assertThat(content).contains("//DEPS
com.google.guava:guava:33.0.0-jre");
+ }
+
+ @Test
+ void shouldUpdateMultipleJBangFiles() throws Exception {
+ Path java1 = createFile("MyRoute1.java", """
+ ///usr/bin/env jbang
+ import org.apache.camel.builder.RouteBuilder;
+ public class MyRoute1 extends RouteBuilder {
+ public void configure() {
+ from("timer:tick").to("log:info");
+ }
+ }
+ """);
+ Path java2 = createFile("MyRoute2.java", """
+ ///usr/bin/env jbang
+ import org.apache.camel.builder.RouteBuilder;
+ public class MyRoute2 extends RouteBuilder {
+ public void configure() {
+ from("timer:tick").to("log:info");
+ }
+ }
+ """);
+
+ DependencyUpdate command = new DependencyUpdate(new
CamelJBangMain().withPrinter(printer));
+ CommandLine.populateCommand(command,
+ "--dir=" + workingDir,
+ java1.toAbsolutePath().toString(),
+ java2.toAbsolutePath().toString());
+
+ int exit = command.doCall();
+ Assertions.assertEquals(0, exit, printer.getLines().toString());
+
+ // both files should have //DEPS
+ String content1 = Files.readString(java1);
+ String content2 = Files.readString(java2);
+ assertThat(content1).contains("//DEPS org.apache.camel:camel-bom:");
+ assertThat(content2).contains("//DEPS org.apache.camel:camel-bom:");
+ }
+
+ // ==================== Helpers ====================
+
+ private void addArangodbToCamelFile() throws Exception {
+ File camelFile = new File(workingDir,
"src/main/resources/camel/my.camel.yaml");
+ String content = Files.readString(camelFile.toPath());
+ content = content.replace("- log: ${body}", """
+ - to:
+ uri: arangodb
+ parameters:
+ database: demo
+ """);
+ Files.writeString(camelFile.toPath(), content);
+ }
+
+ /**
+ * Prepares a project from fixture files in
src/test/resources/dependency-update. Faster than Init+Export since no
+ * command invocation is needed.
+ */
+ private void prepareFixtureProject(RuntimeType rt) throws Exception {
+ String pomResource = switch (rt) {
+ case quarkus -> "dependency-update/quarkus-pom.xml";
+ case springBoot -> "dependency-update/springboot-pom.xml";
+ case main -> "dependency-update/main-pom.xml";
+ };
+
+ // copy pom.xml
+ try (InputStream in =
getClass().getClassLoader().getResourceAsStream(pomResource)) {
+ Files.copy(in, new File(workingDir, "pom.xml").toPath(),
StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ // copy route file
+ Path routeDir = new File(workingDir,
"src/main/resources/camel").toPath();
+ Files.createDirectories(routeDir);
+ try (InputStream in =
getClass().getClassLoader().getResourceAsStream("dependency-update/route.yaml"))
{
+ Files.copy(in, routeDir.resolve("my.camel.yaml"),
StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ private Path createFile(String name, String content) throws Exception {
+ Path file = workingDir.toPath().resolve(name);
+ Files.writeString(file, content);
+ return file;
+ }
+
private static Stream<Arguments> runtimeProvider() {
Stream.Builder<Arguments> builder = Stream.builder();
builder.add(Arguments.of(RuntimeType.quarkus));
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/main-pom.xml
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/main-pom.xml
new file mode 100644
index 000000000000..a8fa3566579b
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/main-pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>examples</groupId>
+ <artifactId>route</artifactId>
+ <version>1.0.0</version>
+ <packaging>jar</packaging>
+
+ <properties>
+ <maven.compiler.release>21</maven.compiler.release>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-bom</artifactId>
+ <version>4.13.0</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-main</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-yaml-dsl</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/quarkus-pom.xml
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/quarkus-pom.xml
new file mode 100644
index 000000000000..ff304cc4d770
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/quarkus-pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>examples</groupId>
+ <artifactId>route</artifactId>
+ <version>1.0.0</version>
+ <packaging>jar</packaging>
+
+ <properties>
+ <maven.compiler.release>21</maven.compiler.release>
+
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
+
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+ <quarkus.platform.version>3.23.0</quarkus.platform.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>${quarkus.platform.group-id}</groupId>
+ <artifactId>${quarkus.platform.artifact-id}</artifactId>
+ <version>${quarkus.platform.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>${quarkus.platform.group-id}</groupId>
+ <artifactId>quarkus-camel-bom</artifactId>
+ <version>${quarkus.platform.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-yaml-dsl</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/route.yaml
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/route.yaml
new file mode 100644
index 000000000000..df0f91ed45f8
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/route.yaml
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+- route:
+ from:
+ uri: timer:yaml
+ parameters:
+ period: "1000"
+ steps:
+ - setBody:
+ simple: Hello Camel from my
+ - log: ${body}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/springboot-pom.xml
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/springboot-pom.xml
new file mode 100644
index 000000000000..98ab74b3ef7b
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/dependency-update/springboot-pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>examples</groupId>
+ <artifactId>route</artifactId>
+ <version>1.0.0</version>
+ <packaging>jar</packaging>
+
+ <properties>
+ <maven.compiler.release>21</maven.compiler.release>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel.springboot</groupId>
+ <artifactId>camel-spring-boot-bom</artifactId>
+ <version>4.13.0</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel.springboot</groupId>
+ <artifactId>camel-spring-boot-starter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel.springboot</groupId>
+ <artifactId>camel-yaml-dsl-starter</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCamelJBangCommandsMojo.java
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCamelJBangCommandsMojo.java
index d2bc45458120..44d099505283 100644
---
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCamelJBangCommandsMojo.java
+++
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCamelJBangCommandsMojo.java
@@ -57,9 +57,9 @@ import org.jboss.forge.roaster.model.source.JavaSource;
requiresDependencyResolution = ResolutionScope.COMPILE)
public class PrepareCamelJBangCommandsMojo extends AbstractGeneratorMojo {
- // Pattern to match .addSubcommand("name", new CommandLine(new
ClassName(this/main))
+ // Pattern to match .addSubcommand("name", new CommandLine(new
ClassName(main)) or (this))
private static final Pattern SUBCOMMAND_PATTERN = Pattern.compile(
-
"\\.addSubcommand\\(\\s*\"([^\"]+)\"\\s*,\\s*new\\s+CommandLine\\(\\s*new\\s+([A-Za-z0-9_]+)\\s*\\(\\s*(?:this|main)\\s*\\)\\s*\\)");
+
"\\.addSubcommand\\(\\s*\"([^\"]+)\"\\s*,\\s*new\\s+CommandLine\\(\\s*new\\s+([A-Za-z0-9_]+)\\s*\\(\\s*(?:main|this)\\s*\\)\\s*\\)");
@Parameter(defaultValue = "${project.basedir}/src/generated/resources")
protected File outFolder;
@@ -165,8 +165,7 @@ public class PrepareCamelJBangCommandsMojo extends
AbstractGeneratorMojo {
// Find the start of the commandLine builder chain
int startLine = -1;
for (int i = 0; i < lines.size(); i++) {
- if (lines.get(i).contains("commandLine = new CommandLine(this)")
- || lines.get(i).contains("commandLine = new
CommandLine(main)")) {
+ if (lines.get(i).contains("commandLine = new CommandLine(")) {
startLine = i;
break;
}