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..fe1b28b10c6e8 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,35 @@ package io.quarkus.devui.deployment.menu; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +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 com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; 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.QuarkusCommandOutcome; +import io.quarkus.devtools.messagewriter.MessageWriter; +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 +51,167 @@ 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)) { + + ObjectMapper objectMapper = new ObjectMapper(); + + Path projectRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath(); + QuarkusProject quarkusProject = QuarkusProjectHelper.getProject(projectRoot); + + BuildTimeActionBuildItem buildTimeActions = new BuildTimeActionBuildItem(NAMESPACE); + + getCategories(buildTimeActions, quarkusProject, objectMapper); + getInstallableExtensions(buildTimeActions, quarkusProject, objectMapper); + getInstalledNamespaces(buildTimeActions, quarkusProject, objectMapper); + removeExtension(buildTimeActions, quarkusProject); + addExtension(buildTimeActions, quarkusProject); + buildTimeActionProducer.produce(buildTimeActions); + } + } + + private void getCategories(BuildTimeActionBuildItem buildTimeActions, QuarkusProject quarkusProject, + ObjectMapper objectMapper) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), ignored -> { + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(byteArrayOutputStream)) { + + QuarkusCommandOutcome outcome = new ListCategories(quarkusProject, MessageWriter.info(printStream)) + .format("json") + .execute(); + + if (outcome.isSuccess()) { + String jsonString = byteArrayOutputStream.toString(); + return objectMapper.readTree(jsonString); + } + return null; + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + private void getInstallableExtensions(BuildTimeActionBuildItem buildTimeActions, QuarkusProject quarkusProject, + ObjectMapper objectMapper) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), ignored -> { + + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(byteArrayOutputStream)) { + + QuarkusCommandOutcome outcome = new ListExtensions(quarkusProject, MessageWriter.info(printStream)) + .installed(false) + .all(false) + .format("json") + .execute(); + + if (outcome.isSuccess()) { + String jsonString = byteArrayOutputStream.toString(); + return objectMapper.readTree(jsonString); + } + + return null; + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + private void getInstalledNamespaces(BuildTimeActionBuildItem buildTimeActions, QuarkusProject quarkusProject, + ObjectMapper objectMapper) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), ignored -> { + + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(byteArrayOutputStream)) { + + QuarkusCommandOutcome outcome = new ListExtensions(quarkusProject, MessageWriter.info(printStream)) + .installed(true) + .all(false) + .format("json") + .execute(); + + if (outcome.isSuccess()) { + String jsonString = byteArrayOutputStream.toString(); + JsonNode rootArrayNode = objectMapper.readTree(jsonString); + List namespaceList = new ArrayList<>(); + + if (rootArrayNode.isArray()) { + for (JsonNode jsonNode : rootArrayNode) { + JsonNode artifactNode = jsonNode.path("artifact"); + String groupId = artifactNode.path("groupId").asText(); + String artifactId = artifactNode.path("artifactId").asText(); + namespaceList.add(groupId + "." + artifactId); + } + } + return namespaceList; + } + + return null; + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + private void removeExtension(BuildTimeActionBuildItem buildTimeActions, QuarkusProject quarkusProject) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), params -> { + String extensionArtifactId = params.get("extensionArtifactId"); + + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(byteArrayOutputStream)) { + + QuarkusCommandOutcome outcome = new RemoveExtensions(quarkusProject, MessageWriter.info(printStream)) + .extensions(Set.of(extensionArtifactId)) + .execute(); + + if (outcome.isSuccess()) { + return true; + } else { + return false; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + private void addExtension(BuildTimeActionBuildItem buildTimeActions, QuarkusProject quarkusProject) { + buildTimeActions.addAction(new Object() { + }.getClass().getEnclosingMethod().getName(), params -> { + String extensionArtifactId = params.get("extensionArtifactId"); + + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(byteArrayOutputStream)) { + + QuarkusCommandOutcome outcome = new AddExtensions(quarkusProject, MessageWriter.info(printStream)) + .extensions(Set.of(extensionArtifactId)) + .execute(); + + if (outcome.isSuccess()) { + return true; + } else { + return false; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + 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)} +
+
+ + + Install + + `; + } + + _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..7a2d7d73b8b69 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 progess"); + 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..5c5ec21f91d9e 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,15 +5,21 @@ 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'; +import '@vaadin/progress-bar'; /** * This component create cards of all the extensions */ 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,51 @@ 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 = null; + } + + 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))} -
`; + if(this._installedExtensions){ + return html`
+ ${this._renderActives(devuiState.cards.active)} + ${devuiState.cards.inactive.map(extension => this._renderInactive(extension))} +
+ ${this._renderAddDialog()}`; + }else{ + return html` + `; + } } _renderActives(extensions){ @@ -97,7 +132,7 @@ export class QwcExtensions extends observeState(LitElement) { } _renderActive(extension, fav){ - + let installed = this._installedExtensions.includes(extension.namespace); return html` @@ -243,5 +279,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 + " instalation in progess"); + }else{ + notifier.showErrorMessage(name + " instalation 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/handlers/ListCategoriesCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/ListCategoriesCommandHandler.java index e7a6a5f6ae4c3..d9cbaf581c90d 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,12 @@ import java.util.Collection; import java.util.Comparator; +import java.util.List; import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.devtools.commands.ListCategories; import io.quarkus.devtools.commands.data.QuarkusCommandException; @@ -20,6 +25,7 @@ public class ListCategoriesCommandHandler implements QuarkusCommandHandler { private static final String ID_FORMAT = "%-20s"; private static final String CONCISE_FORMAT = "%-50s %-50s"; private static final String FULL_FORMAT = "%-30s %-20s %s"; + private ObjectMapper objectMapper = new ObjectMapper(); @Override public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException { @@ -30,13 +36,16 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws final Collection categories = invocation.getExtensionsCatalog().getCategories(); - if (!batchMode) { + if (!batchMode && !format.equalsIgnoreCase("json")) { log.info("Available Quarkus extension categories: "); log.info(""); } BiConsumer formatter; switch (format.toLowerCase()) { + case "json": + formatter = null; + break; case "full": log.info(String.format(FULL_FORMAT, "Category", "CategoryId", "Description")); formatter = this::fullFormatter; @@ -50,10 +59,22 @@ 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 { + try { + List sortedCategories = categories.stream() + .sorted(Comparator.comparing(Category::getName)) + .collect(Collectors.toList()); + log.info(objectMapper.writeValueAsString(sortedCategories)); + } catch (JsonProcessingException ex) { + ex.printStackTrace(); + log.info("[]"); + } + } 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..7cb492b74dd1e 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 @@ -16,6 +16,9 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import io.quarkus.devtools.commands.ListExtensions; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; @@ -43,6 +46,8 @@ public class ListExtensionsCommandHandler implements QuarkusCommandHandler { private static final String ORIGINS_FORMAT = "%-1s %-50s %-50s %-25s %s"; private static final String FULL_FORMAT = "%-1s %-50s %-60s %-25s %s"; + private ObjectMapper objectMapper = new ObjectMapper(); + @Override public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException { @@ -61,11 +66,15 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws .getExtensions(); if (extensions.isEmpty()) { - log.info("No extension found with pattern '%s'", search); + if (format.equalsIgnoreCase("json")) { + log.info("[]"); + } else { + log.info("No extension found with pattern '%s'", search); + } return QuarkusCommandOutcome.success(); } - if (!batchMode) { + if (!batchMode && !format.equalsIgnoreCase("json")) { String extensionStatus = all ? "available" : "installable"; if (installedOnly) extensionStatus = "installed"; @@ -75,6 +84,9 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws BiConsumer currentFormatter; switch (format.toLowerCase()) { + case "json": + currentFormatter = null; + break; case "id": case "name": currentFormatter = this::idFormatter; @@ -110,11 +122,36 @@ 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 { + try { + 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()); + + log.info(objectMapper.writeValueAsString(filteredExtensions)); + } catch (JsonProcessingException ex) { + ex.printStackTrace(); + log.info("[]"); + } + } return QuarkusCommandOutcome.success(); }