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());
+ }
+ }
+}