From ebe18e2c6057820558a9ade531ff8684a7cede7f Mon Sep 17 00:00:00 2001 From: satorus Date: Thu, 23 Apr 2026 16:35:48 +0200 Subject: [PATCH 1/3] #1643: Update error message in case of unknown option passed to commandlet --- .../tools/ide/context/AbstractIdeContext.java | 10 +++++++++ .../tools/ide/validation/ValidationState.java | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index f810c7d4d1..5b7f421544 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -1149,6 +1149,15 @@ public int run(CliArguments arguments) { if (result != null) { LOG.error(result.getErrorMessage()); } + if (cmd != null && result instanceof ValidationState res) { + final CliArgument arg = res.getCliArgument(); + if (arg != null) { + step.error("Option {} not found for commandlet {}.", arg.get(), cmd.getName()); + IdeLogLevel.INTERACTION.log(LOG, "To see the available options and arguments call the following command:\n" + + "ide {} help", cmd.getName()); + return 1; + } + } step.error("Invalid arguments: {}", current.getArgs()); IdeLogLevel.INTERACTION.log(LOG, "For additional details run ide help {}", cmd == null ? "" : cmd.getName()); return 1; @@ -1542,6 +1551,7 @@ public ValidationResult apply(CliArguments arguments, Commandlet cmd) { if (currentProperty == null) { LOG.trace("No option or next value found"); ValidationState state = new ValidationState(null); + state.setCliArgument(currentArgument); state.addErrorMessage("No matching property found"); return state; } diff --git a/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java b/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java index 5758143e60..799b85019f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java +++ b/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java @@ -1,5 +1,7 @@ package com.devonfw.tools.ide.validation; +import com.devonfw.tools.ide.cli.CliArgument; + /** * Implementation of {@link ValidationResult} as a mutable state that can collect errors dynamically. */ @@ -9,6 +11,11 @@ public class ValidationState implements ValidationResult { private StringBuilder errorMessage; + /** + * Field for the {@link CliArgument} that was the reason for a failed validation. + */ + private CliArgument cliArgument; + /** * The default constructor for no property. */ @@ -68,4 +75,18 @@ public void add(ValidationResult result) { } } } + + /** + * @param cliArgument The {@link CliArgument} that failed the validation. + */ + public void setCliArgument(CliArgument cliArgument) { + this.cliArgument = cliArgument; + } + + /** + * @return The {@link CliArgument} that failed the validation. + */ + public CliArgument getCliArgument() { + return this.cliArgument; + } } From 499ddaf12d7306c76b3f0f6ec9e89ac2c3319ee7 Mon Sep 17 00:00:00 2001 From: satorus Date: Thu, 23 Apr 2026 17:05:51 +0200 Subject: [PATCH 2/3] #1643: Improve error message for invalid property arguments Add parseHint and errorHint Fields on ValidationState and Property to convey parse errors from lower levels to CLI output --- .../tools/ide/context/AbstractIdeContext.java | 35 +++++++++++--- .../tools/ide/property/BooleanProperty.java | 6 +++ .../ide/property/CommandletProperty.java | 11 +++++ .../tools/ide/property/EnumProperty.java | 15 ++++++ .../devonfw/tools/ide/property/Property.java | 47 +++++++++++++++++-- .../tools/ide/property/ToolProperty.java | 12 +++++ .../tools/ide/validation/ValidationState.java | 32 +++++++++++++ .../tools/ide/version/VersionIdentifier.java | 27 ++++++++++- .../tools/ide/cli/CliAdvancedParsingTest.java | 38 +++++++++++++++ .../tools/ide/property/EnumPropertyTest.java | 11 +++++ .../ide/version/VersionIdentifierTest.java | 34 ++++++++++++++ 11 files changed, 256 insertions(+), 12 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index 5b7f421544..001cb0890f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -1146,20 +1146,40 @@ public int run(CliArguments arguments) { } activateLogging(cmd); verifyIdeMinVersion(false); - if (result != null) { - LOG.error(result.getErrorMessage()); - } + if (cmd != null && result instanceof ValidationState res) { final CliArgument arg = res.getCliArgument(); if (arg != null) { - step.error("Option {} not found for commandlet {}.", arg.get(), cmd.getName()); + if (arg.getValue() != null) { + // --flag=value with an invalid value: reconstruct the error message here to control order + String exMsg = res.getParseExceptionMessage(); + if (exMsg != null) { + step.error(Property.INVALID_ARGUMENT + ": {}", arg.getValue(), arg.getKey(), cmd.getName(), exMsg); + } else { + step.error(Property.INVALID_ARGUMENT, arg.getValue(), arg.getKey(), cmd.getName()); + } + String hint = res.getParseHint(); + if (hint != null) { + LOG.error(Property.INVALID_ARGUMENT_HELP_MULTIPLE, hint); + } + } else { + // Unknown option flag or positional value with invalid content + step.error("Option {} not found for commandlet {}.", arg.get(), cmd.getName()); + String hint = res.getParseHint(); + if (hint != null) { + LOG.error(Property.INVALID_ARGUMENT_HELP_MULTIPLE, hint); + } + } IdeLogLevel.INTERACTION.log(LOG, "To see the available options and arguments call the following command:\n" + "ide {} help", cmd.getName()); return 1; } } - step.error("Invalid arguments: {}", current.getArgs()); - IdeLogLevel.INTERACTION.log(LOG, "For additional details run ide help {}", cmd == null ? "" : cmd.getName()); + if (result != null && (!(result instanceof ValidationState) || ((ValidationState) result).getCliArgument() == null) ) { + LOG.error(result.getErrorMessage()); + step.error("Invalid arguments: {}", current.getArgs()); + IdeLogLevel.INTERACTION.log(LOG, "For additional details run ide help {}", cmd == null ? "" : cmd.getName()); + } return 1; } catch (Throwable t) { activateLogging(cmd); @@ -1572,6 +1592,9 @@ public ValidationResult apply(CliArguments arguments, Commandlet cmd) { if (!matches) { ValidationState state = new ValidationState(null); state.addErrorMessage("No matching property found"); + state.setCliArgument(currentArgument); + state.setParseHint(currentProperty.getAndClearLastParseHint()); + state.setParseExceptionMessage(currentProperty.getAndClearLastParseExceptionMessage()); return state; } currentArgument = arguments.current(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/BooleanProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/BooleanProperty.java index 680b62b1f3..80d1cf3518 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/BooleanProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/BooleanProperty.java @@ -81,6 +81,12 @@ public void setValueAsString(String valueAsString, IdeContext context) { setValue(b); } + @Override + protected String getValidValuesErrorHint(IdeContext context, Commandlet commandlet) { + + return "'true', 'yes', 'false', 'no'"; + } + @Override protected boolean applyValue(String argValue, boolean lookahead, CliArguments args, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java index 76b42d0aa4..1db3f9761e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/CommandletProperty.java @@ -1,5 +1,7 @@ package com.devonfw.tools.ide.property; +import java.util.stream.Collectors; + import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; @@ -58,6 +60,15 @@ protected void completeValue(String arg, IdeContext context, Commandlet commandl } } + @Override + protected String getValidValuesErrorHint(IdeContext context, Commandlet commandlet) { + + return context.getCommandletManager().getCommandlets().stream() + .map(Commandlet::getName) + .map(n -> "'" + n + "'") + .collect(Collectors.joining(", ")); + } + @Override public Commandlet parse(String valueAsString, IdeContext context) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java index fd3d0ec276..fa897fc3fd 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/EnumProperty.java @@ -1,6 +1,8 @@ package com.devonfw.tools.ide.property; import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; @@ -48,6 +50,19 @@ public V parse(String valueAsString, IdeContext context) { throw new IllegalArgumentException(String.format("Invalid Enum option: %s", valueAsString)); } + /** + * @return All possible EnumValues as string, delimited by a comma. + */ + public String getEnumValuesAsString() { + return Stream.of(this.valueType.getEnumConstants()).map(c -> String.format("'%s'", c.toString().toLowerCase(Locale.ROOT))).collect(Collectors.joining(", ")); + } + + @Override + protected String getValidValuesErrorHint(IdeContext context, Commandlet commandlet) { + + return getEnumValuesAsString(); + } + @Override protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java index dbda7b1271..8494584645 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/Property.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/Property.java @@ -32,7 +32,8 @@ public abstract class Property { private static final Logger LOG = LoggerFactory.getLogger(Property.class); - private static final String INVALID_ARGUMENT = "Invalid CLI argument '{}' for property '{}' of commandlet '{}'"; + public static final String INVALID_ARGUMENT = "Invalid CLI argument '{}' for property '{}' of commandlet '{}'"; + public static final String INVALID_ARGUMENT_HELP_MULTIPLE = "Did you mean one of [{}]?"; private static final String INVALID_ARGUMENT_WITH_EXCEPTION_MESSAGE = INVALID_ARGUMENT + ": {}"; @@ -53,6 +54,10 @@ public abstract class Property { /** @see #getValue() */ protected final List value = new ArrayList<>(); + private String lastParseHint; + + private String lastParseExceptionMessage; + /** * The constructor. * @@ -306,19 +311,51 @@ public void setValueAsString(String valueAsString, IdeContext context) { */ public final boolean assignValueAsString(String valueAsString, IdeContext context, Commandlet commandlet) { + this.lastParseHint = null; + this.lastParseExceptionMessage = null; try { setValueAsString(valueAsString, context); return true; } catch (Exception e) { - if (e instanceof IllegalArgumentException) { - LOG.warn(INVALID_ARGUMENT, valueAsString, getNameOrAlias(), commandlet.getName()); - } else { - LOG.warn(INVALID_ARGUMENT_WITH_EXCEPTION_MESSAGE, valueAsString, getNameOrAlias(), commandlet.getName(), e.getMessage()); + if (!(e instanceof IllegalArgumentException)) { + this.lastParseExceptionMessage = e.getMessage(); } + this.lastParseHint = getValidValuesErrorHint(context, commandlet); return false; } } + /** + * @return the hint string for the last failed parse (e.g. "Did you mean one of [...]?"), and clears it. Returns {@code null} if no hint is available. + */ + public String getAndClearLastParseHint() { + + String h = this.lastParseHint; + this.lastParseHint = null; + return h; + } + + /** + * @return the exception message from the last failed parse if the exception was not an {@link IllegalArgumentException}, and clears it. Returns {@code null} + * otherwise. + */ + public String getAndClearLastParseExceptionMessage() { + + String m = this.lastParseExceptionMessage; + this.lastParseExceptionMessage = null; + return m; + } + + /** + * @param context the {@link IdeContext}. + * @param commandlet the {@link Commandlet} owning this property. + * @return a formatted string of valid values to show as a hint when an invalid value is given, or {@code null} if no hint is available. + */ + protected String getValidValuesErrorHint(IdeContext context, Commandlet commandlet) { + + return null; + } + /** * @return the {@code null} value. */ diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java index 5cf4f2002f..392aa4e437 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/ToolProperty.java @@ -1,5 +1,7 @@ package com.devonfw.tools.ide.property; +import java.util.stream.Collectors; + import com.devonfw.tools.ide.commandlet.Commandlet; import com.devonfw.tools.ide.completion.CompletionCandidateCollector; import com.devonfw.tools.ide.context.IdeContext; @@ -68,6 +70,16 @@ public ToolCommandlet parse(String valueAsString, IdeContext context) { return context.getCommandletManager().getRequiredToolCommandlet(valueAsString); } + @Override + protected String getValidValuesErrorHint(IdeContext context, Commandlet commandlet) { + + return context.getCommandletManager().getCommandlets().stream() + .filter(c -> c instanceof ToolCommandlet) + .map(Commandlet::getName) + .map(n -> "'" + n + "'") + .collect(Collectors.joining(", ")); + } + @Override protected void completeValue(String arg, IdeContext context, Commandlet commandlet, CompletionCandidateCollector collector) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java b/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java index 799b85019f..69787d71cd 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java +++ b/cli/src/main/java/com/devonfw/tools/ide/validation/ValidationState.java @@ -76,6 +76,10 @@ public void add(ValidationResult result) { } } + private String parseHint; + + private String parseExceptionMessage; + /** * @param cliArgument The {@link CliArgument} that failed the validation. */ @@ -89,4 +93,32 @@ public void setCliArgument(CliArgument cliArgument) { public CliArgument getCliArgument() { return this.cliArgument; } + + /** + * @param parseHint the hint to display when a parse error occurred (e.g. a list of valid values). + */ + public void setParseHint(String parseHint) { + this.parseHint = parseHint; + } + + /** + * @return the hint for the failed parse, or {@code null} if none. + */ + public String getParseHint() { + return this.parseHint; + } + + /** + * @param parseExceptionMessage the message from a non-{@link IllegalArgumentException} parse failure. + */ + public void setParseExceptionMessage(String parseExceptionMessage) { + this.parseExceptionMessage = parseExceptionMessage; + } + + /** + * @return the exception message from a non-{@link IllegalArgumentException} parse failure, or {@code null}. + */ + public String getParseExceptionMessage() { + return this.parseExceptionMessage; + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionIdentifier.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionIdentifier.java index bafadc950e..65d2c28417 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionIdentifier.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionIdentifier.java @@ -1,7 +1,9 @@ package com.devonfw.tools.ide.version; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,8 +90,31 @@ public static VersionIdentifier resolveVersionPattern(GenericVersionRange versio return vi; } } + List closest = findClosestVersions(version, versions, 5); + String closestStr = closest.stream().map(Object::toString).collect(Collectors.joining(", ")); throw new CliException( - "Could not find any version matching '" + version + "' - there are " + versions.size() + " version(s) available but none matched!"); + "Could not find any version matching '" + version + "' - there are " + versions.size() + + " version(s) available but none matched!\nDid you mean one of: " + closestStr + "?"); + } + + private static List findClosestVersions(GenericVersionRange version, List versions, int maxCount) { + + if (version instanceof VersionIdentifier vi && !vi.isPattern()) { + long requestedMajor = vi.getStart().getNumber(); + List majorMatches = new ArrayList<>(); + for (VersionIdentifier v : versions) { + if (v.getStart().getNumber() == requestedMajor) { + majorMatches.add(v); + if (majorMatches.size() >= maxCount) { + break; + } + } + } + if (!majorMatches.isEmpty()) { + return majorMatches; + } + } + return versions.size() <= maxCount ? versions : versions.subList(0, maxCount); } /** diff --git a/cli/src/test/java/com/devonfw/tools/ide/cli/CliAdvancedParsingTest.java b/cli/src/test/java/com/devonfw/tools/ide/cli/CliAdvancedParsingTest.java index 8f2966a717..ea73df11a3 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/cli/CliAdvancedParsingTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/cli/CliAdvancedParsingTest.java @@ -1,10 +1,15 @@ package com.devonfw.tools.ide.cli; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.tool.ToolCommandlet; + import org.junit.jupiter.api.Test; import com.devonfw.tools.ide.context.AbstractIdeContextTest; import com.devonfw.tools.ide.context.IdeTestContext; +import java.util.stream.Collectors; + /** * Integration test of {@link com.devonfw.tools.ide.context.AbstractIdeContext#run(CliArguments) CLI parsing}. */ @@ -45,4 +50,37 @@ void testRunRepositorySetupWithoutArgumentWillSucceed() { // assert assertThat(success).isEqualTo(0); } + + @Test + void testRunCommandletWithUnknownOption() { + IdeTestContext context = newContext(PROJECT_MVN); + CliArguments args = new CliArguments("install", "unknownOption"); + args.next(); + final String possibleCommands = context.getCommandletManager().getCommandlets().stream() + .filter(c -> c instanceof ToolCommandlet) + .map(Commandlet::getName) + .map(n -> "'" + n + "'") + .collect(Collectors.joining(", ")); + + int resultStatus = context.run(args); + assertThat(resultStatus).isEqualTo(1); + assertThat(context).logAtError().hasMessage("Option unknownOption not found for commandlet install."); + assertThat(context).logAtError().hasMessage("Did you mean one of [" + possibleCommands + "]?"); + assertThat(context).logAtInteraction().hasMessage("To see the available options and arguments call the following command:\n" + + "ide install help"); + } + + @Test + void testRunCommandletWithUnknownOptionParamValue() { + IdeTestContext context = newContext(PROJECT_MVN); + CliArguments args = new CliArguments("set-version", "intellij", "--cfg=config"); + args.next(); + + int resultStatus = context.run(args); + assertThat(resultStatus).isEqualTo(1); + assertThat(context).logAtError().hasMessage("Invalid CLI argument 'config' for property '--cfg' of commandlet 'set-version'"); + assertThat(context).logAtError().hasMessage("Did you mean one of ['user', 'settings', 'workspace', 'conf']?"); + assertThat(context).logAtInteraction().hasMessage("To see the available options and arguments call the following command:\n" + + "ide set-version help"); + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/property/EnumPropertyTest.java b/cli/src/test/java/com/devonfw/tools/ide/property/EnumPropertyTest.java index 769ff113bb..caddc4edcb 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/property/EnumPropertyTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/property/EnumPropertyTest.java @@ -52,4 +52,15 @@ void testCompleteValue() { assertThat(collector.getCandidates().stream().map(CompletionCandidate::text)).containsExactly(expectedCandidates); } + + @Test + void testGetEnumValuesAsString() { + final String expectedResult = "'elementzero', 'elementone', 'elementtwo'"; + + final EnumProperty enumProp = new EnumProperty<>("", false, "", TestEnum.class); + final String actualResult = enumProp.getEnumValuesAsString(); + + assertThat(actualResult).isEqualTo(expectedResult); + + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/version/VersionIdentifierTest.java b/cli/src/test/java/com/devonfw/tools/ide/version/VersionIdentifierTest.java index c0abaefbde..1bf354893f 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/version/VersionIdentifierTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/version/VersionIdentifierTest.java @@ -4,11 +4,15 @@ import java.util.Collections; import java.util.List; +import com.devonfw.tools.ide.cli.CliException; + import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import static org.junit.jupiter.api.Assertions.assertThrows; + /** * Test of {@link VersionIdentifier}. */ @@ -358,4 +362,34 @@ void testIsStable() { assertThat(VersionIdentifier.LATEST_UNSTABLE.isStable()).isFalse(); } + @Test + void testResolveVersionPatternVersionFound() { + final VersionIdentifier identifier = VersionIdentifier.of("2025.01.002"); + final List availableVersions = List.of(identifier); + + final VersionIdentifier resolvedVersion = VersionIdentifier.resolveVersionPattern(identifier, availableVersions); + assertThat(resolvedVersion).isEqualTo(identifier); + } + + @Test + void testResolveVersionPatternVersionFoundPattern() { + final VersionIdentifier identifier = VersionIdentifier.of("2025.01.002"); + final VersionIdentifier identifierPattern = VersionIdentifier.of("2025.01.*"); + final List availableVersions = List.of(identifier); + + final VersionIdentifier resolvedVersion = VersionIdentifier.resolveVersionPattern(identifierPattern, availableVersions); + assertThat(resolvedVersion).isEqualTo(identifier); + } + + @Test + void testResolveVersionPatternNoVersionFound() { + final VersionIdentifier identifier = VersionIdentifier.of("2025.01.002"); + final VersionIdentifier identifierPattern = VersionIdentifier.of("2026.01.*"); + final List availableVersions = List.of(identifier); + + final Exception exception = assertThrows(CliException.class, () -> VersionIdentifier.resolveVersionPattern(identifierPattern, availableVersions)); + assertThat(exception.getMessage()).isEqualTo("Could not find any version matching '" + identifierPattern + "' - there are " + availableVersions.size() + + " version(s) available but none matched!\nDid you mean one of: " + identifier + "?"); + } + } From f59d7af1d89b55428da427e2cf655c8b23d44c29 Mon Sep 17 00:00:00 2001 From: satorus Date: Mon, 27 Apr 2026 10:12:43 +0200 Subject: [PATCH 3/3] #1643: Add Changelog entry --- CHANGELOG.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 88e91065bb..860dca7e52 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -15,6 +15,7 @@ Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/1823[#1823]: Fix IDEasy creates duplicate entries in .gitconfig * https://github.com/devonfw/IDEasy/issues/1724[#1724]: Add gui commandlet * https://github.com/devonfw/IDEasy/issues/1853[#1853]: Add ARM releases for VSCode on Mac +* https://github.com/devonfw/IDEasy/issues/1643[#1643]: Improve CLI error messages for unknown options and property values The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/44?closed=1[milestone 2026.05.001].