diff --git a/config/native/no-ldap.java.security b/config/native/no-ldap.java.security
new file mode 100644
index 00000000..b45f29d1
--- /dev/null
+++ b/config/native/no-ldap.java.security
@@ -0,0 +1,16 @@
+security.provider.1=SUN
+security.provider.2=SunRsaSign
+security.provider.3=SunEC
+security.provider.4=SunJSSE
+security.provider.5=SunJCE
+security.provider.6=SunJGSS
+security.provider.7=SunSASL
+security.provider.8=XMLDSig
+security.provider.9=SunPCSC
+security.provider.10=JdkSASL
+security.provider.11=SunPKCS11
+keystore.type=pkcs12
+ssl.KeyManagerFactory.algorithm=SunX509
+ssl.TrustManagerFactory.algorithm=PKIX
+securerandom.source=file:/dev/random
+securerandom.strongAlgorithms=NativePRNGBlocking:SUN,DRBG:SUN
diff --git a/pom.xml b/pom.xml
index b4c0ecb8..ed784792 100644
--- a/pom.xml
+++ b/pom.xml
@@ -157,7 +157,6 @@
src/main/resources
true
- version.properties
META-INF/native-image/dev.ayagmar/quarkusforge/resource-config.json
@@ -165,7 +164,6 @@
src/main/resources
false
- version.properties
META-INF/native-image/dev.ayagmar/quarkusforge/resource-config.json
@@ -207,6 +205,8 @@
-Aproject=${project.groupId}/${project.artifactId}
+ -Adisable.reflect.config
+ -Adisable.resource.config
-Adisable.proxy.config
@@ -424,7 +424,7 @@
-Os
--emit build-report
-H:+ReportExceptionStackTraces
- -H:+SharedArenaSupport
+ -Djava.security.properties==${project.basedir}/config/native/no-ldap.java.security
--enable-url-protocols=http,https
${native.access.flag}
@@ -434,6 +434,30 @@
+
+ unix-native-jline-trim
+
+
+ unix
+
+
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+
+
+ --exclude-config
+ jline-.*\.jar
+ META-INF/native-image/org\.jline/(jansi|jline-native|jline-terminal-jansi|jline-terminal-jna|jline-terminal-jni)/.*
+
+
+
+
+
+
+
headless
diff --git a/src/main/java/dev/ayagmar/quarkusforge/cli/CliCommandLineFactory.java b/src/main/java/dev/ayagmar/quarkusforge/cli/CliCommandLineFactory.java
new file mode 100644
index 00000000..9049626a
--- /dev/null
+++ b/src/main/java/dev/ayagmar/quarkusforge/cli/CliCommandLineFactory.java
@@ -0,0 +1,44 @@
+package dev.ayagmar.quarkusforge.cli;
+
+import picocli.CommandLine;
+
+final class CliCommandLineFactory {
+ private CliCommandLineFactory() {}
+
+ static CommandLine create(T command) {
+ CommandLine commandLine = new CommandLine(command);
+ addRequestOptionsMixin(commandLine, command);
+
+ GenerateCommand generateCommand = new GenerateCommand(command);
+ CommandLine generateCommandLine = new CommandLine(generateCommand);
+ generateCommandLine.addMixin("requestOptions", generateCommand.requestOptions());
+ commandLine.addSubcommand("generate", generateCommandLine);
+
+ commandLine.getCommandSpec().version(CliVersionProvider.resolveVersion());
+ commandLine.setExecutionStrategy(
+ parseResult -> {
+ recordMatchedRequestOptions(parseResult);
+ return new CommandLine.RunLast().execute(parseResult);
+ });
+ return commandLine;
+ }
+
+ private static void addRequestOptionsMixin(CommandLine commandLine, HeadlessRunner command) {
+ if (command instanceof QuarkusForgeCli quarkusForgeCli) {
+ commandLine.addMixin("requestOptions", quarkusForgeCli.requestOptions());
+ }
+ }
+
+ private static void recordMatchedRequestOptions(CommandLine.ParseResult parseResult) {
+ for (CommandLine.ParseResult current = parseResult;
+ current != null;
+ current = current.subcommand()) {
+ Object userObject = current.commandSpec().userObject();
+ if (userObject instanceof QuarkusForgeCli quarkusForgeCli) {
+ quarkusForgeCli.requestOptions().recordMatchedOptions(current);
+ } else if (userObject instanceof GenerateCommand generateCommand) {
+ generateCommand.requestOptions().recordMatchedOptions(current);
+ }
+ }
+ }
+}
diff --git a/src/main/java/dev/ayagmar/quarkusforge/cli/CliVersionProvider.java b/src/main/java/dev/ayagmar/quarkusforge/cli/CliVersionProvider.java
index d31aad89..0522e17a 100644
--- a/src/main/java/dev/ayagmar/quarkusforge/cli/CliVersionProvider.java
+++ b/src/main/java/dev/ayagmar/quarkusforge/cli/CliVersionProvider.java
@@ -1,47 +1,18 @@
package dev.ayagmar.quarkusforge.cli;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.Objects;
-import java.util.Properties;
-import picocli.CommandLine;
-final class CliVersionProvider implements CommandLine.IVersionProvider {
- private static final String VERSION_RESOURCE = "version.properties";
- private static final String VERSION_KEY = "version";
+final class CliVersionProvider {
private static final String UNKNOWN_VERSION = "unknown";
- @Override
- public String[] getVersion() {
- return new String[] {resolveVersion()};
- }
-
static String resolveVersion() {
String implementationVersion = CliVersionProvider.class.getPackage().getImplementationVersion();
if (isUsableVersion(implementationVersion)) {
return implementationVersion.strip();
}
- String resourceVersion = readVersionFromResource();
- if (isUsableVersion(resourceVersion)) {
- return resourceVersion.strip();
- }
return UNKNOWN_VERSION;
}
- private static String readVersionFromResource() {
- try (InputStream inputStream =
- CliVersionProvider.class.getClassLoader().getResourceAsStream(VERSION_RESOURCE)) {
- if (inputStream == null) {
- return null;
- }
- Properties properties = new Properties();
- properties.load(inputStream);
- return properties.getProperty(VERSION_KEY);
- } catch (IOException ignored) {
- return null;
- }
- }
-
private static boolean isUsableVersion(String value) {
return value != null && !value.isBlank() && !Objects.equals(value.strip(), UNKNOWN_VERSION);
}
diff --git a/src/main/java/dev/ayagmar/quarkusforge/cli/GenerateCommand.java b/src/main/java/dev/ayagmar/quarkusforge/cli/GenerateCommand.java
index fb92c6ce..e5331fe6 100644
--- a/src/main/java/dev/ayagmar/quarkusforge/cli/GenerateCommand.java
+++ b/src/main/java/dev/ayagmar/quarkusforge/cli/GenerateCommand.java
@@ -4,20 +4,19 @@
import dev.ayagmar.quarkusforge.forge.Forgefile;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Callable;
import picocli.CommandLine.Command;
-import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
-import picocli.CommandLine.ParentCommand;
@Command(
name = "generate",
mixinStandardHelpOptions = true,
description = "Generate a Quarkus project without starting the TUI")
public final class GenerateCommand implements Callable {
- @ParentCommand private HeadlessRunner rootCommand;
+ private final HeadlessRunner rootCommand;
- @Mixin private RequestOptions requestOptions = new RequestOptions();
+ private RequestOptions requestOptions = new RequestOptions();
@Option(
names = {"-e", "--extension"},
@@ -64,6 +63,17 @@ public final class GenerateCommand implements Callable {
description = "Validate full generation request without writing files")
private boolean dryRun;
+ public GenerateCommand() {
+ this(
+ command -> {
+ throw new IllegalStateException("Generate command runner not configured");
+ });
+ }
+
+ GenerateCommand(HeadlessRunner rootCommand) {
+ this.rootCommand = Objects.requireNonNull(rootCommand);
+ }
+
@Override
public Integer call() {
return rootCommand.runHeadlessGenerate(this);
@@ -84,6 +94,10 @@ public void setCliPrefill(CliPrefill prefill) {
requestOptions = RequestOptions.explicitFromCliPrefill(prefill);
}
+ RequestOptions requestOptions() {
+ return requestOptions;
+ }
+
public List extensions() {
return extensions;
}
diff --git a/src/main/java/dev/ayagmar/quarkusforge/cli/HeadlessCli.java b/src/main/java/dev/ayagmar/quarkusforge/cli/HeadlessCli.java
index 7d84bece..62584896 100644
--- a/src/main/java/dev/ayagmar/quarkusforge/cli/HeadlessCli.java
+++ b/src/main/java/dev/ayagmar/quarkusforge/cli/HeadlessCli.java
@@ -5,22 +5,16 @@
import dev.ayagmar.quarkusforge.runtime.RuntimeServices;
import java.util.Objects;
import java.util.concurrent.Callable;
-import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
-import picocli.CommandLine.Spec;
@Command(
name = "quarkus-forge",
mixinStandardHelpOptions = true,
- versionProvider = CliVersionProvider.class,
- subcommands = {GenerateCommand.class},
description = "Quarkus forge headless CLI")
public final class HeadlessCli implements Callable, HeadlessRunner {
private final RuntimeConfig runtimeConfig;
- @Spec CommandLine.Model.CommandSpec spec;
-
@Option(
names = "--verbose",
defaultValue = "false",
@@ -43,7 +37,7 @@ public final class HeadlessCli implements Callable, HeadlessRunner {
@Override
public Integer call() {
- spec.commandLine().usage(System.out);
+ CliCommandLineFactory.create(this).usage(System.out);
return ExitCodes.OK;
}
@@ -60,7 +54,7 @@ public static int runWithArgs(String[] args) {
}
static int runWithArgs(String[] args, RuntimeConfig runtimeConfig) {
- return new CommandLine(new HeadlessCli(runtimeConfig)).execute(args);
+ return CliCommandLineFactory.create(new HeadlessCli(runtimeConfig)).execute(args);
}
public static void main(String[] args) {
diff --git a/src/main/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCli.java b/src/main/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCli.java
index 1c4d4bda..a39ea5f2 100644
--- a/src/main/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCli.java
+++ b/src/main/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCli.java
@@ -23,22 +23,19 @@
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Function;
-import picocli.CommandLine;
import picocli.CommandLine.Command;
-import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
@Command(
name = "quarkus-forge",
- versionProvider = CliVersionProvider.class,
- subcommands = {GenerateCommand.class},
+ mixinStandardHelpOptions = true,
description = "Quarkus forge terminal UI")
public final class QuarkusForgeCli implements Callable, HeadlessRunner {
private static final PostTuiActionExecutor POST_TUI_ACTION_EXECUTOR = new PostTuiActionExecutor();
private static final TuiBootstrapService TUI_BOOTSTRAP_SERVICE = new TuiBootstrapService();
- @Mixin private RequestOptions requestOptions = new RequestOptions();
+ private RequestOptions requestOptions = new RequestOptions();
private final RuntimeConfig runtimeConfig;
private final Function apiClientFactory;
@@ -65,18 +62,6 @@ public final class QuarkusForgeCli implements Callable, HeadlessRunner
@Option(names = "--interactive-smoke-test", hidden = true, defaultValue = "false")
private boolean interactiveSmokeTest;
- @Option(
- names = {"-h", "--help"},
- usageHelp = true,
- description = "Show this help message and exit.")
- private boolean helpRequested;
-
- @Option(
- names = {"-V", "--version"},
- versionHelp = true,
- description = "Print version information and exit.")
- private boolean versionRequested;
-
@Option(
names = "--post-generate-hook",
defaultValue = "",
@@ -158,7 +143,7 @@ public static int runWithArgs(String[] args) {
}
static int runWithArgs(String[] args, RuntimeConfig runtimeConfig) {
- return new CommandLine(new QuarkusForgeCli(runtimeConfig)).execute(args);
+ return CliCommandLineFactory.create(new QuarkusForgeCli(runtimeConfig)).execute(args);
}
public static void main(String[] args) {
diff --git a/src/main/java/dev/ayagmar/quarkusforge/cli/RequestOptions.java b/src/main/java/dev/ayagmar/quarkusforge/cli/RequestOptions.java
index 9f2bd57a..4da1288e 100644
--- a/src/main/java/dev/ayagmar/quarkusforge/cli/RequestOptions.java
+++ b/src/main/java/dev/ayagmar/quarkusforge/cli/RequestOptions.java
@@ -6,18 +6,12 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
-import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
-import picocli.CommandLine.Spec;
+import picocli.CommandLine.ParseResult;
final class RequestOptions {
private final Set prefilledOptions = new HashSet<>();
-
- /**
- * Injected by Picocli after parsing; null when the object is constructed manually (e.g. in tests
- * or when calling {@link #defaults()}).
- */
- @Spec CommandSpec spec;
+ private final Set matchedOptions = new HashSet<>();
static final String DEFAULT_GROUP_ID = ProjectInputDefaults.GROUP_ID;
static final String DEFAULT_ARTIFACT_ID = ProjectInputDefaults.ARTIFACT_ID;
@@ -181,15 +175,16 @@ private static RequestOptions fromCliPrefill(CliPrefill prefill, boolean markPre
/**
* Returns {@code true} when the user explicitly supplied the option on the command line. Falls
- * back to value-equality detection when {@link #spec} is not available (tests, defaults()).
+ * back to value-equality detection when no parse result has been recorded yet (tests,
+ * defaults()).
*
* @param optionName canonical option name, e.g. {@code "--group-id"}
* @param currentValue the current field value
* @param defaultValue the compiled-in default value
*/
boolean isExplicitlySet(String optionName, String currentValue, String defaultValue) {
- if (spec != null && spec.commandLine() != null && spec.commandLine().getParseResult() != null) {
- return spec.commandLine().getParseResult().hasMatchedOption(optionName);
+ if (matchedOptions.contains(optionName)) {
+ return true;
}
if (prefilledOptions.contains(optionName)) {
return false;
@@ -205,6 +200,16 @@ private String explicitValue(String optionName, String currentValue, String defa
return currentValue;
}
+ void recordMatchedOptions(ParseResult parseResult) {
+ matchedOptions.clear();
+ if (parseResult == null) {
+ return;
+ }
+ parseResult
+ .matchedOptions()
+ .forEach(optionSpec -> matchedOptions.add(optionSpec.longestName()));
+ }
+
private String applyCliValue(
String optionName, String cliValue, String fallbackValue, boolean markPrefilled) {
if (cliValue == null || cliValue.isBlank()) {
diff --git a/src/main/java/dev/ayagmar/quarkusforge/headless/HeadlessGenerationService.java b/src/main/java/dev/ayagmar/quarkusforge/headless/HeadlessGenerationService.java
index c282d22b..23268143 100644
--- a/src/main/java/dev/ayagmar/quarkusforge/headless/HeadlessGenerationService.java
+++ b/src/main/java/dev/ayagmar/quarkusforge/headless/HeadlessGenerationService.java
@@ -5,13 +5,10 @@
import dev.ayagmar.quarkusforge.api.CatalogData;
import dev.ayagmar.quarkusforge.api.CatalogDataService;
import dev.ayagmar.quarkusforge.api.QuarkusApiClient;
-import dev.ayagmar.quarkusforge.application.InputResolutionService;
import dev.ayagmar.quarkusforge.archive.ProjectArchiveService;
import dev.ayagmar.quarkusforge.cli.ExitCodes;
import dev.ayagmar.quarkusforge.cli.GenerateCommand;
import dev.ayagmar.quarkusforge.diagnostics.DiagnosticLogger;
-import dev.ayagmar.quarkusforge.domain.ForgeUiState;
-import dev.ayagmar.quarkusforge.domain.MetadataCompatibilityContext;
import dev.ayagmar.quarkusforge.domain.ProjectRequest;
import dev.ayagmar.quarkusforge.domain.ValidationReport;
import dev.ayagmar.quarkusforge.persistence.ExtensionFavoritesStore;
@@ -24,8 +21,8 @@
public final class HeadlessGenerationService implements AutoCloseable {
private final HeadlessCatalogLoader catalogLoader;
- private final HeadlessExtensionResolutionService extensionResolutionService;
private final HeadlessForgefilePersistenceService forgefilePersistenceService;
+ private final HeadlessRequestPlanner requestPlanner;
private final HeadlessGenerationExecutionService generationExecutionService;
private final AutoCloseable closeOwner;
@@ -64,8 +61,10 @@ private HeadlessGenerationService(
HeadlessGenerationExecutionService generationExecutionService,
AutoCloseable closeOwner) {
this.catalogLoader = Objects.requireNonNull(catalogLoader);
- this.extensionResolutionService = Objects.requireNonNull(extensionResolutionService);
this.forgefilePersistenceService = Objects.requireNonNull(forgefilePersistenceService);
+ this.requestPlanner =
+ new HeadlessRequestPlanner(
+ Objects.requireNonNull(extensionResolutionService), forgefilePersistenceService);
this.generationExecutionService = Objects.requireNonNull(generationExecutionService);
this.closeOwner = Objects.requireNonNull(closeOwner);
}
@@ -116,28 +115,9 @@ public int run(GenerateCommand command, boolean globalDryRun, boolean verbose) {
diagnostics);
}
- ForgeUiState validatedState = resolveValidatedState(inputs, catalogData);
- if (!validatedState.canSubmit()) {
- return validationFailure(
- validatedState.validation(),
- catalogData.sourceLabel(),
- catalogData.detailMessage(),
- diagnostics);
- }
-
- List extensionIds;
+ HeadlessRequestPlanner.HeadlessGenerationPlan plan;
try {
- extensionIds =
- extensionResolutionService.resolveExtensionIds(
- validatedState.request().platformStream(),
- inputs.extensionInputs(),
- inputs.presetInputs(),
- HeadlessExtensionResolutionService.knownExtensionIds(catalogData),
- catalogTimeout);
- if (inputs.lockCheck()) {
- forgefilePersistenceService.validateLockDrift(
- inputs, validatedState.request(), extensionIds);
- }
+ plan = requestPlanner.plan(inputs, catalogData, catalogTimeout);
} catch (ValidationException validationException) {
return extensionValidationFailure(validationException, catalogData, diagnostics);
} catch (Exception exception) {
@@ -146,9 +126,9 @@ public int run(GenerateCommand command, boolean globalDryRun, boolean verbose) {
}
if (command.dryRun() || globalDryRun) {
- return handleDryRun(inputs, validatedState.request(), extensionIds, catalogData, diagnostics);
+ return handleDryRun(inputs, plan.request(), plan.extensionIds(), catalogData, diagnostics);
}
- return executeGeneration(inputs, validatedState.request(), extensionIds, diagnostics);
+ return executeGeneration(inputs, plan.request(), plan.extensionIds(), diagnostics);
}
private CatalogData loadCatalogData(Duration catalogTimeout, DiagnosticLogger diagnostics)
@@ -162,23 +142,6 @@ private CatalogData loadCatalogData(Duration catalogTimeout, DiagnosticLogger di
return catalogData;
}
- private static ForgeUiState resolveValidatedState(
- HeadlessGenerationInputs inputs, CatalogData catalogData) {
- MetadataCompatibilityContext metadataCompatibility =
- MetadataCompatibilityContext.success(catalogData.metadata());
- return InputResolutionService.resolveInitialState(inputs.template(), metadataCompatibility);
- }
-
- private static int validationFailure(
- ValidationReport validation,
- String sourceLabel,
- String detailMessage,
- DiagnosticLogger diagnostics) {
- diagnostics.error("generate.validation.failed", of("errorCount", validation.errors().size()));
- HeadlessOutputPrinter.printValidationErrors(validation, sourceLabel, detailMessage);
- return ExitCodes.VALIDATION;
- }
-
private static int extensionValidationFailure(
ValidationException validationException,
CatalogData catalogData,
diff --git a/src/main/java/dev/ayagmar/quarkusforge/headless/HeadlessRequestPlanner.java b/src/main/java/dev/ayagmar/quarkusforge/headless/HeadlessRequestPlanner.java
new file mode 100644
index 00000000..e91c13b0
--- /dev/null
+++ b/src/main/java/dev/ayagmar/quarkusforge/headless/HeadlessRequestPlanner.java
@@ -0,0 +1,57 @@
+package dev.ayagmar.quarkusforge.headless;
+
+import dev.ayagmar.quarkusforge.api.CatalogData;
+import dev.ayagmar.quarkusforge.application.InputResolutionService;
+import dev.ayagmar.quarkusforge.domain.ForgeUiState;
+import dev.ayagmar.quarkusforge.domain.MetadataCompatibilityContext;
+import dev.ayagmar.quarkusforge.domain.ProjectRequest;
+import java.time.Duration;
+import java.util.List;
+import java.util.Objects;
+
+final class HeadlessRequestPlanner {
+ private final HeadlessExtensionResolutionService extensionResolutionService;
+ private final HeadlessForgefilePersistenceService forgefilePersistenceService;
+
+ HeadlessRequestPlanner(
+ HeadlessExtensionResolutionService extensionResolutionService,
+ HeadlessForgefilePersistenceService forgefilePersistenceService) {
+ this.extensionResolutionService = Objects.requireNonNull(extensionResolutionService);
+ this.forgefilePersistenceService = Objects.requireNonNull(forgefilePersistenceService);
+ }
+
+ HeadlessGenerationPlan plan(
+ HeadlessGenerationInputs inputs, CatalogData catalogData, Duration catalogTimeout)
+ throws Exception {
+ ForgeUiState validatedState = resolveValidatedState(inputs, catalogData);
+ if (!validatedState.canSubmit()) {
+ throw new ValidationException(validatedState.validation().errors());
+ }
+
+ List extensionIds =
+ extensionResolutionService.resolveExtensionIds(
+ validatedState.request().platformStream(),
+ inputs.extensionInputs(),
+ inputs.presetInputs(),
+ HeadlessExtensionResolutionService.knownExtensionIds(catalogData),
+ catalogTimeout);
+ if (inputs.lockCheck()) {
+ forgefilePersistenceService.validateLockDrift(inputs, validatedState.request(), extensionIds);
+ }
+ return new HeadlessGenerationPlan(validatedState.request(), extensionIds);
+ }
+
+ private static ForgeUiState resolveValidatedState(
+ HeadlessGenerationInputs inputs, CatalogData catalogData) {
+ MetadataCompatibilityContext metadataCompatibility =
+ MetadataCompatibilityContext.success(catalogData.metadata());
+ return InputResolutionService.resolveInitialState(inputs.template(), metadataCompatibility);
+ }
+
+ record HeadlessGenerationPlan(ProjectRequest request, List extensionIds) {
+ HeadlessGenerationPlan {
+ Objects.requireNonNull(request);
+ extensionIds = List.copyOf(extensionIds);
+ }
+ }
+}
diff --git a/src/main/java/dev/ayagmar/quarkusforge/runtime/LoopbackHosts.java b/src/main/java/dev/ayagmar/quarkusforge/runtime/LoopbackHosts.java
new file mode 100644
index 00000000..085a28cc
--- /dev/null
+++ b/src/main/java/dev/ayagmar/quarkusforge/runtime/LoopbackHosts.java
@@ -0,0 +1,61 @@
+package dev.ayagmar.quarkusforge.runtime;
+
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Locale;
+
+final class LoopbackHosts {
+ private LoopbackHosts() {}
+
+ static boolean isLoopbackHost(String host) {
+ String normalizedHost = host.toLowerCase(Locale.ROOT);
+ if (normalizedHost.startsWith("[") && normalizedHost.endsWith("]")) {
+ normalizedHost = normalizedHost.substring(1, normalizedHost.length() - 1);
+ }
+ return normalizedHost.equals("localhost")
+ || normalizedHost.equals("::1")
+ || isIpv4LoopbackHost(normalizedHost)
+ || resolvesToLoopbackAddress(normalizedHost);
+ }
+
+ static boolean isIpv4LoopbackHost(String host) {
+ List octets = List.of(host.split("\\.", -1));
+ if (octets.size() != 4 || !octets.getFirst().equals("127")) {
+ return false;
+ }
+ for (int index = 1; index < octets.size(); index++) {
+ if (!isIpv4Octet(octets.get(index))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean isIpv4Octet(String value) {
+ if (value.isEmpty()) {
+ return false;
+ }
+ for (int index = 0; index < value.length(); index++) {
+ if (!Character.isDigit(value.charAt(index))) {
+ return false;
+ }
+ }
+ try {
+ int octet = Integer.parseInt(value);
+ return octet >= 0 && octet <= 255;
+ } catch (NumberFormatException ignored) {
+ return false;
+ }
+ }
+
+ private static boolean resolvesToLoopbackAddress(String host) {
+ if (!host.contains(":")) {
+ return false;
+ }
+ try {
+ return InetAddress.getByName(host).isLoopbackAddress();
+ } catch (RuntimeException | java.net.UnknownHostException ignored) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/dev/ayagmar/quarkusforge/runtime/RuntimeConfig.java b/src/main/java/dev/ayagmar/quarkusforge/runtime/RuntimeConfig.java
index 57924a3c..e47a8ac3 100644
--- a/src/main/java/dev/ayagmar/quarkusforge/runtime/RuntimeConfig.java
+++ b/src/main/java/dev/ayagmar/quarkusforge/runtime/RuntimeConfig.java
@@ -2,7 +2,6 @@
import dev.ayagmar.quarkusforge.api.CatalogSnapshotCache;
import dev.ayagmar.quarkusforge.api.ForgeDataPaths;
-import java.net.InetAddress;
import java.net.URI;
import java.nio.file.Path;
import java.util.Locale;
@@ -57,62 +56,10 @@ private static URI validatedApiBaseUri(URI apiBaseUri) {
if (host == null || host.isBlank()) {
throw new IllegalArgumentException("apiBaseUri must include a host");
}
- if (normalizedScheme.equals("http") && !isLoopbackHost(host)) {
+ if (normalizedScheme.equals("http") && !LoopbackHosts.isLoopbackHost(host)) {
throw new IllegalArgumentException(
"apiBaseUri must use https unless it targets localhost or a loopback address");
}
return apiBaseUri.normalize();
}
-
- private static boolean isLoopbackHost(String host) {
- String normalizedHost = host.toLowerCase(Locale.ROOT);
- if (normalizedHost.startsWith("[") && normalizedHost.endsWith("]")) {
- normalizedHost = normalizedHost.substring(1, normalizedHost.length() - 1);
- }
- return normalizedHost.equals("localhost")
- || normalizedHost.equals("::1")
- || isIpv4LoopbackHost(normalizedHost)
- || resolvesToLoopbackAddress(normalizedHost);
- }
-
- private static boolean isIpv4LoopbackHost(String host) {
- String[] octets = host.split("\\.", -1);
- if (octets.length != 4 || !octets[0].equals("127")) {
- return false;
- }
- for (int index = 1; index < octets.length; index++) {
- if (!isIpv4Octet(octets[index])) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean resolvesToLoopbackAddress(String host) {
- if (!host.contains(":")) {
- return false;
- }
- try {
- return InetAddress.getByName(host).isLoopbackAddress();
- } catch (RuntimeException | java.net.UnknownHostException ignored) {
- return false;
- }
- }
-
- private static boolean isIpv4Octet(String value) {
- if (value.isEmpty()) {
- return false;
- }
- for (int index = 0; index < value.length(); index++) {
- if (!Character.isDigit(value.charAt(index))) {
- return false;
- }
- }
- try {
- int octet = Integer.parseInt(value);
- return octet >= 0 && octet <= 255;
- } catch (NumberFormatException ignored) {
- return false;
- }
- }
}
diff --git a/src/main/java/dev/ayagmar/quarkusforge/runtime/TerminalBackendPreference.java b/src/main/java/dev/ayagmar/quarkusforge/runtime/TerminalBackendPreference.java
new file mode 100644
index 00000000..57d36d6c
--- /dev/null
+++ b/src/main/java/dev/ayagmar/quarkusforge/runtime/TerminalBackendPreference.java
@@ -0,0 +1,60 @@
+package dev.ayagmar.quarkusforge.runtime;
+
+import java.util.Locale;
+
+final class TerminalBackendPreference {
+ private static final String BACKEND_PROPERTY_NAME = "tamboui.backend";
+ private static final String BACKEND_ENV_NAME = "TAMBOUI_BACKEND";
+ private static final String PANAMA_BACKEND = "panama";
+ private static final String JLINE3_BACKEND = "jline3";
+
+ private TerminalBackendPreference() {}
+
+ static String defaultBackendPreference() {
+ return defaultBackendPreference(System.getProperty("os.name", ""));
+ }
+
+ static void configure() {
+ configure(
+ System.getProperty(BACKEND_PROPERTY_NAME),
+ System.getenv(BACKEND_ENV_NAME),
+ System.getProperty("os.name", ""));
+ }
+
+ static void configure(String propertyValue, String envValue, String osName) {
+ if (isExplicitlyConfigured(propertyValue, envValue)) {
+ return;
+ }
+ System.setProperty(BACKEND_PROPERTY_NAME, defaultBackendPreference(osName));
+ }
+
+ static boolean isExplicitlyConfigured(String propertyValue, String envValue) {
+ if (propertyValue != null && !propertyValue.isBlank()) {
+ return true;
+ }
+ return envValue != null && !envValue.isBlank();
+ }
+
+ static String defaultBackendPreference(String osName) {
+ if (isWindowsOsName(osName)) {
+ return JLINE3_BACKEND;
+ }
+ return PANAMA_BACKEND;
+ }
+
+ static void restore(String previousBackendPreference) {
+ if (previousBackendPreference == null) {
+ System.clearProperty(BACKEND_PROPERTY_NAME);
+ return;
+ }
+ System.setProperty(BACKEND_PROPERTY_NAME, previousBackendPreference);
+ }
+
+ private static boolean isWindowsOsName(String osName) {
+ if (osName == null) {
+ return false;
+ }
+ String normalized = osName.strip().toLowerCase(Locale.ROOT);
+ return normalized.startsWith("windows");
+ }
+}
diff --git a/src/main/java/dev/ayagmar/quarkusforge/runtime/TuiBootstrapService.java b/src/main/java/dev/ayagmar/quarkusforge/runtime/TuiBootstrapService.java
index ba6e83d0..9bc21797 100644
--- a/src/main/java/dev/ayagmar/quarkusforge/runtime/TuiBootstrapService.java
+++ b/src/main/java/dev/ayagmar/quarkusforge/runtime/TuiBootstrapService.java
@@ -22,7 +22,6 @@
import dev.tamboui.tui.bindings.Bindings;
import java.time.Duration;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -30,9 +29,6 @@
public final class TuiBootstrapService {
private static final String BACKEND_PROPERTY_NAME = "tamboui.backend";
- private static final String BACKEND_ENV_NAME = "TAMBOUI_BACKEND";
- private static final String PANAMA_BACKEND = "panama";
- private static final String JLINE3_BACKEND = "jline3";
private static final ReentrantLock BACKEND_PROPERTY_LOCK = new ReentrantLock();
public static final Duration STARTUP_SPLASH_MIN_DURATION = Duration.ofMillis(450);
private static final Duration TUI_TICK_RATE = Duration.ofMillis(40);
@@ -62,45 +58,7 @@ public static TuiConfig appTuiConfig() {
}
public static String defaultBackendPreference() {
- return defaultBackendPreference(System.getProperty("os.name", ""));
- }
-
- private static void configureTerminalBackendPreference() {
- configureTerminalBackendPreference(
- System.getProperty(BACKEND_PROPERTY_NAME),
- System.getenv(BACKEND_ENV_NAME),
- System.getProperty("os.name", ""));
- }
-
- private static void configureTerminalBackendPreference(
- String propertyValue, String envValue, String osName) {
- if (isBackendPreferenceExplicitlyConfigured(propertyValue, envValue)) {
- return;
- }
- System.setProperty(BACKEND_PROPERTY_NAME, defaultBackendPreference(osName));
- }
-
- private static boolean isBackendPreferenceExplicitlyConfigured(
- String propertyValue, String envValue) {
- if (propertyValue != null && !propertyValue.isBlank()) {
- return true;
- }
- return envValue != null && !envValue.isBlank();
- }
-
- private static String defaultBackendPreference(String osName) {
- if (isWindowsOsName(osName)) {
- return JLINE3_BACKEND;
- }
- return PANAMA_BACKEND;
- }
-
- private static boolean isWindowsOsName(String osName) {
- if (osName == null) {
- return false;
- }
- String normalized = osName.strip().toLowerCase(Locale.ROOT);
- return normalized.startsWith("windows");
+ return TerminalBackendPreference.defaultBackendPreference();
}
public static void runHeadlessSmoke(RuntimeConfig runtimeConfig, DiagnosticLogger diagnostics) {
@@ -122,7 +80,7 @@ public void runInteractiveSmoke(
BACKEND_PROPERTY_LOCK.lock();
String previousBackendPreference = System.getProperty(BACKEND_PROPERTY_NAME);
try {
- configureTerminalBackendPreference();
+ TerminalBackendPreference.configure();
TuiConfig tuiConfig = appTuiConfig();
try (var tui = TuiRunner.create(tuiConfig);
RuntimeServices runtimeServices = RuntimeServices.open(runtimeConfig)) {
@@ -159,7 +117,7 @@ public void runInteractiveSmoke(
}
} finally {
try {
- restoreTerminalBackendPreference(previousBackendPreference);
+ TerminalBackendPreference.restore(previousBackendPreference);
} finally {
BACKEND_PROPERTY_LOCK.unlock();
}
@@ -179,7 +137,7 @@ public TuiSessionSummary run(
BACKEND_PROPERTY_LOCK.lock();
String previousBackendPreference = System.getProperty(BACKEND_PROPERTY_NAME);
try {
- configureTerminalBackendPreference();
+ TerminalBackendPreference.configure();
TuiConfig tuiConfig = appTuiConfig();
try (var tui = TuiRunner.create(tuiConfig);
RuntimeServices runtimeServices = RuntimeServices.open(runtimeConfig)) {
@@ -224,7 +182,7 @@ public TuiSessionSummary run(
}
} finally {
try {
- restoreTerminalBackendPreference(previousBackendPreference);
+ TerminalBackendPreference.restore(previousBackendPreference);
} finally {
BACKEND_PROPERTY_LOCK.unlock();
}
@@ -345,14 +303,6 @@ void runInteractiveSmokeSession(
sessionLoop);
}
- private static void restoreTerminalBackendPreference(String previousBackendPreference) {
- if (previousBackendPreference == null) {
- System.clearProperty(BACKEND_PROPERTY_NAME);
- return;
- }
- System.setProperty(BACKEND_PROPERTY_NAME, previousBackendPreference);
- }
-
private static String resolvePresetStreamKey(
String currentStreamKey, dev.ayagmar.quarkusforge.api.MetadataDto metadata) {
if (currentStreamKey != null && !currentStreamKey.isBlank()) {
diff --git a/src/main/resources/META-INF/native-image/dev.ayagmar/quarkusforge/reflect-config.json b/src/main/resources/META-INF/native-image/dev.ayagmar/quarkusforge/reflect-config.json
new file mode 100644
index 00000000..f294f9ea
--- /dev/null
+++ b/src/main/resources/META-INF/native-image/dev.ayagmar/quarkusforge/reflect-config.json
@@ -0,0 +1,52 @@
+[
+ {
+ "name": "dev.ayagmar.quarkusforge.cli.GenerateCommand",
+ "fields": [
+ { "name": "dryRun" },
+ { "name": "extensions" },
+ { "name": "fromFile" },
+ { "name": "lock" },
+ { "name": "lockCheck" },
+ { "name": "presets" },
+ { "name": "saveAsFile" }
+ ]
+ },
+ {
+ "name": "dev.ayagmar.quarkusforge.cli.HeadlessCli",
+ "fields": [
+ { "name": "dryRun" },
+ { "name": "verbose" }
+ ]
+ },
+ {
+ "name": "dev.ayagmar.quarkusforge.cli.QuarkusForgeCli",
+ "fields": [
+ { "name": "dryRun" },
+ { "name": "interactiveSmokeTest" },
+ { "name": "postGenerateHookCommand" },
+ { "name": "searchDebounceMs" },
+ { "name": "verbose" }
+ ]
+ },
+ {
+ "name": "dev.ayagmar.quarkusforge.cli.RequestOptions",
+ "fields": [
+ { "name": "artifactId" },
+ { "name": "buildTool" },
+ { "name": "groupId" },
+ { "name": "javaVersion" },
+ { "name": "outputDirectory" },
+ { "name": "packageName" },
+ { "name": "platformStream" },
+ { "name": "version" }
+ ]
+ },
+ {
+ "name": "picocli.CommandLine$AutoHelpMixin",
+ "allDeclaredConstructors": true,
+ "fields": [
+ { "name": "helpRequested" },
+ { "name": "versionRequested" }
+ ]
+ }
+]
diff --git a/src/main/resources/META-INF/native-image/dev.ayagmar/quarkusforge/resource-config.json b/src/main/resources/META-INF/native-image/dev.ayagmar/quarkusforge/resource-config.json
index 574975d5..9dac0c9a 100644
--- a/src/main/resources/META-INF/native-image/dev.ayagmar/quarkusforge/resource-config.json
+++ b/src/main/resources/META-INF/native-image/dev.ayagmar/quarkusforge/resource-config.json
@@ -9,9 +9,6 @@
},
{
"pattern": "${native.tui.bindings.resource.pattern}"
- },
- {
- "pattern": "version\\.properties$"
}
]
}
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
deleted file mode 100644
index defbd482..00000000
--- a/src/main/resources/version.properties
+++ /dev/null
@@ -1 +0,0 @@
-version=${project.version}
diff --git a/src/test/java/dev/ayagmar/quarkusforge/TuiBootstrapServiceTest.java b/src/test/java/dev/ayagmar/quarkusforge/TuiBootstrapServiceTest.java
index f15de626..30e1f006 100644
--- a/src/test/java/dev/ayagmar/quarkusforge/TuiBootstrapServiceTest.java
+++ b/src/test/java/dev/ayagmar/quarkusforge/TuiBootstrapServiceTest.java
@@ -203,15 +203,14 @@ void runHeadlessSmokePropagatesCatalogLoadFailure() {
private static void invokeConfigureTerminalBackendPreference(
String propertyValue, String envValue, String osName) throws Exception {
Method method =
- TuiBootstrapService.class.getDeclaredMethod(
- "configureTerminalBackendPreference", String.class, String.class, String.class);
+ backendPreferenceClass()
+ .getDeclaredMethod("configure", String.class, String.class, String.class);
method.setAccessible(true);
method.invoke(null, propertyValue, envValue, osName);
}
private static void invokeConfigureTerminalBackendPreference() throws Exception {
- Method method =
- TuiBootstrapService.class.getDeclaredMethod("configureTerminalBackendPreference");
+ Method method = backendPreferenceClass().getDeclaredMethod("configure");
method.setAccessible(true);
method.invoke(null);
}
@@ -219,15 +218,15 @@ private static void invokeConfigureTerminalBackendPreference() throws Exception
private static boolean invokeIsBackendPreferenceExplicitlyConfigured(
String propertyValue, String envValue) throws Exception {
Method method =
- TuiBootstrapService.class.getDeclaredMethod(
- "isBackendPreferenceExplicitlyConfigured", String.class, String.class);
+ backendPreferenceClass()
+ .getDeclaredMethod("isExplicitlyConfigured", String.class, String.class);
method.setAccessible(true);
return (boolean) method.invoke(null, propertyValue, envValue);
}
private static String invokeDefaultBackendPreference(String osName) throws Exception {
Method method =
- TuiBootstrapService.class.getDeclaredMethod("defaultBackendPreference", String.class);
+ backendPreferenceClass().getDeclaredMethod("defaultBackendPreference", String.class);
method.setAccessible(true);
return (String) method.invoke(null, osName);
}
@@ -243,13 +242,15 @@ private static String invokeResolvePresetStreamKey(
private static void invokeRestoreTerminalBackendPreference(String previousBackendPreference)
throws Exception {
- Method method =
- TuiBootstrapService.class.getDeclaredMethod(
- "restoreTerminalBackendPreference", String.class);
+ Method method = backendPreferenceClass().getDeclaredMethod("restore", String.class);
method.setAccessible(true);
method.invoke(null, previousBackendPreference);
}
+ private static Class> backendPreferenceClass() throws ClassNotFoundException {
+ return Class.forName("dev.ayagmar.quarkusforge.runtime.TerminalBackendPreference");
+ }
+
private static String captureStandardError(ThrowingRunnable action) {
PrintStream originalErr = System.err;
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
diff --git a/src/test/java/dev/ayagmar/quarkusforge/cli/CliVersionProviderTest.java b/src/test/java/dev/ayagmar/quarkusforge/cli/CliVersionProviderTest.java
index 9c06e038..de40a956 100644
--- a/src/test/java/dev/ayagmar/quarkusforge/cli/CliVersionProviderTest.java
+++ b/src/test/java/dev/ayagmar/quarkusforge/cli/CliVersionProviderTest.java
@@ -13,20 +13,18 @@ void resolveVersionReturnsNonBlankValue() {
}
@Test
- void resolveVersionFallsBackToResourceOrUnknown() {
- // In test environment, implementation version from manifest is typically null,
- // so it falls back to version.properties resource or "unknown"
+ void resolveVersionFallsBackToUnknownWhenImplementationVersionIsUnavailable() {
String version = CliVersionProvider.resolveVersion();
assertThat(version).isNotNull();
- // Must be a version string or "unknown"
assertThat(version).matches("(\\d+\\.\\d+\\.\\d+.*|unknown)");
}
@Test
- void getVersionReturnsNonEmptyArray() {
- CliVersionProvider provider = new CliVersionProvider();
- String[] versions = provider.getVersion();
- assertThat(versions).hasSize(1);
- assertThat(versions[0]).isNotBlank();
+ void resolveVersionIsStableAcrossRepeatedCalls() {
+ String first = CliVersionProvider.resolveVersion();
+ String second = CliVersionProvider.resolveVersion();
+
+ assertThat(first).isEqualTo(second);
+ assertThat(first).isNotBlank();
}
}
diff --git a/src/test/java/dev/ayagmar/quarkusforge/cli/HeadlessCliTest.java b/src/test/java/dev/ayagmar/quarkusforge/cli/HeadlessCliTest.java
index f3802127..e67d7b9a 100644
--- a/src/test/java/dev/ayagmar/quarkusforge/cli/HeadlessCliTest.java
+++ b/src/test/java/dev/ayagmar/quarkusforge/cli/HeadlessCliTest.java
@@ -41,7 +41,7 @@ void versionFlagExitsCleanly() {
CliCommandTestSupport.runHeadlessCommand(config, "--version");
assertThat(result.exitCode()).isEqualTo(ExitCodes.OK);
- assertThat(result.standardOut()).containsPattern("\\d+\\.\\d+");
+ assertThat(result.standardOut()).contains(CliVersionProvider.resolveVersion());
}
@Test
diff --git a/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliPrefillTest.java b/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliPrefillTest.java
index 86ca1e2b..6620d945 100644
--- a/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliPrefillTest.java
+++ b/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliPrefillTest.java
@@ -9,7 +9,6 @@
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import picocli.CommandLine;
/**
* Tests verifying that {@link QuarkusForgeCli} correctly detects explicitly-provided CLI options
@@ -24,12 +23,14 @@ class QuarkusForgeCliPrefillTest {
@TempDir Path tempDir;
- // ── isExplicitlySet with real Picocli @Spec injection ─────────────────────
+ // ── Explicit-option detection from recorded Picocli parse results ─────────────────────
@Test
void picocliSpecDetectsOptionThatWasExplicitlyProvided() {
QuarkusForgeCli cli = new QuarkusForgeCli();
- new CommandLine(cli).parseArgs("--dry-run", "--group-id", "com.example");
+ cli.requestOptions()
+ .recordMatchedOptions(
+ CliCommandLineFactory.create(cli).parseArgs("--dry-run", "--group-id", "com.example"));
assertThat(
cli.requestOptions()
@@ -42,7 +43,8 @@ void picocliSpecDetectsOptionThatWasExplicitlyProvided() {
@Test
void picocliSpecDetectsOptionThatWasNotProvided() {
QuarkusForgeCli cli = new QuarkusForgeCli();
- new CommandLine(cli).parseArgs("--dry-run");
+ cli.requestOptions()
+ .recordMatchedOptions(CliCommandLineFactory.create(cli).parseArgs("--dry-run"));
// --group-id was NOT provided; value equals the default
assertThat(
@@ -60,7 +62,10 @@ void picocliSpecDetectsExplicitDefaultEqualValue() {
// This is the core bug scenario: user passes --group-id org.acme (same as default)
// and the system must recognise it as explicit input, NOT as "not provided".
QuarkusForgeCli cli = new QuarkusForgeCli();
- new CommandLine(cli).parseArgs("--dry-run", "--group-id", RequestOptions.DEFAULT_GROUP_ID);
+ cli.requestOptions()
+ .recordMatchedOptions(
+ CliCommandLineFactory.create(cli)
+ .parseArgs("--dry-run", "--group-id", RequestOptions.DEFAULT_GROUP_ID));
assertThat(
cli.requestOptions()
@@ -75,8 +80,10 @@ void picocliSpecDetectsExplicitDefaultEqualValue() {
@Test
void picocliSpecDetectsMultipleExplicitOptions() {
QuarkusForgeCli cli = new QuarkusForgeCli();
- new CommandLine(cli)
- .parseArgs("--dry-run", "--group-id", "com.example", "--java-version", "21");
+ cli.requestOptions()
+ .recordMatchedOptions(
+ CliCommandLineFactory.create(cli)
+ .parseArgs("--dry-run", "--group-id", "com.example", "--java-version", "21"));
assertThat(
cli.requestOptions()
@@ -103,7 +110,10 @@ void picocliSpecDetectsMultipleExplicitOptions() {
@Test
void explicitCliPrefillRetainsExplicitDefaultEqualGroupId() {
QuarkusForgeCli cli = new QuarkusForgeCli();
- new CommandLine(cli).parseArgs("--dry-run", "--group-id", RequestOptions.DEFAULT_GROUP_ID);
+ cli.requestOptions()
+ .recordMatchedOptions(
+ CliCommandLineFactory.create(cli)
+ .parseArgs("--dry-run", "--group-id", RequestOptions.DEFAULT_GROUP_ID));
assertThat(cli.requestOptions().toExplicitCliPrefill().groupId())
.as("Explicit --group-id must remain explicit even when it equals the default")
@@ -113,7 +123,8 @@ void explicitCliPrefillRetainsExplicitDefaultEqualGroupId() {
@Test
void explicitCliPrefillLeavesOmittedDefaultsUnset() {
QuarkusForgeCli cli = new QuarkusForgeCli();
- new CommandLine(cli).parseArgs("--dry-run");
+ cli.requestOptions()
+ .recordMatchedOptions(CliCommandLineFactory.create(cli).parseArgs("--dry-run"));
CliPrefill explicitPrefill = cli.requestOptions().toExplicitCliPrefill();
@@ -126,8 +137,10 @@ void explicitCliPrefillLeavesOmittedDefaultsUnset() {
@Test
void explicitCliPrefillPreservesMultipleExplicitOptions() {
QuarkusForgeCli cli = new QuarkusForgeCli();
- new CommandLine(cli)
- .parseArgs("--dry-run", "--group-id", "com.example", "--java-version", "21");
+ cli.requestOptions()
+ .recordMatchedOptions(
+ CliCommandLineFactory.create(cli)
+ .parseArgs("--dry-run", "--group-id", "com.example", "--java-version", "21"));
CliPrefill explicitPrefill = cli.requestOptions().toExplicitCliPrefill();
diff --git a/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliStartupMetadataTest.java b/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliStartupMetadataTest.java
index 2e6308e7..fc2908ba 100644
--- a/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliStartupMetadataTest.java
+++ b/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliStartupMetadataTest.java
@@ -14,7 +14,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import picocli.CommandLine;
class QuarkusForgeCliStartupMetadataTest {
@TempDir Path tempDir;
@@ -275,7 +274,8 @@ private CliCommandTestSupport.CommandResult runSmoke(
private CliCommandTestSupport.CommandResult runCommand(QuarkusForgeCli cli, String... args) {
try {
- return CliCommandTestSupport.captureCommandOutput(() -> new CommandLine(cli).execute(args));
+ return CliCommandTestSupport.captureCommandOutput(
+ () -> CliCommandLineFactory.create(cli).execute(args));
} catch (RuntimeException runtimeException) {
throw runtimeException;
} catch (Exception exception) {
diff --git a/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliTest.java b/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliTest.java
index b9d354b3..a1da1e53 100644
--- a/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliTest.java
+++ b/src/test/java/dev/ayagmar/quarkusforge/cli/QuarkusForgeCliTest.java
@@ -18,7 +18,6 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
-import picocli.CommandLine;
class QuarkusForgeCliTest {
private static final MetadataDto METADATA =
@@ -139,7 +138,7 @@ void dryRunDelegatesExplicitOverridesToStartupService() throws Exception {
CliCommandTestSupport.CommandResult result =
CliCommandTestSupport.captureCommandOutput(
() ->
- new CommandLine(cli)
+ CliCommandLineFactory.create(cli)
.execute(
"--dry-run",
"--group-id",
diff --git a/src/test/java/dev/ayagmar/quarkusforge/runtime/RuntimeConfigTest.java b/src/test/java/dev/ayagmar/quarkusforge/runtime/RuntimeConfigTest.java
index b7089d10..19103ae6 100644
--- a/src/test/java/dev/ayagmar/quarkusforge/runtime/RuntimeConfigTest.java
+++ b/src/test/java/dev/ayagmar/quarkusforge/runtime/RuntimeConfigTest.java
@@ -203,7 +203,7 @@ void normalizesApiBaseUriAndFilePaths() {
}
private static boolean invokeBooleanHelper(String methodName, String value) throws Exception {
- Method method = RuntimeConfig.class.getDeclaredMethod(methodName, String.class);
+ Method method = LoopbackHosts.class.getDeclaredMethod(methodName, String.class);
method.setAccessible(true);
return (boolean) method.invoke(null, value);
}