From 2e6e1a39a912be069cf6908008d819cb264be405 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Fri, 18 Oct 2024 16:48:41 +1100 Subject: [PATCH] Allow adding and removing extensions from Dev UI Signed-off-by: Phillip Kruger --- extensions/vertx-http/deployment/pom.xml | 12 +- .../deployment/menu/ExtensionsProcessor.java | 170 +++++++- .../resources/dev-ui/qwc/qwc-configuration.js | 6 +- .../resources/dev-ui/qwc/qwc-extension-add.js | 371 ++++++++++++++++++ .../resources/dev-ui/qwc/qwc-extension.js | 32 +- .../resources/dev-ui/qwc/qwc-extensions.js | 86 +++- .../commands/data/QuarkusCommandOutcome.java | 23 +- .../ListCategoriesCommandHandler.java | 21 +- .../ListExtensionsCommandHandler.java | 38 +- .../project/QuarkusProjectHelper.java | 31 +- 10 files changed, 760 insertions(+), 30 deletions(-) create mode 100644 extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension-add.js diff --git a/extensions/vertx-http/deployment/pom.xml b/extensions/vertx-http/deployment/pom.xml index 2ba139c19f25c..464a4237a9d91 100644 --- a/extensions/vertx-http/deployment/pom.xml +++ b/extensions/vertx-http/deployment/pom.xml @@ -76,7 +76,17 @@ com.fasterxml.jackson.datatype jackson-datatype-jdk8 - + + io.quarkus + quarkus-devtools-common + + + org.apache.maven.resolver + maven-resolver-connector-basic + + + + io.quarkus diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ExtensionsProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ExtensionsProcessor.java index 856d8d0fbf440..f3aed23d6332e 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ExtensionsProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ExtensionsProcessor.java @@ -1,14 +1,31 @@ package io.quarkus.devui.deployment.menu; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.dev.spi.DevModeType; +import io.quarkus.devtools.commands.AddExtensions; +import io.quarkus.devtools.commands.ListCategories; +import io.quarkus.devtools.commands.ListExtensions; +import io.quarkus.devtools.commands.RemoveExtensions; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.devui.deployment.ExtensionsBuildItem; import io.quarkus.devui.deployment.InternalPageBuildItem; import io.quarkus.devui.deployment.extension.Extension; import io.quarkus.devui.deployment.extension.ExtensionGroup; +import io.quarkus.devui.spi.buildtime.BuildTimeActionBuildItem; import io.quarkus.devui.spi.page.Page; /** @@ -30,11 +47,160 @@ InternalPageBuildItem createExtensionsPages(ExtensionsBuildItem extensionsBuildI // Page extensionsPages.addPage(Page.webComponentPageBuilder() - .namespace("devui-extensions") + .namespace(NAMESPACE) .title("Extensions") .icon("font-awesome-solid:puzzle-piece") .componentLink("qwc-extensions.js")); return extensionsPages; } -} \ No newline at end of file + + @BuildStep(onlyIf = IsDevelopment.class) + void createBuildTimeActions(BuildProducer buildTimeActionProducer, + LaunchModeBuildItem launchModeBuildItem) { + + if (launchModeBuildItem.getDevModeType().isPresent() + && launchModeBuildItem.getDevModeType().get().equals(DevModeType.LOCAL)) { + + BuildTimeActionBuildItem buildTimeActions = new BuildTimeActionBuildItem(NAMESPACE); + + getCategories(buildTimeActions); + getInstallableExtensions(buildTimeActions); + getInstalledNamespaces(buildTimeActions); + removeExtension(buildTimeActions); + addExtension(buildTimeActions); + buildTimeActionProducer.produce(buildTimeActions); + } + } + + private void getCategories(BuildTimeActionBuildItem buildTimeActions) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), ignored -> { + return CompletableFuture.supplyAsync(() -> { + try { + QuarkusCommandOutcome outcome = new ListCategories(getQuarkusProject()) + .format("object") + .execute(); + + if (outcome.isSuccess()) { + return outcome.getResult(); + } + } catch (QuarkusCommandException ex) { + throw new RuntimeException(ex); + } + return null; + }); + }); + } + + private void getInstallableExtensions(BuildTimeActionBuildItem buildTimeActions) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), ignored -> { + return CompletableFuture.supplyAsync(() -> { + try { + QuarkusCommandOutcome outcome = new ListExtensions(getQuarkusProject()) + .installed(false) + .all(false) + .format("object") + .execute(); + + if (outcome.isSuccess()) { + return outcome.getResult(); + } + + return null; + } catch (QuarkusCommandException e) { + throw new RuntimeException(e); + } + }); + }); + } + + private void getInstalledNamespaces(BuildTimeActionBuildItem buildTimeActions) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), ignored -> { + return CompletableFuture.supplyAsync(() -> { + try { + QuarkusCommandOutcome outcome = new ListExtensions(getQuarkusProject()) + .installed(true) + .all(false) + .format("object") + .execute(); + + if (outcome.isSuccess()) { + + List extensionList = (List) outcome + .getResult(); + + List namespaceList = new ArrayList<>(); + + if (!extensionList.isEmpty()) { + for (io.quarkus.registry.catalog.Extension e : extensionList) { + String groupId = e.getArtifact().getGroupId(); + String artifactId = e.getArtifact().getArtifactId(); + namespaceList.add(groupId + "." + artifactId); + } + } + return namespaceList; + } + + return null; + } catch (QuarkusCommandException e) { + throw new RuntimeException(e); + } + }); + }); + } + + private void removeExtension(BuildTimeActionBuildItem buildTimeActions) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), params -> { + return CompletableFuture.supplyAsync(() -> { + String extensionArtifactId = params.get("extensionArtifactId"); + try { + QuarkusCommandOutcome outcome = new RemoveExtensions(getQuarkusProject()) + .extensions(Set.of(extensionArtifactId)) + .execute(); + + if (outcome.isSuccess()) { + return true; + } else { + return false; + } + } catch (QuarkusCommandException e) { + throw new RuntimeException(e); + } + }); + }); + } + + private void addExtension(BuildTimeActionBuildItem buildTimeActions) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), params -> { + return CompletableFuture.supplyAsync(() -> { + String extensionArtifactId = params.get("extensionArtifactId"); + + try { + QuarkusCommandOutcome outcome = new AddExtensions(getQuarkusProject()) + .extensions(Set.of(extensionArtifactId)) + .execute(); + + if (outcome.isSuccess()) { + return true; + } else { + return false; + } + } catch (QuarkusCommandException e) { + throw new RuntimeException(e); + } + }); + }); + } + + private QuarkusProject getQuarkusProject() { + Path projectRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath(); + return QuarkusProjectHelper.getCachedProject(projectRoot); + } + + private static final String NAMESPACE = "devui-extensions"; +} diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js index ee3729fd0beba..ae022d1abe8c4 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js @@ -252,9 +252,9 @@ export class QwcConfiguration extends observeState(LitElement) { return html` { + this._categories = jsonRpcResponse.result; + this._categories.push({name:'Uncategorised', id: 'uncategorised'}); + }); + + if(!this._extensions){ + this.hotReload(); + } + } + + hotReload(){ + this.jsonRpc.getInstallableExtensions().then(jsonRpcResponse => { + this._extensions = jsonRpcResponse.result; + this._filteredExtensions = this._extensions; + }); + } + + render() { + if(this._filteredExtensions){ + return html`
+ ${this._renderFilterBar()} + ${this._renderGrid()} +
`; + }else{ + return html`
+ + + + +
`; + } + } + + _renderGrid(){ + return html` + + + `; + } + + _renderFilterBar(){ + return html`
+ + + ${this._filteredExtensions.length} + + ${this._renderCategoryDropdown()} +
`; + + } + + _renderCategoryDropdown(){ + if(this._categories){ + return html``; + } + } + + _filterCategoryChanged(e){ + this._filteredCategory = (e.detail.value || '').trim(); + return this._filterGrid(); + } + + _filterTextChanged(e) { + this._filteredValue = (e.detail.value || '').trim(); + return this._filterGrid(); + } + + _filterGrid(){ + this._filteredExtensions = this._extensions.filter((prop) => { + if(this._filteredValue && this._filteredValue !== '' && this._filteredCategory && this._filteredCategory !== ''){ + return this._filterByTerm(prop) && this._filterByCategory(prop); + }else if(this._filteredValue && this._filteredValue !== ''){ + return this._filterByTerm(prop); + }else if(this._filteredCategory && this._filteredCategory !== ''){ + return this._filterByCategory(prop); + }else{ + return true; + } + }); + } + + _filterByTerm(prop){ + if(prop.metadata && prop.metadata.keywords){ + return this._match(prop.name, this._filteredValue) || this._match(prop.description, this._filteredValue) || prop.metadata.keywords.includes(this._filteredValue); + }else{ + return this._match(prop.name, this._filteredValue) || this._match(prop.description, this._filteredValue); + } + } + + _filterByCategory(prop){ + if(prop.metadata && prop.metadata.categories){ + return prop.metadata.categories.includes(this._filteredCategory); + }else if(this._filteredCategory === "uncategorised"){ + return true; + }else { + return false; + } + } + + _match(value, term) { + if (! value) { + return false; + } + + return value.toLowerCase().includes(term.toLowerCase()); + } + + _descriptionRenderer(prop) { + + return html`
+
+ Artifact: ${prop.artifact.groupId}:${prop.artifact.artifactId} + Version: ${prop.artifact.version} + ${this._renderIsPlatform(prop)} + ${this._renderMetadata1(prop)} +
+
+ ${this._renderMetadata2(prop)} +
+
+ + + Add Extension + + `; + } + + _renderMetadata1(prop){ + if(prop.metadata){ + return html`${this._renderGuide(prop.metadata)} + ${this._renderScmUrl(prop.metadata)} + ${this._renderStatus(prop.metadata)} + ${this._renderMinJavaVersion(prop.metadata)}`; + } + } + + _renderIsPlatform(prop){ + if (prop.origins && prop.origins.some(str => str.startsWith("io.quarkus:quarkus-bom-quarkus-platform"))){ + return html`Platform: `; + } else { + return html`Platform: `; + } + } + + _renderGuide(metadata){ + if (metadata.guide){ + return html`Guide: ${metadata.guide}`; + } + } + + _renderScmUrl(metadata){ + if (metadata['scm-url']){ + return html`SCM: ${metadata['scm-url']}`; + } + } + + _renderStatus(metadata){ + if(metadata.status){ + return html`Status: ${metadata.status.toUpperCase()}`; + } + } + + _renderMinJavaVersion(metadata){ + if(metadata['minimum-java-version']){ + return html`Minimum Java version: ${metadata['minimum-java-version']}`; + } + } + + _renderMetadata2(prop){ + if(prop.metadata){ + return html`${this._renderKeywords(prop.metadata)} + ${this._renderCategories(prop.metadata)} + ${this._renderExtensionDependencies(prop.metadata)}`; + } + } + + + + + + _statusLevel(s){ + if(s === "stable") { + return "success"; + } else if(s === "experimental") { + return "warning"; + } else if(s === "preview") { + return "contrast"; + } + return null; + } + + _renderCategories(metadata){ + if(metadata.categories){ + return this._renderList("Categories", metadata.categories); + } + } + + _renderKeywords(metadata){ + if(metadata.keywords){ + return this._renderList("Keywords", metadata.keywords); + } + } + + _renderExtensionDependencies(metadata){ + if(metadata['extension-dependencies']){ + return html` + + ${this._renderExtensionDependenciesLines(metadata['extension-dependencies'])} + + `; + } + } + + _renderExtensionDependenciesLines(lines){ + return html` + ${lines.map((line) => + html`${line}` + )} + `; + } + + _renderList(heading, list) { + return html`${heading}: ${this._renderListLines(list)}`; + } + + _renderListLines(list) { + return html` +
    + ${list.map((item) => + html`
  • ${item}
  • ` + )} +
+ `; + } + + _install(prop){ + let extensionArtifactId = prop.artifact.groupId + ':' + prop.artifact.artifactId; + this.jsonRpc.addExtension({extensionArtifactId:extensionArtifactId}).then(jsonRpcResponse => { + let outcome = jsonRpcResponse.result; + + const options = { + detail: {outcome: outcome, name: prop.name}, + bubbles: true, + composed: true, + }; + this.dispatchEvent(new CustomEvent('inprogress', options)); + + }); + } +} +customElements.define('qwc-extension-add', QwcExtensionAdd); diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js index 5c76cb0bfe8bd..7f719cca05077 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js @@ -1,14 +1,19 @@ import { LitElement, html, css} from 'lit'; +import { observeState } from 'lit-element-state'; import '@vaadin/icon'; import '@vaadin/dialog'; import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js'; import '@qomponent/qui-badge'; +import { JsonRpc } from 'jsonrpc'; +import { notifier } from 'notifier'; +import { connectionState } from 'connection-state'; /** * This component represent one extension * It's a card on the extension board */ -export class QwcExtension extends LitElement { +export class QwcExtension extends observeState(LitElement) { + jsonRpc = new JsonRpc("devui-extensions", false); static styles = css` .card { @@ -95,13 +100,15 @@ export class QwcExtension extends LitElement { builtWith: {type: String}, providesCapabilities: {}, extensionDependencies: {}, - favourite: {type: Boolean}, + favourite: {type: Boolean}, + installed: {type: Boolean}, }; constructor() { super(); this._dialogOpened = false; this.favourite = false; + this.installed = false; } render() { @@ -260,9 +267,30 @@ export class QwcExtension extends LitElement { ${this._renderExtensionDependencies()} + ${this._renderUninstallButton()} `; } + _renderUninstallButton(){ + if(connectionState.current.isConnected && this.installed){ + return html` + + Remove this extension + `; + } + } + + _uninstall(){ + this._dialogOpened = false; + notifier.showInfoMessage(this.name + " removal in progress"); + this.jsonRpc.removeExtension({extensionArtifactId:this.artifact}).then(jsonRpcResponse => { + let outcome = jsonRpcResponse.result; + if(!outcome){ + notifier.showErrorMessage(name + " removal failed"); + } + }); + } + _renderGuideDetails() { return this.guide ? html`${this.guide}` diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js index d555580dcaf02..14ef09471e3d0 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js @@ -5,7 +5,13 @@ import { devuiState } from 'devui-state'; import { observeState } from 'lit-element-state'; import 'qwc/qwc-extension.js'; import 'qwc/qwc-extension-link.js'; +import 'qwc/qwc-extension-add.js'; import { StorageController } from 'storage-controller'; +import '@vaadin/dialog'; +import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js'; +import { notifier } from 'notifier'; +import { connectionState } from 'connection-state'; +import { JsonRpc } from 'jsonrpc'; /** * This component create cards of all the extensions @@ -13,7 +19,7 @@ import { StorageController } from 'storage-controller'; export class QwcExtensions extends observeState(LitElement) { routerController = new RouterController(this); storageController = new StorageController(this); - + jsonRpc = new JsonRpc("devui-extensions", false); static styles = css` .grid { display: flex; @@ -49,22 +55,46 @@ export class QwcExtensions extends observeState(LitElement) { qwc-extension-link { cursor: grab; } + .addExtensionButton { + position: absolute; + bottom: 40px; + right: 40px; + width: 3em; + height: 3em; + box-shadow: var(--lumo-shade) 5px 5px 15px 3px; + } + .addExtensionIcon { + width: 2em; + height: 2em; + } `; static properties = { _favourites: {state: true}, + _addDialogOpened: {state: true}, + _installedExtensions: {state: true, type: Array}, } constructor() { super(); this._favourites = this._getStoredFavourites(); + this._addDialogOpened = false; + this._installedExtensions = []; + } + + connectedCallback() { + super.connectedCallback(); + this.jsonRpc.getInstalledNamespaces().then(jsonRpcResponse => { + this._installedExtensions = jsonRpcResponse.result; + }); } render() { return html`
${this._renderActives(devuiState.cards.active)} ${devuiState.cards.inactive.map(extension => this._renderInactive(extension))} -
`; + + ${this._renderAddDialog()}`; } _renderActives(extensions){ @@ -97,7 +127,6 @@ export class QwcExtensions extends observeState(LitElement) { } _renderActive(extension, fav){ - return html` @@ -243,5 +273,55 @@ export class QwcExtensions extends observeState(LitElement) { `; } } + + _renderAddDialog(){ + return html` + html` + + + + `, + [] + )} + ${dialogRenderer( + () => html`` + )} + > + ${this._renderAddExtensionButton()} + `; + } + + _renderAddExtensionButton(){ + if(connectionState.current.isConnected){ + return html` + + `; + } + } + + _installRequest(e){ + this._addDialogOpened = false; + let name = e.detail.name; + if(e.detail.outcome){ + notifier.showInfoMessage(name + " installation in progress"); + }else{ + notifier.showErrorMessage(name + " installation failed"); + } + } + + _openAddDialog() { + this._addDialogOpened = true; + } + } customElements.define('qwc-extensions', QwcExtensions); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandOutcome.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandOutcome.java index 4bca9c6c87856..f423d663e73b4 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandOutcome.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/data/QuarkusCommandOutcome.java @@ -2,25 +2,32 @@ import java.util.Objects; -public class QuarkusCommandOutcome extends ValueMap { +public class QuarkusCommandOutcome extends ValueMap> { - public static QuarkusCommandOutcome success() { - return new QuarkusCommandOutcome(true, null); + public static QuarkusCommandOutcome success() { + return new QuarkusCommandOutcome<>(true, null, null); } - public static QuarkusCommandOutcome failure(String message) { + public static QuarkusCommandOutcome success(T result) { + return new QuarkusCommandOutcome<>(true, null, result); + } + + public static QuarkusCommandOutcome failure(String message) { Objects.requireNonNull(message, "Message may not be null in case of a failure"); - return new QuarkusCommandOutcome(false, message); + return new QuarkusCommandOutcome(false, message, null); } private final boolean success; private final String message; - private QuarkusCommandOutcome(boolean success, String message) { + private final T result; + + private QuarkusCommandOutcome(boolean success, String message, T result) { this.success = success; this.message = message; + this.result = result; } public boolean isSuccess() { @@ -30,4 +37,8 @@ public boolean isSuccess() { public String getMessage() { return message; } + + public T getResult() { + return result; + } } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java index e7a6a5f6ae4c3..a0bba897a88fa 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java @@ -2,7 +2,9 @@ import java.util.Collection; import java.util.Comparator; +import java.util.List; import java.util.function.BiConsumer; +import java.util.stream.Collectors; import io.quarkus.devtools.commands.ListCategories; import io.quarkus.devtools.commands.data.QuarkusCommandException; @@ -30,13 +32,16 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws final Collection categories = invocation.getExtensionsCatalog().getCategories(); - if (!batchMode) { + if (!batchMode && !format.equalsIgnoreCase("object")) { log.info("Available Quarkus extension categories: "); log.info(""); } BiConsumer formatter; switch (format.toLowerCase()) { + case "object": + formatter = null; + break; case "full": log.info(String.format(FULL_FORMAT, "Category", "CategoryId", "Description")); formatter = this::fullFormatter; @@ -50,10 +55,16 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws break; } - categories.stream() - .sorted(Comparator.comparing(Category::getName)) - .forEach(c -> formatter.accept(log, c)); - + if (formatter != null) { + categories.stream() + .sorted(Comparator.comparing(Category::getName)) + .forEach(c -> formatter.accept(log, c)); + } else { + List sortedCategories = categories.stream() + .sorted(Comparator.comparing(Category::getName)) + .collect(Collectors.toList()); + return QuarkusCommandOutcome.success(sortedCategories); + } return QuarkusCommandOutcome.success(); } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java index ca7154bc9f64c..0f05b84a02513 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java @@ -61,11 +61,13 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws .getExtensions(); if (extensions.isEmpty()) { - log.info("No extension found with pattern '%s'", search); + if (!format.equalsIgnoreCase("object")) { + log.info("No extension found with pattern '%s'", search); + } return QuarkusCommandOutcome.success(); } - if (!batchMode) { + if (!batchMode && !format.equalsIgnoreCase("object")) { String extensionStatus = all ? "available" : "installable"; if (installedOnly) extensionStatus = "installed"; @@ -75,6 +77,9 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws BiConsumer currentFormatter; switch (format.toLowerCase()) { + case "object": + currentFormatter = null; + break; case "id": case "name": currentFormatter = this::idFormatter; @@ -110,11 +115,30 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws categoryFilter = e -> true; } - extensions.stream() - .filter(e -> !ExtensionProcessor.of(e).isUnlisted()) - .filter(categoryFilter) - .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId())) - .forEach(e -> display(log, e, installedByKey.get(toKey(e)), all, installedOnly, currentFormatter)); + if (currentFormatter != null) { + extensions.stream() + .filter(e -> !ExtensionProcessor.of(e).isUnlisted()) + .filter(categoryFilter) + .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId())) + .forEach(e -> display(log, e, installedByKey.get(toKey(e)), all, installedOnly, currentFormatter)); + } else { + List filteredExtensions = extensions.stream() + .filter(e -> !ExtensionProcessor.of(e).isUnlisted()) + .filter(categoryFilter) + .filter(e -> { + ArtifactCoords installed = installedByKey.get(toKey(e)); + if (installedOnly && installed == null) { + return false; + } + if (!installedOnly && !all && installed != null) { + return false; + } + return true; + }) + .sorted(Comparator.comparing(e -> e.getArtifact().getArtifactId())) + .collect(Collectors.toList()); + return QuarkusCommandOutcome.success(filteredExtensions); + } return QuarkusCommandOutcome.success(); } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java index bf50251b319e1..379cf8460636a 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/QuarkusProjectHelper.java @@ -2,6 +2,8 @@ import static io.quarkus.devtools.project.CodestartResourceLoadersBuilder.getCodestartResourceLoaders; +import java.io.OutputStream; +import java.io.PrintStream; import java.nio.file.Path; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; @@ -16,7 +18,7 @@ import io.quarkus.registry.config.RegistriesConfig; public class QuarkusProjectHelper { - + private static QuarkusProject cachedProject; private static RegistriesConfig toolsConfig; private static MessageWriter log; private static MavenArtifactResolver artifactResolver; @@ -44,6 +46,33 @@ public static BuildTool detectExistingBuildTool(Path projectDirPath) { return BuildTool.fromProject(projectDirPath); } + public static QuarkusProject getCachedProject(Path projectDir) { + if (cachedProject == null) { + PrintStream nullPrintStream = new PrintStream(OutputStream.nullOutputStream()); + log = MessageWriter.info(nullPrintStream); + BuildTool buildTool = detectExistingBuildTool(projectDir); + if (buildTool == null) { + buildTool = BuildTool.MAVEN; + } + if (BuildTool.MAVEN.equals(buildTool)) { + try { + return MavenProjectBuildFile.getProject(projectDir, log, null); + } catch (RegistryResolutionException e) { + throw new RuntimeException("Failed to initialize the Quarkus Maven extension manager", e); + } + } + final ExtensionCatalog catalog; + try { + catalog = resolveExtensionCatalog(); + } catch (Exception e) { + throw new RuntimeException("Failed to resolve the Quarkus extension catalog", e); + } + cachedProject = getProject(projectDir, catalog, buildTool, JavaVersion.NA, log); + } + + return cachedProject; + } + public static QuarkusProject getProject(Path projectDir) { BuildTool buildTool = detectExistingBuildTool(projectDir); if (buildTool == null) {