This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch CAMEL-23226-env-awareness in repository https://gitbox.apache.org/repos/asf/camel.git
commit fef35cdc38daff75e9f0d4f1d5f6629edd44e699 Author: Guillaume Nodet <[email protected]> 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 <[email protected]> --- .../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 3f3c7064ea5d..890bbe778cb8 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 f4f8f45f7338..5c6ec4b08876 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 @@ -219,6 +219,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", description = "Logging level") protected String loggingLevel = "info"; 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 1b5d3a19157e..2cc5825421e0 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; @@ -254,8 +255,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()); + } + } +}
