Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ TIP: You can also use `camel --help` or `camel <command> --help` to see availabl
| xref:jbang-commands/camel-jbang-dependency.adoc[camel dependency] | Displays all Camel dependencies required to run
| xref:jbang-commands/camel-jbang-dirty.adoc[camel dirty] | Check if there are dirty files from previous Camel runs that did not terminate gracefully
| xref:jbang-commands/camel-jbang-doc.adoc[camel doc] | Shows documentation for kamelet, component, and other Camel resources
| xref:jbang-commands/camel-jbang-doctor.adoc[camel doctor] | Checks the environment and reports potential issues
| xref:jbang-commands/camel-jbang-eval.adoc[camel eval] | Evaluate Camel expressions and scripts
| xref:jbang-commands/camel-jbang-explain.adoc[camel explain] | Explain what a Camel route does using AI/LLM
| xref:jbang-commands/camel-jbang-export.adoc[camel export] | Export to other runtimes (Camel Main, Spring Boot, or Quarkus)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ camel debug [options]
| `--dep,--dependency` | Add additional dependencies | | List
| `--download` | Whether to allow automatic downloading JAR dependencies (over the internet) | true | boolean
| `--empty` | Run an empty Camel without loading source files | false | boolean
| `--example` | Run a built-in example by name (e.g., timer-log, rest-api). Use --example --list to show available examples. | | String
| `--example-list` | List available built-in examples | | boolean
| `--exclude` | Exclude files by name or pattern | | List
| `--fresh` | Make sure we use fresh (i.e. non-cached) resources | false | boolean
| `--gav` | The Maven group:artifact:version (used during exporting) | | String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
= camel doctor

Checks the environment and reports potential issues


== Usage

[source,bash]
----
camel doctor [options]
----



== Options

