This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-26931-bad-version
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 0c945b18a946e7782d55b53ad709d2d0d35b350e
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 14 18:47:53 2026 +0100

    CAMEL-26931: camel-jbang - a Camel version that does not exist says so and 
exits with 3
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   9 ++
 .../modules/ROOT/pages/camel-jbang-projects.adoc   |   3 +
 .../jbang-commands/camel-jbang-version-set.adoc    |   2 +-
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../core/commands/catalog/CatalogBaseCommand.java  |  18 ++++
 .../jbang/core/commands/version/VersionSet.java    |  30 +++++-
 .../dsl/jbang/core/common/CamelVersionCheck.java   | 101 +++++++++++++++++++++
 .../core/commands/version/VersionSetTest.java      |  60 ++++++++++--
 .../jbang/core/common/CamelVersionCheckTest.java   |  83 +++++++++++++++++
 .../src/test/resources/golden/cli-help.txt         |   3 +-
 .../commands/validate/CatalogVersionMixin.java     |  16 ++++
 .../camel/dsl/jbang/run/common/CatalogLoader.java  |  18 ++++
 .../run/common/CatalogLoaderNotFoundTest.java      |  86 ++++++++++++++++++
 13 files changed, 418 insertions(+), 13 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 028422a474ff..383cdcf71eab 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -1459,6 +1459,15 @@ with code `1` (fix the route) instead of `70` (internal 
error), and prints the e
 The stack trace is in the log of the integration, and is no longer printed a 
second time on stderr (add `--verbose`
 for it). Scripts that checked for `70` after a failed start should check for 
`1`.
 
+=== camel-jbang - a Camel version that does not exist exits with 3
+
+A `--camel-version` that was never released, in `camel catalog`, `camel doc` 
and `camel validate`, now says
+`Camel version X not found in <repositories>` and exits with code `3` (not 
found) instead of `1` with "Cannot
+resolve dependencies". A Maven repository that cannot be reached still exits 
with `1`. `camel version set` checks
+that the version exists before it stores it: a version that does not exist 
exits with `3` and is not stored, and when
+the repositories cannot be reached the version is stored with a warning. 
`camel version set` without a version,
+`--runtime`, `--repo` or `--reset` is now a usage error (exit code `2`) 
instead of doing nothing.
+
 === camel-resilience4j, camel-microprofile-fault-tolerance - a circuit breaker 
in a doTry
 
 A circuit breaker in the steps of a `doTry` no longer ends that `doTry` for 
the steps after it. Before, when the
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
index 5b4820208a6b..8fbb4e52b56c 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
@@ -450,6 +450,9 @@ camel version set 3.18.2
 camel version set --reset
 ----
 
