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 3adff4a7fd4e CAMEL-23226: Add environment awareness and destructive 
operation confirmation
3adff4a7fd4e is described below

commit 3adff4a7fd4e5a5175c92702abcb50a1aa693e8d
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Mar 23 11:23:54 2026 +0100

    CAMEL-23226: Add environment awareness and destructive operation 
confirmation
    
    - Add EnvironmentHelper utility: detects NO_COLOR, CI, FORCE_COLOR, and 
interactive terminal
    - Auto-detect --logging-color default based on environment (NO_COLOR, CI, 
FORCE_COLOR, TTY)
    - Add confirmation prompt for destructive export --clean-dir operations
    - Add --yes/-y flag to skip confirmation prompts
    - Fix Scanner not closing System.in (avoid try-with-resources on System.in 
wrapper)
---
 .../dsl/jbang/core/commands/CommandHelper.java     | 34 ++++++++
 .../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, 235 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..460ae9520cd1 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,38 @@ 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 {
+            // Do not use try-with-resources here: closing the Scanner would 
close System.in
+            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 ffc0742a097e..a45d4b79b088 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 917dafa75ef1..3c8a946ccb99 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());
+        }
+    }
+}

Reply via email to