diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 7c785992..53a29ada 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -42,5 +42,9 @@
hamcrest-library
test
+
+ org.semver4j
+ semver4j
+
diff --git a/integration-tests/src/test/java/org/openmrs/maven/plugins/AddDependencyTest.java b/integration-tests/src/test/java/org/openmrs/maven/plugins/AddDependencyTest.java
new file mode 100644
index 00000000..51dc4712
--- /dev/null
+++ b/integration-tests/src/test/java/org/openmrs/maven/plugins/AddDependencyTest.java
@@ -0,0 +1,137 @@
+package org.openmrs.maven.plugins;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.openmrs.maven.plugins.model.DistroProperties;
+import org.openmrs.maven.plugins.utility.DistroHelper;
+
+import java.io.File;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class AddDependencyTest extends AbstractSdkIntegrationTest {
+
+ private String distroFile;
+
+ private DistroProperties originalProperties;
+
+
+ @Before
+ public void setUp() {
+ distroFile = testDirectory + File.separator + "openmrs-distro.properties";
+ originalProperties = DistroHelper.getDistroPropertiesFromFile(new File(distroFile));
+ }
+
+ @Test
+ public void shouldAddOmodDependency() throws Exception {
+ addTaskParam("distro", distroFile);
+ addTaskParam("type", "OMOD");
+ addTaskParam("groupId", "org.openmrs.module");
+ addTaskParam("artifactId", "webservices.rest");
+ addTaskParam("version", "2.30.0");
+
+ executeTask("add");
+ assertSuccess();
+
+ DistroProperties distroProperties = DistroHelper.getDistroPropertiesFromFile(new File(distroFile));
+ assertNotNull(distroProperties);
+ assertTrue(distroProperties.getAllKeys().contains("omod.webservices.rest"));
+ assertEquals(distroProperties.getParam("omod.webservices.rest"), "2.30.0");
+ }
+
+ @Test
+ public void shouldAddSpaDependency() throws Exception {
+ addTaskParam("distro", distroFile);
+ addTaskParam("type", "SPA");
+ addTaskParam("moduleName", "@openmrs/esm-system-admin-app");
+ addTaskParam("version", "4.0.3");
+
+ executeTask("add");
+ assertSuccess();
+
+ DistroProperties distroProperties = DistroHelper.getDistroPropertiesFromFile(new File(distroFile));
+ assertNotNull(distroProperties);
+
+ assertTrue(distroProperties.getAllKeys().contains("spa.frontendModules.@openmrs/esm-system-admin-app"));
+ assertEquals(distroProperties.getParam("spa.frontendModules.@openmrs/esm-system-admin-app"), "4.0.3");
+ }
+
+ @Test
+ public void shouldAddOwaDependency() throws Exception {
+ addTaskParam("distro", distroFile);
+ addTaskParam("type", "OWA");
+ addTaskParam("artifactId", "sysadmin");
+ addTaskParam("version", "1.2.0");
+
+ executeTask("add");
+ assertSuccess();
+
+ DistroProperties distroProperties = DistroHelper.getDistroPropertiesFromFile(new File(distroFile));
+ assertNotNull(distroProperties);
+
+ assertTrue(distroProperties.getAllKeys().contains("owa.sysadmin"));
+ assertEquals(distroProperties.getParam("owa.sysadmin"), "1.2.0");
+ }
+
+ @Test
+ public void shouldAddWarDependency() throws Exception {
+ addTaskParam("distro", distroFile);
+ addTaskParam("type", "WAR");
+ addTaskParam("version", "2.6.1");
+
+ executeTask("add");
+ assertSuccess();
+
+ DistroProperties distroProperties = DistroHelper.getDistroPropertiesFromFile(new File(distroFile));
+ assertNotNull(distroProperties);
+
+ assertTrue(distroProperties.getAllKeys().contains("war.openmrs"));
+ assertEquals(distroProperties.getParam("war.openmrs"), "2.6.1");
+ }
+
+ @Test
+ public void shouldAddCustomDependencyIfTypeIsNotSpecified() throws Exception {
+ addTaskParam("distro", distroFile);
+ addTaskParam("property", "custom.property");
+ addTaskParam("version", "1.2.3");
+
+ executeTask("add");
+ assertSuccess();
+
+ DistroProperties distroProperties = DistroHelper.getDistroPropertiesFromFile(new File(distroFile));
+ assertTrue(distroProperties.getAllKeys().contains("custom.property"));
+ assertEquals(distroProperties.getParam("custom.property"), "1.2.3");
+
+ }
+
+ @Test
+ public void shouldOverrideIfPropertyAlreadyExists() throws Exception {
+ DistroProperties distroProperties = DistroHelper.getDistroPropertiesFromFile(new File(distroFile));
+ assertNotNull(distroProperties);
+ assertTrue(distroProperties.getAllKeys().contains("omod.uiframework"));
+
+ addTaskParam("distro", distroFile);
+ addTaskParam("type", "OMOD");
+ addTaskParam("groupId", "org.openmrs.module");
+ addTaskParam("artifactId", "uiframework");
+ addTaskParam("version", "2.30.0");
+
+ executeTask("add");
+ assertSuccess();
+
+ distroProperties = DistroHelper.getDistroPropertiesFromFile(new File(distroFile));
+ assertNotNull(distroProperties);
+
+ assertTrue(distroProperties.getAllKeys().contains("omod.uiframework"));
+ assertEquals(distroProperties.getParam("omod.uiframework"), "2.30.0");
+ }
+
+ @After
+ public void reset() throws MojoExecutionException {
+ originalProperties.saveTo(new File(distroFile).getParentFile());
+ }
+}
diff --git a/maven-plugin/src/main/java/org/openmrs/maven/plugins/AddDependency.java b/maven-plugin/src/main/java/org/openmrs/maven/plugins/AddDependency.java
new file mode 100644
index 00000000..f358efa8
--- /dev/null
+++ b/maven-plugin/src/main/java/org/openmrs/maven/plugins/AddDependency.java
@@ -0,0 +1,157 @@
+package org.openmrs.maven.plugins;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.openmrs.maven.plugins.model.Artifact;
+import org.openmrs.maven.plugins.model.DistroProperties;
+
+import org.openmrs.maven.plugins.utility.NpmVersionHelper;
+import org.openmrs.maven.plugins.utility.SDKConstants;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Mojo(name = "add", requiresProject = false)
+public class AddDependency extends AbstractTask {
+
+ /**
+ * Path to the openmrs-distro.properties file to modify
+ */
+ @Parameter(property = "distro")
+ private String distro;
+
+ /**
+ * Type of the dependency to add (OMOD, SPA, OWA, WAR or Custom)
+ */
+ @Parameter(property = "type")
+ private String type;
+
+ /**
+ * Maven group id of the dependency
+ */
+ @Parameter(property = "groupId")
+ private String groupId;
+
+ /**
+ * Maven artifact id of the dependency
+ */
+ @Parameter(property = "artifactId")
+ private String artifactId;
+
+ /**
+ * Version of the dependency
+ */
+ @Parameter(property = "version")
+ private String version;
+
+ /**
+ * Name of the frontend module
+ */
+ @Parameter(property = "moduleName")
+ private String moduleName;
+
+ /**
+ * Name of the custom property
+ */
+ @Parameter(property = "property")
+ private String property;
+
+ private static final String DEPENDENCY_TYPE_PROMPT = "Enter the type of dependency you need to add";
+
+ private static final String OMOD_OPTION = "OMOD";
+ private static final String SPA_OPTION = "SPA";
+ private static final String OWA_OPTION = "OWA";
+ private static final String WAR_OPTION = "WAR";
+ private static final String CUSTOM_OPTION = "Custom";
+
+
+ @Override
+ public void executeTask() throws MojoExecutionException, MojoFailureException {
+ if (distro == null) {
+ File userDir = new File(System.getProperty("user.dir"));
+ File distroFile = new File(userDir, DistroProperties.DISTRO_FILE_NAME);
+ if (distroFile.exists()) {
+ distro = distroFile.getAbsolutePath();
+ }
+ }
+
+ if (StringUtils.isBlank(type) && StringUtils.isBlank(property)) {
+ List dependencyTypes = new ArrayList<>(Arrays.asList(OMOD_OPTION, SPA_OPTION, OWA_OPTION, WAR_OPTION, CUSTOM_OPTION));
+ type = wizard.promptForMissingValueWithOptions(DEPENDENCY_TYPE_PROMPT, null, null, dependencyTypes);
+ }
+
+ if (StringUtils.isBlank(type) && StringUtils.isNotBlank(property)) {
+ type = CUSTOM_OPTION;
+ }
+
+ DistroProperties distroProperties = null;
+
+ if (StringUtils.isNotBlank(distro)) {
+ distroProperties = distroHelper.resolveDistroPropertiesForStringSpecifier(distro, versionsHelper);
+ }
+
+ if (distroProperties == null) {
+ throw new MojoFailureException("Invalid distro properties");
+ }
+
+ switch (type.toUpperCase()) {
+ case OMOD_OPTION:
+ groupId = wizard.promptForValueIfMissingWithDefault(null, groupId, "groupId", Artifact.GROUP_MODULE);
+ artifactId = wizard.promptForValueIfMissing(artifactId, "artifactId");
+ if (StringUtils.isBlank(version)) {
+ Artifact artifact = new Artifact(artifactId, "1.0", groupId);
+ List versions = versionsHelper.getSuggestedVersions(artifact, 5);
+ version = wizard.promptForMissingValueWithOptions("Enter the version", null, null, versions,
+ "Please specify the module version", null);
+ }
+ distroProperties.addProperty("omod." + artifactId, version);
+ break;
+ case SPA_OPTION:
+ moduleName = wizard.promptForValueIfMissing(moduleName, "frontend module name");
+ if (StringUtils.isBlank(version)) {
+ List versions = new NpmVersionHelper().getPackageVersions(moduleName, 6);;
+ version = wizard.promptForMissingValueWithOptions("Enter the module version", null, null, versions,
+ "Please specify the SPA version", null);
+ }
+ distroProperties.addProperty("spa.frontendModules." + moduleName, version);
+ break;
+ case OWA_OPTION:
+ artifactId = wizard.promptForValueIfMissing(artifactId, "OWA name");
+ Artifact artifact = new Artifact(artifactId, "1.0", Artifact.GROUP_OWA);
+ if (StringUtils.isBlank(version)) {
+ List suggestedVersions = versionsHelper.getSuggestedVersions(artifact, 6);
+ version = wizard
+ .promptForMissingValueWithOptions("Which version would you like to deploy?%s", null, "", suggestedVersions,
+ "Please specify OWA version", null);
+ }
+ distroProperties.addProperty("owa." + artifactId, version);
+ break;
+ case WAR_OPTION:
+ Artifact platformArtifact = new Artifact(SDKConstants.PLATFORM_ARTIFACT_ID,
+ SDKConstants.SETUP_DEFAULT_PLATFORM_VERSION, Artifact.GROUP_DISTRO);
+ if (StringUtils.isBlank(version)) {
+ version = wizard.promptForPlatformVersion(versionsHelper.getSuggestedVersions(platformArtifact, 5));
+ }
+ distroProperties.addProperty("war.openmrs", version);
+ break;
+ default:
+ property = wizard.promptForValueIfMissing(property, "the property name (ex: omod.legacyui)");
+ version = wizard.promptForValueIfMissing(version, "the property version");
+ distroProperties.addProperty(property, version);
+ }
+
+ if (StringUtils.isBlank(distro)) {
+ wizard.showMessage("No distro.properpties file is provided. Generating a new openmrs-distro.properties file.");
+ distro = Paths.get(System.getProperty("user.dir"), DistroProperties.DISTRO_FILE_NAME).toString();
+ }
+
+ distroProperties.saveTo(new File(distro).getParentFile());
+ }
+
+}
diff --git a/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/DistroProperties.java b/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/DistroProperties.java
index aa759d0e..4c887d34 100644
--- a/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/DistroProperties.java
+++ b/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/DistroProperties.java
@@ -1,5 +1,8 @@
package org.openmrs.maven.plugins.model;
+import static org.openmrs.maven.plugins.utility.PropertiesUtils.loadPropertiesFromFile;
+import static org.openmrs.maven.plugins.utility.PropertiesUtils.loadPropertiesFromResource;
+
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
@@ -18,9 +21,6 @@
import java.util.Properties;
import java.util.Set;
-import static org.openmrs.maven.plugins.utility.PropertiesUtils.loadPropertiesFromFile;
-import static org.openmrs.maven.plugins.utility.PropertiesUtils.loadPropertiesFromResource;
-
/**
*
*/
@@ -292,6 +292,10 @@ public void addExclusion(String exclusion) {
properties.setProperty("exclusions", exclusions + "," + exclusion);
}
+ public void addProperty(String property, String value) throws MojoExecutionException {
+ properties.put(property, value);
+ }
+
private String getPlaceholderKey(String string){
int startIndex = string.indexOf("${")+2;
int endIndex = string.indexOf("}", startIndex);
diff --git a/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/NpmVersionHelper.java b/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/NpmVersionHelper.java
index f6f993aa..2ce46a7f 100644
--- a/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/NpmVersionHelper.java
+++ b/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/NpmVersionHelper.java
@@ -6,10 +6,16 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
public class NpmVersionHelper {
@@ -44,6 +50,35 @@ public String getResolvedVersionFromNpmRegistry(PackageJson packageJson, String
throw new RuntimeException("Error retrieving resolved version from NPM", e);
}
}
+
+ public List getPackageVersions(String packageName, int limit) {
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder()
+ .command("npm", "view", packageName, "versions", "--dry-run", "--json").redirectErrorStream(true);
+
+ Process process = processBuilder.start();
+
+ StringBuilder output = new StringBuilder();
+ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ output.append(line);
+ }
+ }
+
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ return new ArrayList<>();
+ }
+ JsonNode jsonNode = objectMapper.readTree(output.toString());
+
+ return StreamSupport.stream(jsonNode.spliterator(), false).map(JsonNode::asText)
+ .sorted(Collections.reverseOrder()).limit(limit).collect(Collectors.toList());
+
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
private static JsonNode getPackageMetadata(String versionRange, String packageName) throws IOException, InterruptedException {
if (packageName == null || packageName.isEmpty()) {