You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by gn...@apache.org on 2026/03/21 18:43:57 UTC
(camel) 03/11: CAMEL-23226: Add init --list and export --dry-run to camel-jbang
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch CAMEL-23226-all-jbang-ux
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 3bcc71323b19d5edc57b803c4a70a64b1fcd1629
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Sat Mar 21 13:37:08 2026 +0100
CAMEL-23226: Add init --list and export --dry-run to camel-jbang
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../camel/dsl/jbang/core/commands/Export.java | 1 +
.../dsl/jbang/core/commands/ExportBaseCommand.java | 72 ++++++++++++++++++++++
.../apache/camel/dsl/jbang/core/commands/Init.java | 57 ++++++++++++++++-
.../camel/dsl/jbang/core/commands/InitTest.java | 34 ++++++++++
4 files changed, 163 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 f4f8f45f7338..4551cab8c722 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;
@@ -259,6 +260,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;
@@ -304,10 +309,77 @@ 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 export directory
+ String originalExportDir = this.exportDir;
+ 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();
+
+ 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 {
+ // 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);
+ }
}