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:44:00 UTC
(camel) 06/11: CAMEL-23226: Add NO_COLOR, CI environment detection and destructive operation confirmation
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 2019ed455092937ed9bf665a3c6e2ed69176fb62
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Sat Mar 21 13:50:12 2026 +0100
CAMEL-23226: Add NO_COLOR, CI environment detection and destructive operation confirmation
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../dsl/jbang/core/commands/CommandHelper.java | 32 ++++++++
.../camel/dsl/jbang/core/commands/Export.java | 1 +
.../dsl/jbang/core/commands/ExportBaseCommand.java | 4 +
.../dsl/jbang/core/commands/ExportCamelMain.java | 6 ++
.../dsl/jbang/core/commands/ExportQuarkus.java | 6 ++
.../dsl/jbang/core/commands/ExportSpringBoot.java | 6 ++
.../apache/camel/dsl/jbang/core/commands/Run.java | 6 +-
.../dsl/jbang/core/common/EnvironmentHelper.java | 90 ++++++++++++++++++++++
.../jbang/core/common/EnvironmentHelperTest.java | 84 ++++++++++++++++++++
9 files changed, 233 insertions(+), 2 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CommandHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CommandHelper.java
index 11ffe230d224..d755f7774d6e 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CommandHelper.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CommandHelper.java
@@ -20,8 +20,10 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Scanner;
import java.util.stream.Stream;
+import org.apache.camel.dsl.jbang.core.common.EnvironmentHelper;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
import org.apache.camel.dsl.jbang.core.common.Printer;
@@ -68,6 +70,36 @@ public final class CommandHelper {
}
}
+ /**
+ * Prompts the user for confirmation before performing a destructive operation. Returns true if the operation should
+ * proceed, false otherwise.
+ *
+ * <p>
+ * The confirmation is automatically granted (returns true) when:
+ * <ul>
+ * <li>The {@code yes} parameter is true (user passed --yes/-y)</li>
+ * <li>Running in a CI environment</li>
+ * <li>No interactive console is available</li>
+ * </ul>
+ *
+ * @param message the confirmation prompt message to display
+ * @param yes whether the user explicitly confirmed via --yes/-y flag
+ * @return true if the operation should proceed
+ */
+ public static boolean confirmOperation(String message, boolean yes) {
+ if (yes || EnvironmentHelper.isCIEnvironment() || !EnvironmentHelper.isInteractiveTerminal()) {
+ return true;
+ }
+ System.out.print(message + " [y/N] ");
+ System.out.flush();
+ try (Scanner scanner = new Scanner(System.in)) {
+ String answer = scanner.nextLine().trim().toLowerCase();
+ return "y".equals(answer) || "yes".equals(answer);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
/**
* A background task that reads from console, and can be used to signal when user has entered or pressed ctrl + c /
* ctrl + d
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 02e77c2e5b6b..463dcdd9efe3 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
@@ -226,6 +226,7 @@ public class Export extends ExportBaseCommand {
cmd.mavenApacheSnapshotEnabled = this.mavenApacheSnapshotEnabled;
cmd.exportDir = this.exportDir;
cmd.cleanExportDir = this.cleanExportDir;
+ cmd.yes = this.yes;
cmd.fresh = this.fresh;
cmd.download = this.download;
cmd.skipPlugins = this.skipPlugins;
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 9138c4849cd3..a3015f5436a8 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
@@ -225,6 +225,10 @@ public abstract class ExportBaseCommand extends CamelCommand {
description = "If exporting to current directory (default) then all existing files are preserved. Enabling this option will force cleaning current directory including all sub dirs (use this with care)")
protected boolean cleanExportDir;
+ @CommandLine.Option(names = { "--yes", "-y" }, defaultValue = "false",
+ description = "Automatically answer yes to confirmation prompts (e.g. when using --clean-dir)")
+ protected boolean yes;
+
@CommandLine.Option(names = { "--logging-level" }, defaultValue = "info",
completionCandidates = LoggingLevelCompletionCandidates.class,
description = "Logging level (${COMPLETION-CANDIDATES})")
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java
index 9a0a3a3f3ef3..1ac673b32302 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java
@@ -166,6 +166,12 @@ class ExportCamelMain extends Export {
if (cleanExportDir || !exportDir.equals(".")) {
// cleaning current dir can be a bit dangerous so only clean if explicit enabled
// otherwise always clean export-dir to avoid stale data
+ if (cleanExportDir) {
+ String absPath = Path.of(exportDir).toAbsolutePath().toString();
+ if (!CommandHelper.confirmOperation("Are you sure you want to delete " + absPath + "?", yes)) {
+ return 1;
+ }
+ }
CommandHelper.cleanExportDir(exportDir);
}
// copy to export dir and remove work dir
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportQuarkus.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportQuarkus.java
index bd3b4366ec51..959b0af739d2 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportQuarkus.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportQuarkus.java
@@ -143,6 +143,12 @@ class ExportQuarkus extends Export {
if (cleanExportDir || !exportDir.equals(".")) {
// cleaning current dir can be a bit dangerous so only clean if explicit enabled
// otherwise always clean export-dir to avoid stale data
+ if (cleanExportDir) {
+ String absPath = Path.of(exportDir).toAbsolutePath().toString();
+ if (!CommandHelper.confirmOperation("Are you sure you want to delete " + absPath + "?", yes)) {
+ return 1;
+ }
+ }
CommandHelper.cleanExportDir(exportDir);
}
// copy to export dir and remove work dir
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java
index a734f19c56f4..405dc7b48c07 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java
@@ -159,6 +159,12 @@ class ExportSpringBoot extends Export {
if (cleanExportDir || !exportDir.equals(".")) {
// cleaning current dir can be a bit dangerous so only clean if explicit enabled
// otherwise always clean export-dir to avoid stale data
+ if (cleanExportDir) {
+ String absPath = Paths.get(exportDir).toAbsolutePath().toString();
+ if (!CommandHelper.confirmOperation("Are you sure you want to delete " + absPath + "?", yes)) {
+ return 1;
+ }
+ }
CommandHelper.cleanExportDir(exportDir);
}
// copy to export dir and remove work dir
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index b90c1a44a221..d2bed2fbe3d3 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -43,6 +43,7 @@ import java.util.stream.Stream;
import org.apache.camel.catalog.CamelCatalog;
import org.apache.camel.catalog.DefaultCamelCatalog;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.EnvironmentHelper;
import org.apache.camel.dsl.jbang.core.common.LauncherHelper;
import org.apache.camel.dsl.jbang.core.common.LoggingLevelCompletionCandidates;
import org.apache.camel.dsl.jbang.core.common.Plugin;
@@ -2161,8 +2162,9 @@ public class Run extends CamelCommand {
defaultValue = "info", description = "Logging level (${COMPLETION-CANDIDATES})")
String loggingLevel;
- @Option(names = { "--logging-color" }, defaultValue = "true", description = "Use colored logging")
- boolean loggingColor = true;
+ @Option(names = { "--logging-color" },
+ description = "Use colored logging. Default is auto-detected based on NO_COLOR, CI, FORCE_COLOR environment variables and terminal capabilities")
+ boolean loggingColor = EnvironmentHelper.isColorEnabled();
@Option(names = { "--logging-json" }, defaultValue = "false", description = "Use JSON logging (ECS Layout)")
boolean loggingJson;
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/EnvironmentHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/EnvironmentHelper.java
new file mode 100644
index 000000000000..e35385be27c2
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/EnvironmentHelper.java
@@ -0,0 +1,90 @@
+/*
+ * 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.common;
+
+/**
+ * Helper for detecting environment characteristics such as CI environments, color support, and interactive terminals.
+ *
+ * Supports the following standard environment variables:
+ * <ul>
+ * <li>{@code NO_COLOR} - When set (any value), disables colored output. See
+ * <a href="https://no-color.org/">no-color.org</a></li>
+ * <li>{@code FORCE_COLOR} - When set (any value), forces colored output even when not a TTY</li>
+ * <li>{@code CI} - When set (any value), indicates a CI environment (disables color and interactive prompts)</li>
+ * <li>{@code GITHUB_ACTIONS} - GitHub Actions CI detection</li>
+ * <li>{@code GITLAB_CI} - GitLab CI detection</li>
+ * <li>{@code JENKINS_URL} - Jenkins CI detection</li>
+ * </ul>
+ */
+public final class EnvironmentHelper {
+
+ private EnvironmentHelper() {
+ }
+
+ /**
+ * Determines whether colored output should be enabled based on environment variables and terminal capabilities.
+ *
+ * <p>
+ * The precedence order is:
+ * <ol>
+ * <li>{@code NO_COLOR} set - returns false</li>
+ * <li>{@code CI} set (without {@code FORCE_COLOR}) - returns false</li>
+ * <li>{@code FORCE_COLOR} set - returns true</li>
+ * <li>Otherwise, returns true if a console (TTY) is available</li>
+ * </ol>
+ *
+ * @return true if colored output should be enabled
+ */
+ public static boolean isColorEnabled() {
+ if (getEnv("NO_COLOR") != null) {
+ return false;
+ }
+ if (getEnv("CI") != null && getEnv("FORCE_COLOR") == null) {
+ return false;
+ }
+ if (getEnv("FORCE_COLOR") != null) {
+ return true;
+ }
+ return System.console() != null;
+ }
+
+ /**
+ * Detects whether the current process is running in a CI/CD environment.
+ *
+ * @return true if a known CI environment variable is set
+ */
+ public static boolean isCIEnvironment() {
+ return getEnv("CI") != null
+ || getEnv("GITHUB_ACTIONS") != null
+ || getEnv("GITLAB_CI") != null
+ || getEnv("JENKINS_URL") != null;
+ }
+
+ /**
+ * Determines whether the current terminal is interactive (has a console and is not in CI).
+ *
+ * @return true if the terminal supports interactive prompts
+ */
+ public static boolean isInteractiveTerminal() {
+ return System.console() != null && !isCIEnvironment();
+ }
+
+ // Visible for testing - allows overriding in tests
+ static String getEnv(String name) {
+ return System.getenv(name);
+ }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/EnvironmentHelperTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/EnvironmentHelperTest.java
new file mode 100644
index 000000000000..0a5985a4ac1c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/EnvironmentHelperTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.common;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class EnvironmentHelperTest {
+
+ @Test
+ void testIsColorEnabledReturnsBoolean() {
+ // Should not throw and should return a boolean value
+ // The actual result depends on the environment, but the method should work
+ boolean result = EnvironmentHelper.isColorEnabled();
+ assertNotNull(result);
+ }
+
+ @Test
+ void testIsCIEnvironmentReturnsBoolean() {
+ boolean result = EnvironmentHelper.isCIEnvironment();
+ assertNotNull(result);
+ }
+
+ @Test
+ void testIsInteractiveTerminalReturnsBoolean() {
+ boolean result = EnvironmentHelper.isInteractiveTerminal();
+ assertNotNull(result);
+ }
+
+ @Test
+ void testNoColorEnvDisablesColor() {
+ // When NO_COLOR is set in the actual environment, color should be disabled
+ if (System.getenv("NO_COLOR") != null) {
+ assertFalse(EnvironmentHelper.isColorEnabled());
+ }
+ }
+
+ @Test
+ void testCIEnvDetected() {
+ // When CI is set in the actual environment, it should be detected
+ if (System.getenv("CI") != null) {
+ assertTrue(EnvironmentHelper.isCIEnvironment());
+ assertFalse(EnvironmentHelper.isInteractiveTerminal());
+ }
+ }
+
+ @Test
+ void testGitHubActionsDetected() {
+ if (System.getenv("GITHUB_ACTIONS") != null) {
+ assertTrue(EnvironmentHelper.isCIEnvironment());
+ }
+ }
+
+ @Test
+ void testGitLabCIDetected() {
+ if (System.getenv("GITLAB_CI") != null) {
+ assertTrue(EnvironmentHelper.isCIEnvironment());
+ }
+ }
+
+ @Test
+ void testJenkinsCIDetected() {
+ if (System.getenv("JENKINS_URL") != null) {
+ assertTrue(EnvironmentHelper.isCIEnvironment());
+ }
+ }
+}