[cols="2,5,1,2",options="header"]
|===
| Option | Description | Default | Type
| `-h,--help` | Display the help and sub-commands | | boolean
|===


Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ camel run [options]
| `--dep,--dependency` | Add additional dependencies | | List
| `--download` | Whether to allow automatic downloading JAR dependencies (over the internet) | true | boolean
| `--empty` | Run an empty Camel without loading source files | false | boolean
| `--example` | Run a built-in example by name (e.g., timer-log, rest-api). Use --example --list to show available examples. | | String
| `--example-list` | List available built-in examples | | boolean
| `--exclude` | Exclude files by name or pattern | | List
| `--fresh` | Make sure we use fresh (i.e. non-cached) resources | false | boolean
| `--gav` | The Maven group:artifact:version (used during exporting) | | String
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public void execute(String... args) {
.addSubcommand("runtime", new CommandLine(new DependencyRuntime(this)))
.addSubcommand("update", new CommandLine(new DependencyUpdate(this))))
.addSubcommand("dirty", new CommandLine(new Dirty(this)))
.addSubcommand("doctor", new CommandLine(new Doctor(this)))
.addSubcommand("eval", new CommandLine(new EvalCommand(this))
.addSubcommand("expression", new CommandLine(new EvalExpressionCommand(this))))
.addSubcommand("export", new CommandLine(new Export(this)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* 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.io.File;
import java.net.ServerSocket;
import java.util.List;
import java.util.Set;

import org.apache.camel.catalog.CamelCatalog;
import org.apache.camel.catalog.DefaultCamelCatalog;
import org.apache.camel.dsl.jbang.core.common.VersionHelper;
import org.apache.camel.tooling.maven.MavenDownloaderImpl;
import org.apache.camel.tooling.maven.MavenResolutionException;
import picocli.CommandLine.Command;

@Command(name = "doctor", description = "Checks the environment and reports potential issues",
sortOptions = false, showDefaultValues = true)
public class Doctor extends CamelCommand {

public Doctor(CamelJBangMain main) {
super(main);
}

@Override
public Integer doCall() throws Exception {
printer().println("Camel JBang Doctor");
printer().println("==================");
printer().println();

checkJava();
checkJBang();
checkCamelVersion();
checkMavenRepository();
checkContainerRuntime();
checkCommonPorts();
checkDiskSpace();

return 0;
}

private void checkJava() {
String version = System.getProperty("java.version");
String vendor = System.getProperty("java.vendor", "");
int major = Runtime.version().feature();
String status = major >= 21 ? "OK" : "WARN (21+ required)";
printer().printf(" Java: %s (%s) [%s]%n", version, vendor, status);
}

private void checkJBang() {
String version = VersionHelper.getJBangVersion();
if (version != null) {
printer().printf(" JBang: %s (OK)%n", version);
} else {
printer().printf(" JBang: not detected%n");
}
}

private void checkCamelVersion() {
CamelCatalog catalog = new DefaultCamelCatalog();
String version = catalog.getCatalogVersion();
printer().printf(" Camel: %s%n", version);
}

private void checkMavenRepository() {
MavenDownloaderImpl downloader = new MavenDownloaderImpl();
try {
downloader.build();
CamelCatalog catalog = new DefaultCamelCatalog();
String version = catalog.getCatalogVersion();
downloader.resolveArtifacts(
List.of("org.apache.camel:camel-api:" + version),
Set.of(), false, false);
printer().printf(" Maven: artifact resolution OK%n");
} catch (MavenResolutionException e) {
printer().printf(" Maven: artifact resolution failed (%s)%n", e.getMessage());
} catch (Exception e) {
printer().printf(" Maven: error (%s)%n", e.getMessage());
}
}

private void checkContainerRuntime() {
// check docker first, then podman as fallback
for (String cmd : new String[] { "docker", "podman" }) {
try {
Process p = new ProcessBuilder(cmd, "info")
.redirectErrorStream(true)
.start();
// drain output to prevent blocking
p.getInputStream().transferTo(java.io.OutputStream.nullOutputStream());
int exit = p.waitFor();
if (exit == 0) {
printer().printf(" Container: %s running (OK, optional)%n", cmd);
return;
}
} catch (Exception e) {
// not found, try next
}
}
printer().printf(" Container: not found (optional — needed for test containers)%n");
}

private void checkCommonPorts() {
StringBuilder conflicts = new StringBuilder();
for (int port : new int[] { 8080, 8443, 9090 }) {
if (isPortInUse(port)) {
if (!conflicts.isEmpty()) {
conflicts.append(", ");
}
conflicts.append(port);
}
}
if (!conflicts.isEmpty()) {
printer().printf(" Ports: in use: %s%n", conflicts);
} else {
printer().printf(" Ports: 8080, 8443, 9090 free (OK)%n");
}
}

private static boolean isPortInUse(int port) {
try (ServerSocket ss = new ServerSocket(port)) {
ss.setReuseAddress(true);
return false;
} catch (Exception e) {
return true;
}
}

private void checkDiskSpace() {
File tmpDir = new File(System.getProperty("java.io.tmpdir"));
long free = tmpDir.getFreeSpace();
long mb = free / (1024 * 1024);
String status = mb > 500 ? "OK" : "LOW";
printer().printf(" Disk space: %d MB free in temp (%s)%n", mb, status);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;
import java.util.StringJoiner;

Expand Down Expand Up @@ -54,7 +57,8 @@
" camel init hello.java",
" camel init hello.yaml",
" camel init hello.xml",
" camel init --list" })
" camel init --list",
"%nTip: For AI-assisted project scaffolding, try: camel plugin add kit" })
public class Init extends CamelCommand {

@Parameters(description = "Name of integration file (or a github link)", arity = "0..1",
Expand Down Expand Up @@ -101,7 +105,12 @@ public Integer doCall() throws Exception {
return listTemplates();
}
if (file == null) {
// try interactive picker if running in a TTY and not in CI
if (System.console() != null && System.getenv("CI") == null) {
return interactivePicker();
}
printer().printErr("Missing required parameter: <file>");
printer().printErr("Run 'camel init --list' to see available templates, or run interactively in a terminal.");
return 1;
}
int code = execute();
Expand Down Expand Up @@ -285,6 +294,104 @@ private int listTemplates() {
return 0;
}

private int interactivePicker() throws Exception {
// Build template categories
Map<String, List<String[]>> categories = new LinkedHashMap<>();
categories.put("Routes", List.of(
new String[] { "yaml", "YAML DSL route", ".yaml" },
new String[] { "java", "Java DSL route", ".java" },
new String[] { "xml", "XML DSL route", ".xml" }));
categories.put("Kamelets", List.of(
new String[] { "kamelet-source.yaml", "Kamelet source connector", ".kamelet.yaml" },
new String[] { "kamelet-sink.yaml", "Kamelet sink connector", ".kamelet.yaml" },
new String[] { "kamelet-action.yaml", "Kamelet action processor", ".kamelet.yaml" }));
List<String[]> pipeTemplates = new ArrayList<>();
pipeTemplates.add(new String[] { "init-pipe.yaml", "Pipe CR (source to sink)", ".yaml" });
categories.put("Pipes and CRs", pipeTemplates);

Scanner scanner = new Scanner(System.in);

// Step 1: Pick a category
printer().println("Select a template category:");
List<String> categoryNames = new ArrayList<>(categories.keySet());
for (int i = 0; i < categoryNames.size(); i++) {
printer().printf(" %d) %s%n", i + 1, categoryNames.get(i));
}
printer().print("Choice [1]: ");
String categoryInput = scanner.nextLine().trim();
int categoryIdx;
try {
categoryIdx = categoryInput.isEmpty() ? 0 : Integer.parseInt(categoryInput) - 1;
} catch (NumberFormatException e) {
printer().printErr("Invalid choice: " + categoryInput);
return 1;
}
if (categoryIdx < 0 || categoryIdx >= categoryNames.size()) {
printer().printErr("Invalid choice: must be between 1 and " + categoryNames.size());
return 1;
}

// Step 2: Pick a template
String selectedCategory = categoryNames.get(categoryIdx);
List<String[]> templates = categories.get(selectedCategory);
printer().println();
printer().println("Select a template:");
for (int i = 0; i < templates.size(); i++) {
printer().printf(" %d) %s%n", i + 1, templates.get(i)[1]);
}
printer().print("Choice [1]: ");
String templateInput = scanner.nextLine().trim();
int templateIdx;
try {
templateIdx = templateInput.isEmpty() ? 0 : Integer.parseInt(templateInput) - 1;
} catch (NumberFormatException e) {
printer().printErr("Invalid choice: " + templateInput);
return 1;
}
if (templateIdx < 0 || templateIdx >= templates.size()) {
printer().printErr("Invalid choice: must be between 1 and " + templates.size());
return 1;
}

String[] selected = templates.get(templateIdx);
String ext = selected[2];
String defaultName = "MyRoute" + ext;
if (ext.endsWith(".kamelet.yaml")) {
if (selected[0].contains("source")) {
defaultName = "my-source.kamelet.yaml";
} else if (selected[0].contains("sink")) {
defaultName = "my-sink.kamelet.yaml";
} else {
defaultName = "my-action.kamelet.yaml";
}
} else if (selected[0].contains("pipe")) {
defaultName = "my-pipe.yaml";
pipe = true;
}

// Step 3: Prompt for filename
printer().println();
printer().printf("Filename [%s]: ", defaultName);
String filename = scanner.nextLine().trim();
if (filename.isEmpty()) {
filename = defaultName;
}

this.file = filename;
int code = execute();
if (code == 0) {
createWorkingDirectoryIfAbsent();
printer().println();
printer().println("Created: " + filename);
printer().println();
printer().println("Next steps:");
printer().println(" Run: camel run " + filename);
printer().println(" Run (live): camel run " + filename + " --dev");
printer().println(" Documentation: camel doc <component>");
}
return code;
}

private void createWorkingDirectoryIfAbsent() {
Path work = CommandLineHelper.getWorkDir();
if (!Files.exists(work)) {
Expand Down
Loading
Loading