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 <[email protected]> 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 <[email protected]> --- .../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); + } }
