This is an automated email from the ASF dual-hosted git repository.
gnodet 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 61c84da8fcce CAMEL-23226: Add init --list and export --dry-run to
camel-jbang
61c84da8fcce is described below
commit 61c84da8fcced831502a262626fd823c648a51f5
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Mar 23 11:20:12 2026 +0100
CAMEL-23226: Add init --list and export --dry-run to camel-jbang
- Add `camel init --list` to show available templates grouped by category
- Add `camel export --dry-run` to preview export without writing files
- Shows file listing with human-readable sizes from a temp directory
- Properly restores state (exportDir, quiet, cleanExportDir, dryRun) in
finally block
---
.../camel/dsl/jbang/core/commands/Export.java | 1 +
.../dsl/jbang/core/commands/ExportBaseCommand.java | 82 ++++++++++++++++++++++
.../apache/camel/dsl/jbang/core/commands/Init.java | 57 ++++++++++++++-
.../camel/dsl/jbang/core/commands/InitTest.java | 34 +++++++++
4 files changed, 173 insertions(+), 1 deletion(-)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
index 3f3c7064ea5d..02e77c2e5b6b 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
@@ -256,6 +256,7 @@ public class Export extends ExportBaseCommand {
cmd.groovyPrecompiled = this.groovyPrecompiled;
cmd.hawtio = this.hawtio;
cmd.hawtioVersion = this.hawtioVersion;
+ cmd.dryRun = this.dryRun;
// run export
return cmd.export();
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
index ac866cf3ddc4..ffc0742a097e 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
@@ -27,6 +27,7 @@ import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -266,6 +267,10 @@ public abstract class ExportBaseCommand extends
CamelCommand {
description = "Verbose output of startup activity
(dependency resolution and downloading")
protected boolean verbose;
+ @CommandLine.Option(names = { "--dry-run" }, defaultValue = "false",
+ description = "Preview export without writing files")
+ protected boolean dryRun;
+
@CommandLine.Option(names = { "--ignore-loading-error" }, defaultValue =
"false",
description = "Whether to ignore route loading and
compilation errors (use this with care!)")
protected boolean ignoreLoadingError;
@@ -311,10 +316,87 @@ public abstract class ExportBaseCommand extends
CamelCommand {
if (!quiet) {
printConfigurationValues("Exporting integration with the following
configuration:");
}
+
+ if (dryRun) {
+ return doDryRunExport();
+ }
+
// export
return export();
}
+ /**
+ * Performs a dry-run export: runs the export to a temporary directory,
lists the files that would be generated, and
+ * cleans up.
+ */
+ private Integer doDryRunExport() throws Exception {
+ // Save original state
+ String originalExportDir = this.exportDir;
+ boolean originalCleanExportDir = this.cleanExportDir;
+ boolean originalQuiet = this.quiet;
+ Path tempDir = Files.createTempDirectory("camel-export-dry-run-");
+ try {
+ // Redirect export to temp directory
+ this.exportDir = tempDir.toString();
+ this.cleanExportDir = false;
+ this.dryRun = false; // avoid recursion in subclasses
+ this.quiet = true; // suppress normal output
+
+ Integer result = export();
+
+ // Restore quiet so output works correctly
+ this.quiet = originalQuiet;
+
+ printer().println("Dry-run export preview:");
+ printer().println();
+ printer().println("Target directory: " + (originalExportDir !=
null ? originalExportDir : "."));
+ printer().println();
+ printer().println("Files that would be created:");
+ try (Stream<Path> walk = Files.walk(tempDir)) {
+ walk.filter(Files::isRegularFile)
+ .sorted()
+ .forEach(p -> {
+ Path rel = tempDir.relativize(p);
+ try {
+ long size = Files.size(p);
+ printer().printf(" %s (%s)%n", rel,
humanReadableSize(size));
+ } catch (IOException e) {
+ printer().printf(" %s%n", rel);
+ }
+ });
+ }
+
+ return result;
+ } finally {
+ // Restore original state
+ this.exportDir = originalExportDir;
+ this.cleanExportDir = originalCleanExportDir;
+ this.quiet = originalQuiet;
+ this.dryRun = true;
+ // Clean up temp directory
+ try (Stream<Path> walk = Files.walk(tempDir)) {
+ walk.sorted(Comparator.reverseOrder())
+ .forEach(p -> {
+ try {
+ Files.deleteIfExists(p);
+ } catch (IOException e) {
+ // ignore
+ }
+ });
+ }
+ }
+ }
+
+ private static String humanReadableSize(long bytes) {
+ if (bytes < 1024) {
+ return bytes + " bytes";
+ } else if (bytes < 1024 * 1024) {
+ return String.format("%.1f KB", bytes / 1024.0);
+ } else {
+ return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
+ }
+ }
+
protected static String mavenRepositoriesAsPomXml(String repos) {
StringBuilder sb = new StringBuilder();
int i = 1;
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
index 4100a11e70a6..1a0f967b8bff 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
@@ -22,6 +22,8 @@ import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.Stack;
import java.util.StringJoiner;
@@ -49,11 +51,14 @@ import static
org.apache.camel.dsl.jbang.core.common.GitHubHelper.fetchGithubUrl
sortOptions = false, showDefaultValues = true)
public class Init extends CamelCommand {
- @Parameters(description = "Name of integration file (or a github link)",
arity = "1",
+ @Parameters(description = "Name of integration file (or a github link)",
arity = "0..1",
paramLabel = "<file>", parameterConsumer = FileConsumer.class)
private Path filePath; // Defined only for file path completion; the field
never used
private String file;
+ @Option(names = { "--list" }, description = "List available templates")
+ private boolean list;
+
@Option(names = {
"--dir",
"--directory" }, description = "Directory relative path where the
new Camel integration will be saved",
@@ -86,6 +91,13 @@ public class Init extends CamelCommand {
@Override
public Integer doCall() throws Exception {
+ if (list) {
+ return listTemplates();
+ }
+ if (file == null) {
+ printer().printErr("Missing required parameter: <file>");
+ return 1;
+ }
int code = execute();
if (code == 0) {
// In case of successful execution, we create the working
directory if it does not exist to help the tooling
@@ -216,6 +228,49 @@ public class Init extends CamelCommand {
return packageDeclaration;
}
+ private int listTemplates() {
+ // Templates grouped by category with descriptions
+ // Only include user-facing templates (not POM/Dockerfile/internal
templates)
+ Map<String, Map<String, String>> categories = new LinkedHashMap<>();
+
+ Map<String, String> routes = new LinkedHashMap<>();
+ routes.put("java", "Java DSL route (MyRoute.java)");
+ routes.put("xml", "XML DSL route (my-route.xml)");
+ routes.put("yaml", "YAML DSL route (my-route.yaml)");
+ categories.put("Routes", routes);
+
+ Map<String, String> kamelets = new LinkedHashMap<>();
+ kamelets.put("kamelet-source.yaml", "Kamelet source connector
(my-source.kamelet.yaml)");
+ kamelets.put("kamelet-sink.yaml", "Kamelet sink connector
(my-sink.kamelet.yaml)");
+ kamelets.put("kamelet-action.yaml", "Kamelet action processor
(my-action.kamelet.yaml)");
+ categories.put("Kamelets", kamelets);
+
+ Map<String, String> pipes = new LinkedHashMap<>();
+ pipes.put("init-pipe.yaml", "Pipe CR connecting source and sink
(my-pipe.yaml --pipe)");
+ pipes.put("pipe.yaml", "Pipe resource (my-pipe.pipe.yaml)");
+ pipes.put("integration.yaml", "Integration CR
(my-integration.integration.yaml)");
+ categories.put("Pipes and CRs", pipes);
+
+ Map<String, String> restDsl = new LinkedHashMap<>();
+ restDsl.put("rest-dsl.yaml", "REST DSL with OpenAPI
(my-api.rest-dsl.yaml)");
+ categories.put("REST", restDsl);
+
+ printer().println("Available templates for 'camel init':");
+ printer().println();
+ for (Map.Entry<String, Map<String, String>> category :
categories.entrySet()) {
+ printer().println(category.getKey() + ":");
+ for (Map.Entry<String, String> template :
category.getValue().entrySet()) {
+ printer().printf(" %-25s %s%n", template.getKey(),
template.getValue());
+ }
+ printer().println();
+ }
+ printer().println("Usage: camel init <filename>.<ext>");
+ printer().println("The template is selected based on the file
extension.");
+ printer().println("Example: camel init MyRoute.java");
+
+ return 0;
+ }
+
private void createWorkingDirectoryIfAbsent() {
Path work = CommandLineHelper.getWorkDir();
if (!Files.exists(work)) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
index aed11b75f115..50b4ff4ca86c 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
@@ -23,12 +23,14 @@ import java.nio.file.Paths;
import java.util.List;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import picocli.CommandLine;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class InitTest {
@@ -122,4 +124,36 @@ class InitTest {
assertEquals("import org.apache.camel.builder.RouteBuilder;",
lines.get(0));
Files.delete(f);
}
+
+ @Test
+ void initListTemplates() throws Exception {
+ StringPrinter printer = new StringPrinter();
+ Init initCommand = new Init(new CamelJBangMain().withPrinter(printer));
+ CommandLine.populateCommand(initCommand, "--list");
+
+ int exit = initCommand.doCall();
+
+ assertEquals(0, exit);
+ String output = printer.getOutput();
+ assertTrue(output.contains("Available templates"), "Should contain
header");
+ assertTrue(output.contains("Routes:"), "Should contain Routes
category");
+ assertTrue(output.contains("Kamelets:"), "Should contain Kamelets
category");
+ assertTrue(output.contains("java"), "Should list java template");
+ assertTrue(output.contains("yaml"), "Should list yaml template");
+ assertTrue(output.contains("xml"), "Should list xml template");
+ // Should not list internal templates
+ assertFalse(output.contains("Dockerfile"), "Should not list Dockerfile
templates");
+ assertFalse(output.contains("pom"), "Should not list POM templates");
+ }
+
+ @Test
+ void initWithoutFileAndWithoutList() throws Exception {
+ StringPrinter printer = new StringPrinter();
+ Init initCommand = new Init(new CamelJBangMain().withPrinter(printer));
+ CommandLine.populateCommand(initCommand);
+
+ int exit = initCommand.doCall();
+
+ assertEquals(1, exit);
+ }
}