+`camel version set` checks in the Maven repositories that the version exists, 
and refuses one that does not
+(exit code 3). `--reset` removes the `camel-version`, `kamelets-version`, 
`repos` and `runtime` settings.
+
 IMPORTANT: You cannot combine `camel version set` with `--camel-version`.
 
 For SNAPSHOT versions:
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-version-set.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-version-set.adoc
index c9b5c7fe4759..dfebcca7e399 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-version-set.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-version-set.adoc
@@ -21,7 +21,7 @@ camel version set [options]
 | Option | Description | Default | Type
 | `--global` | Use global or local configuration |  | boolean
 | `--repo,--repos` | Maven repository for downloading the dependencies (Use 
commas to separate multiple repositories) |  | String
-| `--reset` | Reset by removing any custom version settings |  | boolean
+| `--reset` | Reset by removing the version settings: camel-version, 
kamelets-version, repos and runtime |  | boolean
 | `--runtime` | Runtime (camel-main, spring-boot, quarkus) |  | RuntimeType
 | `-h,--help` | Display this help and exit |  | boolean
 |===
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index a41581312714..30ad2d68876e 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -38,7 +38,7 @@
     { "name": "trace", "fullName": "trace", "description": "Tail message 
traces from running Camel integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelTraceAction", "options": 
[ { "names": "--action", "description": "Action to start, stop, clear, list 
status, or dump traces", "defaultValue": "status", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--ago", "description": "Use 
ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "b [...]
     { "name": "transform", "fullName": "transform", "description": "Transform 
message or Camel routes", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.TransformCommand", "options": [ { 
"names": "-h,--help", "description": "Display this help and exit", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "dataweave", 
"fullName": "transform dataweave", "description": "Convert DataWeave scripts to 
DataSonnet format", "sourceClass": "org.apache.camel.dsl.jbang.c [...]
     { "name": "update", "fullName": "update", "description": "Update Camel 
project", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.update.UpdateCommand", "options": [ { 
"names": "-h,--help", "description": "Display this help and exit", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "list", 
"fullName": "update list", "description": "List available update versions for 
Camel and its runtime variants", "sourceClass": 
"org.apache.camel.dsl.jbang.core.comma [...]
-    { "name": "version", "fullName": "version", "description": "Manage Camel 
versions", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.version.VersionCommand", "options": [ 
{ "names": "-h,--help", "description": "Display this help and exit", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "version get", "description": "Displays current Camel version", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.version.VersionGet", 
"optio [...]
+    { "name": "version", "fullName": "version", "description": "Manage Camel 
versions", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.version.VersionCommand", "options": [ 
{ "names": "-h,--help", "description": "Display this help and exit", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "version get", "description": "Displays current Camel version", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.version.VersionGet", 
"optio [...]
     { "name": "wrapper", "fullName": "wrapper", "description": "Install Camel 
wrapper scripts for version pinning", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.WrapperCommand", "options": [ { 
"names": "--camel-version", "description": "Camel version to pin (defaults to 
current version)", "javaType": "java.lang.String", "type": "string" }, { 
"names": "--command-name", "description": "The name of the command to use 
(camel or camelw)", "defaultValue": "camel", "javaType": "java [...]
   ]
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java
index 6c720d18ec5c..8748d1919136 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java
@@ -35,6 +35,8 @@ import 
org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.commands.MavenResolverMixin;
 import org.apache.camel.dsl.jbang.core.commands.QuarkusPlatformMixin;
 import org.apache.camel.dsl.jbang.core.common.CamelTableColumns;
+import org.apache.camel.dsl.jbang.core.common.CamelVersionCheck;
+import org.apache.camel.dsl.jbang.core.common.CliException;
 import org.apache.camel.dsl.jbang.core.common.RowSort;
 import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates;
 import org.apache.camel.dsl.jbang.core.common.RuntimeTypeConverter;
@@ -43,6 +45,7 @@ import org.apache.camel.dsl.jbang.core.model.CatalogBaseDTO;
 import org.apache.camel.dsl.jbang.run.common.CatalogLoader;
 import org.apache.camel.dsl.jbang.run.common.RuntimeType;
 import org.apache.camel.dsl.jbang.run.common.VersionHelper;
+import org.apache.camel.main.download.DownloadException;
 import org.apache.camel.main.util.SuggestSimilarHelper;
 import org.apache.camel.tooling.maven.MavenGav;
 import org.apache.camel.tooling.model.ArtifactModel;
@@ -116,6 +119,21 @@ public abstract class CatalogBaseCommand extends 
CamelCommand {
     static CamelCatalog loadCatalog(
             RuntimeType runtime, String camelVersion, QuarkusPlatformMixin 
quarkusPlatform, MavenResolverMixin mavenResolver)
             throws Exception {
+        try {
+            return doLoadCatalog(runtime, camelVersion, quarkusPlatform, 
mavenResolver);
+        } catch (DownloadException e) {
+            // a version that was never released says so, and is not found 
(CAMEL-26931)
+            CliException notFound = CamelVersionCheck.notFound(e, runtime, 
camelVersion);
+            if (notFound != null) {
+                throw notFound;
+            }
+            throw e;
+        }
+    }
+
+    private static CamelCatalog doLoadCatalog(
+            RuntimeType runtime, String camelVersion, QuarkusPlatformMixin 
quarkusPlatform, MavenResolverMixin mavenResolver)
+            throws Exception {
         if (RuntimeType.springBoot == runtime) {
             return CatalogLoader.loadSpringBootCatalog(mavenResolver.repos(), 
camelVersion, mavenResolver.download());
         } else if (RuntimeType.quarkus == runtime) {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSet.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSet.java
index f006d8ebced9..51785631cc30 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSet.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSet.java
@@ -16,11 +16,14 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.version;
 
+import java.util.function.BiFunction;
+
 import org.aesh.command.CommandDefinition;
 import org.aesh.command.option.Argument;
 import org.aesh.command.option.Option;
 import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CamelVersionCheck;
 import org.apache.camel.dsl.jbang.core.common.CliExitCode;
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
 import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates;
@@ -46,12 +49,16 @@ public class VersionSet extends CamelCommand {
             description = "Maven repository for downloading the dependencies 
(Use commas to separate multiple repositories)")
     String repo;
 
-    @Option(name = "reset", hasValue = false, description = "Reset by removing 
any custom version settings")
+    @Option(name = "reset", hasValue = false,
+            description = "Reset by removing the version settings: 
camel-version, kamelets-version, repos and runtime")
     boolean reset;
 
     @Option(name = "global", hasValue = false, description = "Use global or 
local configuration")
     boolean global = true;
 
+    /** Whether a version exists, given the version and the extra 
repositories: true, false, or null when unknown. */
+    BiFunction<String, String, Boolean> versionCheck = 
CamelVersionCheck::exists;
+
     public VersionSet(CamelJBangMain main) {
         super(main);
     }
@@ -59,6 +66,27 @@ public class VersionSet extends CamelCommand {
     @Override
     public Integer doCall() throws Exception {
         boolean local = !global;
+        if (!reset && version == null && repo == null && runtime == null) {
+            printer().printErr("Give the Camel version to set, --runtime, 
--repo, or --reset");
+            return CliExitCode.USAGE;
+        }
+        if (!reset && version != null) {
+            // a version that was never released fails every command that uses 
it later (CAMEL-26931)
+            String repos = repo;
+            if (repos == null) {
+                String[] stored = new String[1];
+                CommandLineHelper.loadProperties(p -> stored[0] = 
p.getProperty("repos"), local, printer());
+                repos = stored[0];
+            }
+            Boolean exists = versionCheck.apply(version, repos);
+            if (Boolean.FALSE.equals(exists)) {
+                printer().printErr("Camel version " + version + " not found 
(camel version list shows the released versions)");
+                return CliExitCode.NOT_FOUND;
+            } else if (exists == null) {
+                printer().printDiagnostic("Cannot check that Camel version " + 
version
+                                          + " exists: the Maven repositories 
cannot be reached");
+            }
+        }
         CommandLineHelper.createPropertyFile(local);
 
         boolean ok = CommandLineHelper.loadProperties(properties -> {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheck.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheck.java
new file mode 100644
index 000000000000..e3a79ca1746b
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheck.java
@@ -0,0 +1,101 @@
+/*
+ * 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 java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.camel.dsl.jbang.run.common.CatalogLoader;
+import org.apache.camel.dsl.jbang.run.common.RuntimeType;
+import org.apache.camel.main.download.DownloadException;
+import org.apache.camel.main.download.MavenDependencyDownloader;
+import org.apache.camel.tooling.maven.MavenResolutionException;
+
+/**
+ * Tells a Camel version that does not exist from a Maven repository that 
cannot be reached, so a wrong
+ * {@code --camel-version} says so and exits with {@link 
CliExitCode#NOT_FOUND} (CAMEL-26931).
+ */
+public final class CamelVersionCheck {
+
+    private CamelVersionCheck() {
+    }
+
+    /**
+     * The error for a {@code --camel-version} that the Maven repositories say 
does not exist, or null when the
+     * exception is something else. With the Quarkus runtime the version picks 
a platform, which reports its own errors.
+     *
+     * @param e       the exception of loading the catalog of the version
+     * @param runtime the runtime, or null for Camel Main
+     * @param version the version, or null for the version of the CLI
+     */
+    public static CliException notFound(Throwable e, RuntimeType runtime, 
String version) {
+        if (RuntimeType.quarkus == runtime) {
+            return null;
+        }
+        return notFound(e, RuntimeType.springBoot == runtime ? "Camel Spring 
Boot" : "Camel", version);
+    }
+
+    /**
+     * The error for a version that the Maven repositories say does not exist, 
or null when the exception is something
+     * else, such as a repository that cannot be reached.
+     *
+     * @param e       the exception of loading the artifacts of the version
+     * @param what    what the version is of, such as {@code Camel} or {@code 
Camel Spring Boot}
+     * @param version the version
+     */
+    private static CliException notFound(Throwable e, String what, String 
version) {
+        if (version == null || !CatalogLoader.isArtifactNotFound(e)) {
+            return null;
+        }
+        String repos = null;
+        for (Throwable c = e; c != null && repos == null; c = c.getCause()) {
+            if (c instanceof MavenResolutionException mre && 
mre.getRepositories() != null
+                    && !mre.getRepositories().isEmpty()) {
+                repos = String.join(", ", mre.getRepositories());
+            }
+        }
+        return new CliException(
+                CliExitCode.NOT_FOUND,
+                what + " version " + version + " not found" + (repos != null ? 
" in " + repos : "")
+                                       + " (camel version list shows the 
released versions)",
+                e);
+    }
+
+    /**
+     * Whether the Camel version exists: {@code true} or {@code false} as the 
Maven repositories answer, or null when
+     * they cannot be reached.
+     *
+     * @param version the Camel version
+     * @param repos   extra Maven repositories (comma separated), or null
+     */
+    public static Boolean exists(String version, String repos) {
+        try (MavenDependencyDownloader downloader = new 
MavenDependencyDownloader()) {
+            downloader.setRepositories(repos);
+            downloader.setDownload(true);
+            downloader.start();
+            
downloader.resolveDependenciesViaAether(List.of("org.apache.camel:camel-api:pom:"
 + version), Set.of(),
+                    false, version.endsWith("-SNAPSHOT"));
+            return true;
+        } catch (DownloadException e) {
+            return CatalogLoader.isArtifactNotFound(e) ? false : null;
+        } catch (IOException e) {
+            // closing the downloader; the answer is known already
+            return true;
+        }
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSetTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSetTest.java
index 5977c7e094bd..d32df6cf2591 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSetTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSetTest.java
@@ -44,7 +44,7 @@ class VersionSetTest extends BaseConfigTestSupport {
     void shouldStoreVersionInGlobalConfig() throws Exception {
         UserConfigHelper.createUserConfig("");
 
-        VersionSet command = new VersionSet(new 
CamelJBangMain().withPrinter(printer));
+        VersionSet command = command();
         command.version = "4.10.0";
         command.doCall();
 
@@ -60,7 +60,7 @@ class VersionSetTest extends BaseConfigTestSupport {
     void shouldStoreVersionRepoAndRuntimeTogether() throws Exception {
         UserConfigHelper.createUserConfig("");
 
-        VersionSet command = new VersionSet(new 
CamelJBangMain().withPrinter(printer));
+        VersionSet command = command();
         command.version = "4.10.0";
         command.repo = "https://repo.example.org/maven";;
         command.runtime = RuntimeType.springBoot;
@@ -75,18 +75,55 @@ class VersionSetTest extends BaseConfigTestSupport {
     }
 
     @Test
-    void shouldLeaveExistingConfigUntouchedWhenNothingProvided() throws 
Exception {
+    void shouldRefuseACallWithNothingToSet() throws Exception {
         UserConfigHelper.createUserConfig("""
                 camel-version=4.9.0
                 """);
 
-        VersionSet command = new VersionSet(new 
CamelJBangMain().withPrinter(printer));
-        // no version, repo, runtime or reset: doCall must not change the 
stored value
-        command.doCall();
+        VersionSet command = command();
+        // no version, repo, runtime or reset: it was a silent no-op 
(CAMEL-26931)
+        assertEquals(CliExitCode.USAGE, command.doCall());
+        assertTrue(printer.getStderr().contains("Give the Camel version to 
set"), printer.getOutput());
+
+        CommandLineHelper.loadProperties(properties -> assertEquals("4.9.0", 
properties.get("camel-version")));
+    }
+
+    @Test
+    void shouldRefuseAVersionThatDoesNotExist() throws Exception {
+        UserConfigHelper.createUserConfig("""
+                camel-version=4.9.0
+                """);
+
+        VersionSet command = command();
+        command.version = "9.9.9";
+        command.versionCheck = (version, repos) -> false;
+        assertEquals(CliExitCode.NOT_FOUND, command.doCall());
+        assertTrue(printer.getStderr().contains("Camel version 9.9.9 not 
found"), printer.getOutput());
 
         CommandLineHelper.loadProperties(properties -> assertEquals("4.9.0", 
properties.get("camel-version")));
     }
 
+    @Test
+    void shouldSetAVersionThatCannotBeCheckedWithAWarning() throws Exception {
+        UserConfigHelper.createUserConfig("""
+                repos=https://repo.example.org/maven
+                """);
+
+        String[] asked = new String[1];
+        VersionSet command = command();
+        command.version = "4.10.0";
+        command.versionCheck = (version, repos) -> {
+            asked[0] = repos;
+            return null;
+        };
+        assertEquals(CliExitCode.OK, command.doCall());
+        assertTrue(printer.getStderr().contains("Cannot check that Camel 
version 4.10.0 exists"), printer.getOutput());
+        // the check asks the repositories that are set already
+        assertEquals("https://repo.example.org/maven";, asked[0]);
+
+        CommandLineHelper.loadProperties(properties -> assertEquals("4.10.0", 
properties.get("camel-version")));
+    }
+
     @Test
     void shouldRemoveAllSettingsOnReset() throws Exception {
         UserConfigHelper.createUserConfig("""
@@ -97,7 +134,7 @@ class VersionSetTest extends BaseConfigTestSupport {
                 other=keep-me
                 """);
 
-        VersionSet command = new VersionSet(new 
CamelJBangMain().withPrinter(printer));
+        VersionSet command = command();
         command.reset = true;
         command.doCall();
 
@@ -113,7 +150,7 @@ class VersionSetTest extends BaseConfigTestSupport {
 
     @Test
     void shouldStoreVersionInLocalConfigWhenNotGlobal() throws Exception {
-        VersionSet command = new VersionSet(new 
CamelJBangMain().withPrinter(printer));
+        VersionSet command = command();
         command.version = "4.10.0";
         command.global = false;
         command.doCall();
@@ -131,7 +168,7 @@ class VersionSetTest extends BaseConfigTestSupport {
         String originalContent = Files.readString(
                 
CommandLineHelper.getHomeDir().resolve(CommandLineHelper.USER_CONFIG));
 
-        VersionSet command = new VersionSet(new 
CamelJBangMain().withPrinter(printer));
+        VersionSet command = command();
         command.version = "4.10.0";
         Integer exitCode = command.doCall();
 
@@ -142,4 +179,9 @@ class VersionSetTest extends BaseConfigTestSupport {
         assertEquals(originalContent, afterContent, "malformed config file 
must not be overwritten");
     }
 
+    private VersionSet command() {
+        VersionSet command = new VersionSet(new 
CamelJBangMain().withPrinter(printer));
+        command.versionCheck = (version, repos) -> true;
+        return command;
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheckTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheckTest.java
new file mode 100644
index 000000000000..e23c6cd7c838
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheckTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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 java.net.ConnectException;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.run.common.RuntimeType;
+import org.apache.camel.main.download.DownloadException;
+import org.apache.camel.tooling.maven.MavenResolutionException;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.transfer.ArtifactNotFoundException;
+import org.eclipse.aether.transfer.ArtifactTransferException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * A {@code --camel-version} that was never released says so and exits with 3, 
instead of "Cannot resolve dependencies"
+ * with exit 1 (CAMEL-26931).
+ */
+class CamelVersionCheckTest {
+
+    private static final DefaultArtifact CATALOG = new 
DefaultArtifact("org.apache.camel:camel-core-catalog:9.9.9");
+    private static final RemoteRepository CENTRAL
+            = new RemoteRepository.Builder("central", "default", 
"https://repo1.maven.org/maven2";).build();
+
+    @Test
+    void aVersionThatWasNeverReleasedIsNotFound() {
+        CliException e = CamelVersionCheck.notFound(download(new 
ArtifactNotFoundException(CATALOG, CENTRAL)), null, "9.9.9");
+        assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+        assertEquals("Camel version 9.9.9 not found in 
https://repo1.maven.org/maven2";
+                     + " (camel version list shows the released versions)",
+                e.getMessage());
+
+        CliException sb = CamelVersionCheck.notFound(
+                download(new ArtifactNotFoundException(CATALOG, CENTRAL)), 
RuntimeType.springBoot, "9.9.9");
+        assertEquals("Camel Spring Boot version 9.9.9 not found in 
https://repo1.maven.org/maven2";
+                     + " (camel version list shows the released versions)",
+                sb.getMessage());
+    }
+
+    @Test
+    void aRepositoryThatCannotBeReachedKeepsItsError() {
+        Exception refused = new ArtifactTransferException(CATALOG, CENTRAL, 
new ConnectException("Connection refused"));
+        assertNull(CamelVersionCheck.notFound(download(refused), null, 
"9.9.9"));
+    }
+
+    @Test
+    void theQuarkusPlatformReportsItsOwnErrors() {
+        assertNull(CamelVersionCheck.notFound(
+                download(new ArtifactNotFoundException(CATALOG, CENTRAL)), 
RuntimeType.quarkus, "9.9.9"));
+    }
+
+    /** As {@code MavenDependencyDownloader} throws it: the resolver error in 
a MavenResolutionException. */
+    private static DownloadException download(Exception error) {
+        ArtifactResult result = new ArtifactResult(new 
ArtifactRequest(CATALOG, List.of(CENTRAL), null));
+        result.addException(error);
+        ArtifactResolutionException are = new 
ArtifactResolutionException(List.of(result));
+        MavenResolutionException mre = new 
MavenResolutionException(are.getMessage(), are);
+        mre.getRepositories().add("https://repo1.maven.org/maven2";);
+        return new DownloadException("Cannot resolve dependencies in 
https://repo1.maven.org/maven2";, mre);
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt 
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
index 6f71ecd415c2..b1f57ff37e32 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
@@ -6799,7 +6799,8 @@ Options:
       --repo, --repos=<repo>
                             Maven repository for downloading the dependencies
                             (Use commas to separate multiple repositories)
-      --reset               Reset by removing any custom version settings
+      --reset               Reset by removing the version settings:
+                            camel-version, kamelets-version, repos and runtime
       --global              Use global or local configuration
   <version>                 Camel version
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-validate/src/main/java/org/apache/camel/dsl/jbang/core/commands/validate/CatalogVersionMixin.java
 
b/dsl/camel-jbang/camel-jbang-plugin-validate/src/main/java/org/apache/camel/dsl/jbang/core/commands/validate/CatalogVersionMixin.java
index e4ded1a62655..4f23380608c9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-validate/src/main/java/org/apache/camel/dsl/jbang/core/commands/validate/CatalogVersionMixin.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-validate/src/main/java/org/apache/camel/dsl/jbang/core/commands/validate/CatalogVersionMixin.java
@@ -22,12 +22,15 @@ import org.apache.camel.catalog.CamelCatalog;
 import org.apache.camel.catalog.DefaultCamelCatalog;
 import org.apache.camel.dsl.jbang.core.commands.MavenResolverMixin;
 import org.apache.camel.dsl.jbang.core.commands.QuarkusPlatformMixin;
+import org.apache.camel.dsl.jbang.core.common.CamelVersionCheck;
+import org.apache.camel.dsl.jbang.core.common.CliException;
 import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates;
 import org.apache.camel.dsl.jbang.core.common.RuntimeTypeConverter;
 import org.apache.camel.dsl.jbang.run.common.CatalogLoader;
 import org.apache.camel.dsl.jbang.run.common.QuarkusHelper;
 import org.apache.camel.dsl.jbang.run.common.RuntimeType;
 import org.apache.camel.dsl.yaml.validator.YamlValidator;
+import org.apache.camel.main.download.DownloadException;
 
 /**
  * The Camel version and runtime a validation answers for (CAMEL-24711): the 
catalog of that version and runtime, as
@@ -60,6 +63,19 @@ public class CatalogVersionMixin {
      * Loads the catalog of the version and runtime asked for; the built-in 
catalog when none is.
      */
     public Loaded load() throws Exception {
+        try {
+            return doLoad();
+        } catch (DownloadException e) {
+            // a version that was never released says so, and is not found 
(CAMEL-26931)
+            CliException notFound = CamelVersionCheck.notFound(e, runtime, 
camelVersion);
+            if (notFound != null) {
+                throw notFound;
+            }
+            throw e;
+        }
+    }
+
+    private Loaded doLoad() throws Exception {
         if (RuntimeType.springBoot == runtime) {
             String version = camelVersion != null ? camelVersion : new 
DefaultCamelCatalog().getCatalogVersion();
             return new Loaded(
diff --git 
a/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/common/CatalogLoader.java
 
b/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/common/CatalogLoader.java
index 7cb1acb0ba7d..37dd38d43cf8 100644
--- 
a/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/common/CatalogLoader.java
+++ 
b/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/common/CatalogLoader.java
@@ -43,6 +43,9 @@ import 
org.apache.camel.main.download.MavenDependencyDownloader;
 import org.apache.camel.support.ObjectHelper;
 import org.apache.camel.tooling.maven.MavenArtifact;
 import org.apache.camel.tooling.maven.MavenGav;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.transfer.ArtifactNotFoundException;
+import org.eclipse.aether.transfer.RepositoryOfflineException;
 
 public final class CatalogLoader {
 
@@ -53,6 +56,21 @@ public final class CatalogLoader {
     private CatalogLoader() {
     }
 
+    /**
+     * Whether the Maven repositories answered that the artifact does not 
exist, such as a Camel version that was never
+     * released. It is false when a repository could not be reached, or is 
offline and has not got the artifact.
+     */
+    public static boolean isArtifactNotFound(Throwable e) {
+        for (Throwable c = e; c != null; c = c.getCause()) {
+            if (c instanceof ArtifactResolutionException are) {
+                List<Exception> errors = are.getResults().stream().flatMap(r 
-> r.getExceptions().stream()).toList();
+                return !errors.isEmpty() && errors.stream().allMatch(
+                        x -> x instanceof ArtifactNotFoundException && 
!(x.getCause() instanceof RepositoryOfflineException));
+            }
+        }
+        return false;
+    }
+
     public static CamelCatalog loadCatalog(String repos, String version, 
boolean download) throws Exception {
         return loadCatalog(repos, version, null, download);
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/common/CatalogLoaderNotFoundTest.java
 
b/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/common/CatalogLoaderNotFoundTest.java
new file mode 100644
index 000000000000..552e6cad4c7e
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/common/CatalogLoaderNotFoundTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.run.common;
+
+import java.net.ConnectException;
+import java.util.List;
+
+import org.apache.camel.main.download.DownloadException;
+import org.apache.camel.tooling.maven.MavenResolutionException;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.transfer.ArtifactNotFoundException;
+import org.eclipse.aether.transfer.ArtifactTransferException;
+import org.eclipse.aether.transfer.RepositoryOfflineException;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * {@link CatalogLoader#isArtifactNotFound(Throwable)} tells a version that 
does not exist from a repository that cannot
+ * be reached (CAMEL-26931).
+ */
+class CatalogLoaderNotFoundTest {
+
+    private static final DefaultArtifact CATALOG = new 
DefaultArtifact("org.apache.camel:camel-core-catalog:9.9.9");
+    private static final RemoteRepository CENTRAL
+            = new RemoteRepository.Builder("central", "default", 
"https://repo1.maven.org/maven2";).build();
+    private static final RemoteRepository CUSTOM
+            = new RemoteRepository.Builder("custom1", "default", 
"http://127.0.0.1:9";).build();
+
+    @Test
+    void aVersionNoRepositoryHasIsNotFound() {
+        assertThat(CatalogLoader.isArtifactNotFound(download(new 
ArtifactNotFoundException(CATALOG, CENTRAL)))).isTrue();
+    }
+
+    @Test
+    void aRepositoryThatCannotBeReachedIsNotANotFound() {
+        Exception refused = new ArtifactTransferException(CATALOG, CUSTOM, new 
ConnectException("Connection refused"));
+        
assertThat(CatalogLoader.isArtifactNotFound(download(refused))).isFalse();
+        // central has not got it, but the other repository was not asked
+        assertThat(CatalogLoader.isArtifactNotFound(download(new 
ArtifactNotFoundException(CATALOG, CENTRAL), refused)))
+                .isFalse();
+    }
+
+    @Test
+    void anOfflineRepositoryIsNotANotFound() {
+        Exception offline = new ArtifactNotFoundException(
+                CATALOG, CENTRAL, "Cannot access central in offline mode", new 
RepositoryOfflineException(CENTRAL));
+        
assertThat(CatalogLoader.isArtifactNotFound(download(offline))).isFalse();
+    }
+
+    @Test
+    void anotherErrorIsNotANotFound() {
+        assertThat(CatalogLoader.isArtifactNotFound(new 
DownloadException("Unknown error"))).isFalse();
+        assertThat(CatalogLoader.isArtifactNotFound(null)).isFalse();
+    }
+
+    /** As {@code MavenDependencyDownloader} throws it: the resolver error in 
a MavenResolutionException. */
+    private static DownloadException download(Exception... errors) {
+        ArtifactResult result = new ArtifactResult(new 
ArtifactRequest(CATALOG, List.of(CENTRAL, CUSTOM), null));
+        for (Exception e : errors) {
+            result.addException(e);
+        }
+        ArtifactResolutionException are = new 
ArtifactResolutionException(List.of(result));
+        MavenResolutionException mre = new 
MavenResolutionException(are.getMessage(), are);
+        mre.getRepositories().add("https://repo1.maven.org/maven2";);
+        return new DownloadException("Cannot resolve dependencies in 
https://repo1.maven.org/maven2";, mre);
+    }
+}

Reply via email to