From b3f159045e8904aa880cd5a7eb571c1adc77cdff Mon Sep 17 00:00:00 2001
From: Ryan Zegray <ryan.zegray@gmail.com>
Date: Tue, 23 Jun 2020 09:50:07 -0400
Subject: [PATCH] Use v4 of the MicroProfile starter api (#59)

* Update to use v4 mp starter api

* move project generation to starter api helper

* Update wording in error messages

Signed-off-by: Ryan Zegray <ryan.zegray@gmail.com>
---
 CHANGELOG.md                    |   6 ++
 README.md                       |   1 +
 package-lock.json               |   2 +-
 package.json                    |   2 +-
 src/commands/generateProject.ts | 104 ++++++++++++++------------------
 src/constants.ts                |  10 ++-
 src/util/mpStarterApi.ts        |  89 +++++++++++++++++++++++++++
 src/util/vscodePrompts.ts       |  11 +---
 8 files changed, 151 insertions(+), 74 deletions(-)
 create mode 100644 src/util/mpStarterApi.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18e0eab..e426b3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
 # Change Log
 All notable changes to the MicroProfile Starter extension will be documented below.
 
+## 0.2.5
+- MicroProfile 3.3
+- Support for generating a WildFly project
+- Better support for selecting a JavaSE version
+- Use version 4 of the MicroProfile Starter API 
+
 ## 0.2.4
 - Use version 3 of the MicroProfile Starter API when generating a project
 - Update dependencies
diff --git a/README.md b/README.md
index 076cdeb..0adba3f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
 # VS Code MicroProfile Starter Extension
 
 [![Marketplace Version](https://vsmarketplacebadge.apphb.com/version/MicroProfile-Community.mp-starter-vscode-ext.svg "Current Release")](https://marketplace.visualstudio.com/items?itemName=MicroProfile-Community.mp-starter-vscode-ext)
+[![Marketplace Installs](https://vsmarketplacebadge.apphb.com/installs-short/MicroProfile-Community.mp-starter-vscode-ext.svg "Installs")](https://marketplace.visualstudio.com/items?itemName=MicroProfile-Community.mp-starter-vscode-ext)
 [![Build Status](https://travis-ci.org/MicroShed/mp-starter-vscode-ext.svg?branch=master)](https://travis-ci.org/MicroShed/mp-starter-vscode-ext)
 
 The MicroProfile Starter extension provides support for generating a MicroProfile Maven project with examples based on the Eclipse MicroProfile Starter project (https://start.microprofile.io/) by the MicroProfile community. You will be able to generate a project by choosing a MicroProfile version, server and specifications, such as CDI, Config, Health, Metrics, and more. The code for this extension is hosted under the MicroShed organization on GitHub.
diff --git a/package-lock.json b/package-lock.json
index 3180be2..44857e8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "mp-starter-vscode-ext",
-  "version": "0.2.4",
+  "version": "0.2.5",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 034079d..30927a4 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "mp-starter-vscode-ext",
   "displayName": "MicroProfile Starter",
   "description": "Generate Java Microservice Maven projects using Eclipse MicroProfile",
-  "version": "0.2.4",
+  "version": "0.2.5",
   "publisher": "MicroProfile-Community",
   "preview": true,
   "license": "Apache-2.0",
diff --git a/src/commands/generateProject.ts b/src/commands/generateProject.ts
index 495973d..325ef84 100644
--- a/src/commands/generateProject.ts
+++ b/src/commands/generateProject.ts
@@ -2,23 +2,12 @@ import * as vscode from "vscode";
 import * as util from "../util/util";
 import * as prompts from "../util/vscodePrompts";
 import * as path from "path";
-import fetch from "node-fetch";
-import { MP_STARTER_API_ROOT, OPEN_NEW_PROJECT_OPTIONS, EXTENSION_USER_AGENT } from "../constants";
+import { OPEN_NEW_PROJECT_OPTIONS, ERRORS } from "../constants";
+import * as mpStarterApi from "../util/mpStarterApi";
 
 export async function generateProject(): Promise<void> {
   try {
-    const mpSupportResponse = await fetch(`${MP_STARTER_API_ROOT}/supportMatrix`, {
-      method: "GET",
-      headers: {
-        "User-Agent": EXTENSION_USER_AGENT,
-      },
-    });
-    if (mpSupportResponse.status >= 400 && mpSupportResponse.status < 600) {
-      throw new Error(`Bad response ${mpSupportResponse.status}: ${mpSupportResponse.statusText}`);
-    }
-
-    const mpSupportMatrix = await mpSupportResponse.json();
-
+    const mpSupportMatrix = await mpStarterApi.getSupportMatrix();
     // mpConfigurations is a map of mp version -> mp configuration
     const mpConfigurations = mpSupportMatrix.configs;
     const allMpVersions = Object.keys(mpConfigurations);
@@ -44,18 +33,20 @@ export async function generateProject(): Promise<void> {
       return;
     }
 
-    const javaSEVersion = await prompts.askForJavaSEVersion(mpVersion, mpServer);
+    // gets support information about which JavaSE versions / microprofile specs are supported by the
+    // users selected mp server / mp version combination
+    const { javaSEVersions, mpSpecs } = await mpStarterApi.getSupportedJavaAndSpecs(
+      mpServer,
+      mpVersion
+    );
+
+    const javaSEVersion = await prompts.askForJavaSEVersion(javaSEVersions);
     if (javaSEVersion === undefined) {
       return;
     }
 
-    // ask user to pick a list of mp specifications to use for the given version of mp they selected
-    const allSupportedSpecs = mpConfigurations[mpVersion].specs;
     const specDescriptions = mpSupportMatrix.descriptions;
-    const mpSpecifications = await prompts.askForMPSpecifications(
-      allSupportedSpecs,
-      specDescriptions
-    );
+    const mpSpecifications = await prompts.askForMPSpecifications(mpSpecs, specDescriptions);
     if (mpSpecifications === undefined) {
       return;
     }
@@ -67,7 +58,7 @@ export async function generateProject(): Promise<void> {
 
     const targetDirString = targetFolder.fsPath;
 
-    const requestPayload = {
+    const projectOptions = {
       groupId: groupId,
       artifactId: artifactId,
       mpVersion: mpVersion,
@@ -80,16 +71,6 @@ export async function generateProject(): Promise<void> {
     // location to download the zip file
     const zipPath = path.join(targetDirString, zipName);
 
-    const requestOptions = {
-      url: `${MP_STARTER_API_ROOT}/project`,
-      method: "POST",
-      headers: {
-        "Content-Type": "application/json",
-        "User-Agent": EXTENSION_USER_AGENT,
-      },
-      body: JSON.stringify(requestPayload),
-    };
-
     // show a progress bar as the zip file is being downloaded
     await vscode.window.withProgress(
       {
@@ -97,44 +78,47 @@ export async function generateProject(): Promise<void> {
         title: "Generating the MicroProfile Starter project...",
         cancellable: false,
       },
-      () => util.downloadFile(requestOptions, zipPath)
+      () => mpStarterApi.downloadMPStarterProjectZip(projectOptions, zipPath)
     );
 
+    const targetDirFolder = path.join(targetDirString, artifactId);
+
     try {
-      const targetDirFolder = path.join(targetDirString, artifactId);
       await util.unzipFile(zipPath, targetDirString, targetDirFolder);
-      try {
-        await util.deleteFile(zipPath);
-      } catch (e) {
-        console.error(e);
-        vscode.window.showErrorMessage(`Failed to delete file ${zipName}`);
-      }
-
-      // open the unzipped folder in a new VS Code window
-      const uriPath = vscode.Uri.file(targetDirFolder);
-      // prompt user whether they want to add project to current workspace or open in a new window
-      const selection = await vscode.window.showInformationMessage(
-        "MicroProfile Starter project generated.  Would you like to add your project to the current workspace or open it in a new window?",
-        ...[
-          OPEN_NEW_PROJECT_OPTIONS.ADD_CURRENT_WORKSPACE,
-          OPEN_NEW_PROJECT_OPTIONS.OPEN_NEW_WINDOW,
-        ]
-      );
-      if (selection === OPEN_NEW_PROJECT_OPTIONS.ADD_CURRENT_WORKSPACE) {
-        vscode.workspace.updateWorkspaceFolders(0, 0, { uri: uriPath });
-      } else if (selection === OPEN_NEW_PROJECT_OPTIONS.OPEN_NEW_WINDOW) {
-        await vscode.commands.executeCommand("vscode.openFolder", uriPath, true);
-      }
-    } catch (err) {
-      console.error(err);
-      vscode.window.showErrorMessage("Failed to extract the MicroProfile Starter project.");
+    } catch (e) {
+      console.error(e);
+      const err = new Error("Unable to extract MicroProfile Starter project");
+      err.name = ERRORS.EXTRACT_PROJECT_ERROR;
+      throw err;
+    }
+
+    // if failed to delete the zip, no need to error out but show a warning to users
+    try {
+      await util.deleteFile(zipPath);
+    } catch (e) {
+      console.error(e);
+      vscode.window.showErrorMessage(`Failed to delete file ${zipName}`);
+    }
+
+    const uriPath = vscode.Uri.file(targetDirFolder);
+    // prompt user whether they want to add project to current workspace or open in a new window
+    const selection = await vscode.window.showInformationMessage(
+      "MicroProfile Starter project generated.  Add your project to the current workspace or open it in a new window?",
+      ...[OPEN_NEW_PROJECT_OPTIONS.ADD_CURRENT_WORKSPACE, OPEN_NEW_PROJECT_OPTIONS.OPEN_NEW_WINDOW]
+    );
+    if (selection === OPEN_NEW_PROJECT_OPTIONS.ADD_CURRENT_WORKSPACE) {
+      vscode.workspace.updateWorkspaceFolders(0, 0, { uri: uriPath });
+    } else if (selection === OPEN_NEW_PROJECT_OPTIONS.OPEN_NEW_WINDOW) {
+      await vscode.commands.executeCommand("vscode.openFolder", uriPath, true);
     }
   } catch (e) {
     console.error(e);
-    if (e.name === "FetchError") {
+    if (e.name === ERRORS.FETCH_ERROR) {
       vscode.window.showErrorMessage(
         "Failed to connect to the MicroProfile Starter. Please check your network connection and try again."
       );
+    } else if (e.name === ERRORS.EXTRACT_PROJECT_ERROR) {
+      vscode.window.showErrorMessage("Failed to extract the MicroProfile Starter project");
     } else {
       vscode.window.showErrorMessage("Failed to generate a MicroProfile Starter project");
     }
diff --git a/src/constants.ts b/src/constants.ts
index 5dc4554..28a8056 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,6 +1,7 @@
-export const MP_STARTER_API_ROOT = "https://start.microprofile.io/api/3";
+export const MP_STARTER_API_ROOT = "https://start.microprofile.io/api/4";
 
 export const MP_VERSION_LABELS: Record<string, string> = {
+  MP33: "Version 3.3",
   MP32: "Version 3.2",
   MP30: "Version 3.0",
   MP22: "Version 2.2",
@@ -17,7 +18,7 @@ export const MP_SERVER_LABELS: Record<string, string> = {
   PAYARA_MICRO: "Payara Micro",
   THORNTAIL_V2: "Thorntail Version 2",
   KUMULUZEE: "KumuluzEE",
-  TOMEE: "Apache TomEE 8.00-M2",
+  TOMEE: "Apache TomEE 8.00-M3",
   WILDFLY: "WildFly",
   WILDFLY_SWARM: "WildFly Swarm",
   QUARKUS: "Quarkus",
@@ -34,3 +35,8 @@ export const CONFIRM_OPTIONS = {
   YES: "Yes",
   NO: "No",
 };
+
+export const ERRORS = {
+  FETCH_ERROR: "FetchError",
+  EXTRACT_PROJECT_ERROR: "ExtractProjectError",
+};
diff --git a/src/util/mpStarterApi.ts b/src/util/mpStarterApi.ts
new file mode 100644
index 0000000..27052c1
--- /dev/null
+++ b/src/util/mpStarterApi.ts
@@ -0,0 +1,89 @@
+import { MP_STARTER_API_ROOT, EXTENSION_USER_AGENT } from "../constants";
+import fetch from "node-fetch";
+import * as util from "../util/util";
+
+interface MPVersionSupport {
+  supportedServers: string[];
+  specs: string[];
+}
+
+interface SupportMatrix {
+  configs: Record<string, MPVersionSupport>;
+  descriptions: Record<string, string>;
+}
+
+export async function getSupportMatrix(): Promise<SupportMatrix> {
+  const mpSupportResponse = await fetch(`${MP_STARTER_API_ROOT}/supportMatrix`, {
+    method: "GET",
+    headers: {
+      "User-Agent": EXTENSION_USER_AGENT,
+    },
+  });
+  if (mpSupportResponse.status >= 400 && mpSupportResponse.status < 600) {
+    throw new Error(`Bad response ${mpSupportResponse.status}: ${mpSupportResponse.statusText}`);
+  }
+
+  return mpSupportResponse.json();
+}
+
+interface SupportDetails {
+  mpVersion: string;
+  mpSpecs: string[];
+  javaSEVersions: string[];
+}
+
+export async function getSupportedJavaAndSpecs(
+  serverName: string,
+  microprofileVersion: string
+): Promise<SupportDetails> {
+  const serverSupportResponse = await fetch(`${MP_STARTER_API_ROOT}/supportMatrix/servers`, {
+    method: "GET",
+    headers: {
+      "User-Agent": EXTENSION_USER_AGENT,
+    },
+  });
+  if (serverSupportResponse.status >= 400 && serverSupportResponse.status < 600) {
+    throw new Error(
+      `Bad response ${serverSupportResponse.status}: ${serverSupportResponse.statusText}`
+    );
+  }
+
+  const supportJSON = await serverSupportResponse.json();
+  const serverInformation = supportJSON.configs[serverName];
+
+  const supportDetails: SupportDetails | undefined = serverInformation.find(
+    (supportRecord: SupportDetails) => supportRecord.mpVersion === microprofileVersion
+  );
+
+  if (supportDetails === undefined) {
+    throw new Error("Unable to find supported MicroProfile specifications and Java versions");
+  }
+
+  return supportDetails;
+}
+
+interface StarterProjectOptions {
+  groupId: string;
+  artifactId: string;
+  mpVersion: string;
+  supportedServer: string;
+  javaSEVersion: string;
+  selectedSpecs: string[];
+}
+
+export async function downloadMPStarterProjectZip(
+  options: StarterProjectOptions,
+  downloadLocation: string
+): Promise<void> {
+  const requestOptions = {
+    url: `${MP_STARTER_API_ROOT}/project`,
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json",
+      "User-Agent": EXTENSION_USER_AGENT,
+    },
+    body: JSON.stringify(options),
+  };
+
+  await util.downloadFile(requestOptions, downloadLocation);
+}
diff --git a/src/util/vscodePrompts.ts b/src/util/vscodePrompts.ts
index cc47521..2a5f720 100644
--- a/src/util/vscodePrompts.ts
+++ b/src/util/vscodePrompts.ts
@@ -41,17 +41,8 @@ export async function askForArtifactID(): Promise<string | undefined> {
 }
 
 export async function askForJavaSEVersion(
-  mpVersion: string,
-  mpServer: string
+  supportedJavaSEVersions: string[]
 ): Promise<string | undefined> {
-  const MP32_JAVA_11_SUPPORTED = ["LIBERTY", "PAYARA_MICRO", "HELIDON", "THORNTAIL_V2", "WILDFLY"];
-
-  let supportedJavaSEVersions = ["SE8"];
-
-  if (mpVersion === "MP32" && MP32_JAVA_11_SUPPORTED.includes(mpServer)) {
-    supportedJavaSEVersions = ["SE8", "SE11"];
-  }
-
   return await vscode.window.showQuickPick(supportedJavaSEVersions, {
     ignoreFocusOut: true,
     placeHolder: "Select a Java SE version.",