diff --git a/habushu-maven-plugin/pom.xml b/habushu-maven-plugin/pom.xml
index dfe076b..a120221 100644
--- a/habushu-maven-plugin/pom.xml
+++ b/habushu-maven-plugin/pom.xml
@@ -89,6 +89,11 @@
maven-deploy-plugin
${version.maven.deploy.plugin}
+
+ org.technologybrewery.baton
+ baton-maven-plugin
+ 0.1.0
+
org.codehaus.plexus
plexus-sec-dispatcher
diff --git a/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/InstallDependenciesMojo.java b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/InstallDependenciesMojo.java
index 2522162..b738e76 100644
--- a/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/InstallDependenciesMojo.java
+++ b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/InstallDependenciesMojo.java
@@ -15,6 +15,8 @@
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.technologybrewery.habushu.exec.PoetryCommandHelper;
+import org.technologybrewery.habushu.util.TomlReplacementTuple;
+import org.technologybrewery.habushu.util.TomlUtils;
import java.io.BufferedReader;
import java.io.File;
@@ -286,7 +288,7 @@ private void executeDetailedManagedDependencyMismatchActions(Map extras = config.get("extras");
- if (CollectionUtils.isNotEmpty(extras)) {
- sb.append(", extras = [");
- // NB: if we expect more complex values, such as multiple extras, more work would need to be done for
- // both consistent formatting and comparison of these values. However, at the time of initially writing
- // this method, there isn't a clear demand signal, so we are going to KISS for now:
-
- for (int i = 0; i < extras.size(); i++) {
- if (i > 0) {
- sb.append(", ");
- }
- sb.append("\"").append(extras.get(i)).append("\"");
- }
- sb.append("]");
- }
- sb.append("}");
-
- return sb.toString();
- }
-
protected static String replaceSnapshotWithWildcard(String pomVersion) {
return pomVersion.substring(0, pomVersion.indexOf(SNAPSHOT)) + ".*";
}
@@ -492,31 +441,4 @@ protected static String replaceSnapshotWithDev(String pomVersion) {
return pomVersion.substring(0, pomVersion.indexOf(SNAPSHOT)) + ".dev";
}
- private class TomlReplacementTuple {
- private String packageName;
-
- private String originalOperatorAndVersion;
-
- private String updatedOperatorAndVersion;
-
- public TomlReplacementTuple(String packageName, String originalOperatorAndVersion, String updatedOperatorAndVersion) {
- this.packageName = packageName;
- this.originalOperatorAndVersion = originalOperatorAndVersion;
- this.updatedOperatorAndVersion = updatedOperatorAndVersion;
-
- }
-
- public String getPackageName() {
- return packageName;
- }
-
- public String getOriginalOperatorAndVersion() {
- return originalOperatorAndVersion;
- }
-
- public String getUpdatedOperatorAndVersion() {
- return updatedOperatorAndVersion;
- }
- }
-
}
diff --git a/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/MigrateMojo.java b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/MigrateMojo.java
new file mode 100644
index 0000000..d4a17e1
--- /dev/null
+++ b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/MigrateMojo.java
@@ -0,0 +1,13 @@
+package org.technologybrewery.habushu;
+
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.technologybrewery.baton.BatonMojo;
+
+/**
+ * Overriding the baton-maven-plugin so we can get access to Habushu's classpath for migration configuration.
+ */
+@Mojo(name = "baton-migrate", defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true)
+public class MigrateMojo extends BatonMojo {
+}
diff --git a/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/migration/CustomMonorepoGroupMigration.java b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/migration/CustomMonorepoGroupMigration.java
new file mode 100644
index 0000000..6fe577d
--- /dev/null
+++ b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/migration/CustomMonorepoGroupMigration.java
@@ -0,0 +1,147 @@
+package org.technologybrewery.habushu.migration;
+
+import com.electronwill.nightconfig.core.CommentedConfig;
+import com.electronwill.nightconfig.core.Config;
+import com.electronwill.nightconfig.core.file.FileConfig;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.technologybrewery.baton.AbstractMigration;
+import org.technologybrewery.baton.BatonException;
+import org.technologybrewery.habushu.HabushuException;
+import org.technologybrewery.habushu.util.TomlReplacementTuple;
+import org.technologybrewery.habushu.util.TomlUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Automatically migrates any monorepo dependencies (e.g., foo = {path = "../foo", develop = true}) in the main
+ * [tool.poetry.dependencies] group into the [tool.poetry.group.monorepo.dependencies] group instead. As noted in the
+ * project's README.md, this prevents these dependencies from causing issues when Poetry projects are exported in
+ * development releases.
+ */
+public class CustomMonorepoGroupMigration extends AbstractMigration {
+
+ public static final Logger logger = LoggerFactory.getLogger(CustomMonorepoGroupMigration.class);
+
+ protected Map replacements = new HashMap<>();
+
+ protected boolean hasExistingMonoRepoDependenciesGroup;
+
+ @Override
+ protected boolean shouldExecuteOnFile(File file) {
+ replacements.clear();
+ hasExistingMonoRepoDependenciesGroup = false;
+
+ boolean shouldExecute = false;
+ try (FileConfig tomlFileConfig = FileConfig.of(file)) {
+ tomlFileConfig.load();
+
+ Optional toolPoetryDependencies = tomlFileConfig.getOptional(TomlUtils.TOOL_POETRY_DEPENDENCIES);
+ if (toolPoetryDependencies.isPresent()) {
+ Config foundDependencies = toolPoetryDependencies.get();
+ Map dependencyMap = foundDependencies.valueMap();
+
+ for (Map.Entry dependency : dependencyMap.entrySet()) {
+ String packageName = dependency.getKey();
+ Object packageRhs = dependency.getValue();
+ if (TomlUtils.representsLocalDevelopmentVersion(packageRhs)) {
+ String packageRshAsString = TomlUtils.convertCommentedConfigToToml((CommentedConfig) packageRhs);
+ logger.info("Found local dependency not within monorepo group! ({} = {})", packageName, packageRshAsString);
+ TomlReplacementTuple replacementTuple = new TomlReplacementTuple(packageName, packageRshAsString, "");
+ replacements.put(packageName, replacementTuple);
+ shouldExecute = true;
+ }
+ }
+ }
+
+ Optional toolPoetryMonorepoDependencies = tomlFileConfig.getOptional(TomlUtils.TOOL_POETRY_GROUP_MONOREPO_DEPENDENCIES);
+ if (toolPoetryMonorepoDependencies.isPresent()) {
+ hasExistingMonoRepoDependenciesGroup = true;
+ }
+ }
+
+ return shouldExecute;
+ }
+
+ @Override
+ protected boolean performMigration(File pyProjectTomlFile) {
+ String fileContent = StringUtils.EMPTY;
+ try (BufferedReader reader = new BufferedReader(new FileReader(pyProjectTomlFile))) {
+ String line = reader.readLine();
+ boolean injectAfterNextEmptyLine = false;
+
+ while (line != null) {
+ boolean addLine = true;
+ boolean isEmptyLine = line.isBlank();
+
+ if (line.contains(StringUtils.SPACE) && line.contains(TomlUtils.EQUALS)) {
+ String key = line.substring(0, line.indexOf(StringUtils.SPACE));
+
+ if (key == null) {
+ key = line.substring(0, line.indexOf(TomlUtils.EQUALS));
+ }
+
+ if (key != null) {
+ key = key.strip();
+
+ TomlReplacementTuple matchedTuple = replacements.get(key);
+ if (matchedTuple != null) {
+ // skip this line, we will add it back to [tool.poetry.group.monorepo.dependencies] later
+ addLine = false;
+ }
+ }
+
+ } else if (line.contains("[") && line.contains("]")) {
+ String key = line.strip();
+
+ if (hasExistingMonoRepoDependenciesGroup && (key.equals("[" + TomlUtils.TOOL_POETRY_GROUP_MONOREPO_DEPENDENCIES + "]"))) {
+ // skip this line as we are overriding with the line plus monorepo dependencies here:
+ addLine = false;
+ fileContent += line + "\n";
+ fileContent = injectMonorepoDependencies(fileContent);
+ } else if (!hasExistingMonoRepoDependenciesGroup && (key.equals("[" + TomlUtils.TOOL_POETRY_DEPENDENCIES + "]"))) {
+ injectAfterNextEmptyLine = true;
+ }
+ }
+
+ if (isEmptyLine && injectAfterNextEmptyLine) {
+ fileContent += "\n[" + TomlUtils.TOOL_POETRY_GROUP_MONOREPO_DEPENDENCIES + "]" + "\n";
+ fileContent = injectMonorepoDependencies(fileContent);
+ injectAfterNextEmptyLine = false;
+ }
+
+ if (addLine) {
+ fileContent += line + "\n";
+ }
+
+ line = reader.readLine();
+ }
+
+ } catch (IOException e) {
+ throw new HabushuException("Problem reading pyproject.toml to update with managed dependencies!", e);
+ }
+
+ try {
+ TomlUtils.writeTomlFile(pyProjectTomlFile, fileContent);
+ } catch (IOException e) {
+ throw new BatonException("Problem moving monorepo dependencies to [tool.poetry.group.monorepo.dependencies]!", e);
+ }
+
+ return true;
+
+ }
+
+ private String injectMonorepoDependencies(String fileContent) {
+ for (Map.Entry entry : replacements.entrySet()) {
+ fileContent += entry.getKey() + " = " + TomlUtils.escapeTomlRightHandSide(entry.getValue().getOriginalOperatorAndVersion()) + "\n";
+ }
+ return fileContent;
+ }
+}
diff --git a/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/util/TomlReplacementTuple.java b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/util/TomlReplacementTuple.java
new file mode 100644
index 0000000..c50091c
--- /dev/null
+++ b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/util/TomlReplacementTuple.java
@@ -0,0 +1,28 @@
+package org.technologybrewery.habushu.util;
+
+public class TomlReplacementTuple {
+ private String packageName;
+
+ private String originalOperatorAndVersion;
+
+ private String updatedOperatorAndVersion;
+
+ public TomlReplacementTuple(String packageName, String originalOperatorAndVersion, String updatedOperatorAndVersion) {
+ this.packageName = packageName;
+ this.originalOperatorAndVersion = originalOperatorAndVersion;
+ this.updatedOperatorAndVersion = updatedOperatorAndVersion;
+
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public String getOriginalOperatorAndVersion() {
+ return originalOperatorAndVersion;
+ }
+
+ public String getUpdatedOperatorAndVersion() {
+ return updatedOperatorAndVersion;
+ }
+}
diff --git a/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/util/TomlUtils.java b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/util/TomlUtils.java
new file mode 100644
index 0000000..f43359d
--- /dev/null
+++ b/habushu-maven-plugin/src/main/java/org/technologybrewery/habushu/util/TomlUtils.java
@@ -0,0 +1,110 @@
+package org.technologybrewery.habushu.util;
+
+import com.electronwill.nightconfig.core.CommentedConfig;
+import org.apache.commons.collections4.CollectionUtils;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * Common utility methods for handling TOML files.
+ */
+public final class TomlUtils {
+
+ public static final String EQUALS = "=";
+ public static final String DOUBLE_QUOTE = "\"";
+ public static final String TOOL_POETRY_DEPENDENCIES = "tool.poetry.dependencies";
+ public static final String TOOL_POETRY_GROUP_MONOREPO_DEPENDENCIES = "tool.poetry.group.monorepo.dependencies";
+ public static final String VERSION = "version";
+ public static final String PATH = "path";
+ public static final String DEVELOP = "develop";
+ public static final String EXTRAS = "extras";
+
+ protected TomlUtils() {
+ // prevent instantiation of all static class
+ }
+
+ public static boolean representsLocalDevelopmentVersion(Object rawData) {
+ boolean localDevelopmentVersion = false;
+
+ if (rawData instanceof CommentedConfig) {
+ CommentedConfig config = (CommentedConfig) rawData;
+ if (!config.contains(VERSION)) {
+ localDevelopmentVersion = true;
+ }
+
+ }
+
+ return localDevelopmentVersion;
+ }
+
+ /**
+ * Handles escaping with double quotes only if the value is not an inline table.
+ *
+ * @param valueToEscape value to potentially escape
+ * @return value ready to write to toml file
+ */
+ public static String escapeTomlRightHandSide(String valueToEscape) {
+ return (!valueToEscape.contains("{")) ? DOUBLE_QUOTE + valueToEscape + DOUBLE_QUOTE : valueToEscape;
+ }
+
+ public static void writeTomlFile(File pyProjectTomlFile, String fileContent) throws IOException {
+ if (fileContent != null) {
+ try (Writer writer = new FileWriter(pyProjectTomlFile)) {
+ writer.write(fileContent);
+ }
+ }
+ }
+
+ public static String convertCommentedConfigToToml(CommentedConfig config) {
+ int valuesRemaining = config.size();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+
+ if (config.get(PATH) != null) {
+ sb.append(PATH).append(" = \"").append(config.get(PATH).toString()).append("\"");
+ valuesRemaining--;
+ addCommaBetweenValues(valuesRemaining, sb);
+ }
+
+ if (config.get(DEVELOP) != null) {
+ sb.append(DEVELOP).append(" = ").append(config.get(DEVELOP).toString());
+ valuesRemaining--;
+ addCommaBetweenValues(valuesRemaining, sb);
+ }
+
+ if (config.get(VERSION) != null) {
+ sb.append(VERSION).append(" = \"").append(config.get(VERSION).toString()).append("\"");
+ List extras = config.get(EXTRAS);
+ if (CollectionUtils.isNotEmpty(extras)) {
+ sb.append(", ").append(EXTRAS).append(" = [");
+ // NB: if we expect more complex values, such as multiple extras, more work would need to be done for
+ // both consistent formatting and comparison of these values. However, at the time of initially writing
+ // this method, there isn't a clear demand signal, so we are going to KISS for now:
+
+ for (int i = 0; i < extras.size(); i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append("\"").append(extras.get(i)).append("\"");
+ }
+ sb.append("]");
+ }
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ private static void addCommaBetweenValues(int valuesRemaining, StringBuilder sb) {
+ if (valuesRemaining > 0) {
+ sb.append(", ");
+ }
+ }
+
+}
diff --git a/habushu-maven-plugin/src/main/resources/META-INF/plexus/components.xml b/habushu-maven-plugin/src/main/resources/META-INF/plexus/components.xml
index b4a946e..8aec40d 100644
--- a/habushu-maven-plugin/src/main/resources/META-INF/plexus/components.xml
+++ b/habushu-maven-plugin/src/main/resources/META-INF/plexus/components.xml
@@ -13,8 +13,11 @@
default
- org.technologybrewery.habushu:habushu-maven-plugin:${project.version}:validate-pyenv-and-poetry
- org.technologybrewery.habushu:habushu-maven-plugin:${project.version}:initialize-habushu
+
+ org.technologybrewery.habushu:habushu-maven-plugin:${project.version}:initialize-habushu,
+ org.technologybrewery.habushu:habushu-maven-plugin:${project.version}:validate-pyenv-and-poetry,
+ org.technologybrewery.habushu:habushu-maven-plugin:${project.version}:baton-migrate
+
org.technologybrewery.habushu:habushu-maven-plugin:${project.version}:install-dependencies
org.technologybrewery.habushu:habushu-maven-plugin:${project.version}:format-python
org.technologybrewery.habushu:habushu-maven-plugin:${project.version}:behave-bdd-test
diff --git a/habushu-maven-plugin/src/main/resources/migrations.json b/habushu-maven-plugin/src/main/resources/migrations.json
new file mode 100644
index 0000000..4371f69
--- /dev/null
+++ b/habushu-maven-plugin/src/main/resources/migrations.json
@@ -0,0 +1,11 @@
+[
+ {
+ "name": "custom-monorepo-group-migration",
+ "implementation": "org.technologybrewery.habushu.migration.CustomMonorepoGroupMigration",
+ "fileSets": [
+ {
+ "includes": ["pyproject.toml"]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/habushu-maven-plugin/src/test/java/org/technologybrewery/habushu/DependencyManagementSteps.java b/habushu-maven-plugin/src/test/java/org/technologybrewery/habushu/DependencyManagementSteps.java
index 2d2d78e..b2c9856 100644
--- a/habushu-maven-plugin/src/test/java/org/technologybrewery/habushu/DependencyManagementSteps.java
+++ b/habushu-maven-plugin/src/test/java/org/technologybrewery/habushu/DependencyManagementSteps.java
@@ -6,7 +6,6 @@
import io.cucumber.java.en.When;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringEscapeUtils;
-import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Assertions;
import java.io.BufferedReader;
diff --git a/habushu-maven-plugin/src/test/java/org/technologybrewery/habushu/migration/MonorepoMigrationSteps.java b/habushu-maven-plugin/src/test/java/org/technologybrewery/habushu/migration/MonorepoMigrationSteps.java
new file mode 100644
index 0000000..4884002
--- /dev/null
+++ b/habushu-maven-plugin/src/test/java/org/technologybrewery/habushu/migration/MonorepoMigrationSteps.java
@@ -0,0 +1,112 @@
+package org.technologybrewery.habushu.migration;
+
+import com.electronwill.nightconfig.core.Config;
+import com.electronwill.nightconfig.core.file.FileConfig;
+import io.cucumber.java.en.Given;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+import org.technologybrewery.habushu.util.TomlUtils;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class MonorepoMigrationSteps {
+
+ private File testTomlFileDirectory = new File("./target/test-classes/migration/monorepo");
+ private File pyProjectToml;
+
+ private boolean shouldExecute;
+
+ private boolean executionSucceeded;
+
+ @Given("an existing pyproject.toml file with two monorepo dependencies in the tool.poetry.dependencies group")
+ public void an_existing_pyproject_toml_file_with_two_monorepo_dependencies_in_the_tool_poetry_dependencies_group() {
+ pyProjectToml = new File(testTomlFileDirectory, "with-dependencies-no-monorepo-group.toml");
+ }
+
+ @Given("an existing pyproject.toml file with two monorepo dependencies each in the tool.poetry.dependencies and tool.poetry.group.monorepo.dependencies groups")
+ public void an_existing_pyproject_toml_file_with_two_monorepo_dependencies_each_in_the_tool_poetry_dependencies_and_tool_poetry_group_monorepo_dependencies_groups() {
+ pyProjectToml = new File(testTomlFileDirectory, "with-dependencies-in-both-groups.toml");
+ }
+
+ @Given("an existing pyproject.toml without any monorepo dependencies")
+ public void an_existing_pyproject_toml_without_any_monorepo_dependencies() {
+ pyProjectToml = new File(testTomlFileDirectory, "no-monorepo-dependencies-present.toml");
+ }
+
+
+ @Given("an existing pyproject.toml file with three monorepo dependencies already in the tool.poetry.group.monorepo.dependencies group")
+ public void an_existing_pyproject_toml_file_with_three_monorepo_dependencies_already_in_the_tool_poetry_group_monorepo_dependencies_group() {
+ pyProjectToml = new File(testTomlFileDirectory, "with-dependencies-already-in-monorepo-group.toml");
+ }
+
+ @When("Habushu migrations execute")
+ public void habushu_migrations_execute() {
+ CustomMonorepoGroupMigration migration = new CustomMonorepoGroupMigration();
+ shouldExecute = migration.shouldExecuteOnFile(pyProjectToml);
+ executionSucceeded = (shouldExecute) ? migration.performMigration(pyProjectToml) : false;
+ }
+
+ @Then("{int} pyproject.toml monorepo dependencies exist in the tool.poetry.group.monorepo.dependencies group")
+ public void pyproject_toml_monorepo_dependencies_exist_in_the_tool_poetry_group_monorepo_dependencies_group(Integer expectedMonorepoGroupDependencies) {
+ verifyExeuctionOccurred();
+ try (FileConfig tomlFileConfig = FileConfig.of(pyProjectToml)) {
+ tomlFileConfig.load();
+
+ Optional toolPoetryMonorepoDependencies = tomlFileConfig.getOptional(TomlUtils.TOOL_POETRY_GROUP_MONOREPO_DEPENDENCIES);
+ int numberOfMonorepoDependenciesInMonorepoGroup = getNumberOfMonorepoChildren(toolPoetryMonorepoDependencies);
+ assertEquals(expectedMonorepoGroupDependencies, numberOfMonorepoDependenciesInMonorepoGroup,
+ expectedMonorepoGroupDependencies + " monorepo dependencies should exist in monorepo group!");
+
+
+ }
+ }
+ @Then("{int} pyproject.toml monorepo dependencies exist in the tool.poetry.dependencies group")
+ public void pyproject_toml_monorepo_dependencies_exist_in_the_tool_poetry_dependencies_group(Integer expectedMainGroupDependencies) {
+ verifyExeuctionOccurred();
+ try (FileConfig tomlFileConfig = FileConfig.of(pyProjectToml)) {
+ tomlFileConfig.load();
+
+ Optional toolPoetryDependencies = tomlFileConfig.getOptional(TomlUtils.TOOL_POETRY_DEPENDENCIES);
+ int numberOfMonorepoDependenciesInMainGroup = getNumberOfMonorepoChildren(toolPoetryDependencies);
+ assertEquals(expectedMainGroupDependencies, numberOfMonorepoDependenciesInMainGroup,
+ expectedMainGroupDependencies + " monorepo dependencies should remain in main group!");
+ }
+ }
+
+ @Then("no migration was performed")
+ public void no_migration_was_performed() {
+ assertFalse(shouldExecute, "Migration execution should have been skipped!");
+ }
+
+
+ private static int getNumberOfMonorepoChildren(Optional tomlElement) {
+ int numberOfMonorepoDependencies = 0;
+ if (tomlElement.isPresent()) {
+ Config foundDependencies = tomlElement.get();
+ Map dependencyMap = foundDependencies.valueMap();
+
+ for (Map.Entry dependency : dependencyMap.entrySet()) {
+ String packageName = dependency.getKey();
+ Object packageRhs = dependency.getValue();
+ if (TomlUtils.representsLocalDevelopmentVersion(packageRhs)) {
+ numberOfMonorepoDependencies++;
+ }
+ }
+ }
+
+ return numberOfMonorepoDependencies;
+ }
+
+
+ private void verifyExeuctionOccurred() {
+ assertTrue(shouldExecute, "Migration should have been selected to execute!");
+ assertTrue(executionSucceeded, "Migration should have executed successfully!");
+ }
+
+}
diff --git a/habushu-maven-plugin/src/test/resources/migration/monorepo/no-monorepo-dependencies-present.toml b/habushu-maven-plugin/src/test/resources/migration/monorepo/no-monorepo-dependencies-present.toml
new file mode 100644
index 0000000..7ff8c7e
--- /dev/null
+++ b/habushu-maven-plugin/src/test/resources/migration/monorepo/no-monorepo-dependencies-present.toml
@@ -0,0 +1,16 @@
+[tool.poetry]
+name = "no-monorepo-dependencies-present"
+version = "2.9.0.dev"
+description = "Test monorepo migration does not impact toml file with no applicable changes"
+authors = ["Test "]
+license = "MIT License"
+
+[tool.poetry.dependencies]
+python = "^3.11"
+
+[tool.poetry.dev-dependencies]
+black = "^18.0.0"
+
+[build-system]
+requires = ["poetry-core>=1.6.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-already-in-monorepo-group.toml b/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-already-in-monorepo-group.toml
new file mode 100644
index 0000000..75ae26b
--- /dev/null
+++ b/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-already-in-monorepo-group.toml
@@ -0,0 +1,21 @@
+[tool.poetry]
+name = "with-dependencies-already-in-monorepo-group"
+version = "2.9.0.dev"
+description = "Test moving monorepo dependecies when monorepo group already exists and no more chages are needed"
+authors = ["Test "]
+license = "MIT License"
+
+[tool.poetry.dependencies]
+python = "^3.11"
+
+[tool.poetry.group.monorepo.dependencies]
+some-local-monorepo-dependencies-1 = {path = "../some-local-monorepo-dependencies-1", develop = true}
+some-local-monorepo-dependencies-2 = {path = "../some-local-monorepo-dependencies-2", develop = true}
+some-local-monorepo-dependencies-3 = {path = "../some-local-monorepo-dependencies-3", develop = true}
+
+[tool.poetry.dev-dependencies]
+black = "^18.0.0"
+
+[build-system]
+requires = ["poetry-core>=1.6.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-in-both-groups.toml b/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-in-both-groups.toml
new file mode 100644
index 0000000..2829763
--- /dev/null
+++ b/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-in-both-groups.toml
@@ -0,0 +1,22 @@
+[tool.poetry]
+name = "with-dependencies-is-both-groups"
+version = "2.9.0.dev"
+description = "Test moving monorepo dependecies from main to monorepo group when monorepo group already exists"
+authors = ["Test "]
+license = "MIT License"
+
+[tool.poetry.dependencies]
+python = "^3.11"
+some-local-monorepo-dependencies-1 = {path = "../some-local-monorepo-dependencies-1", develop = true}
+some-local-monorepo-dependencies-2 = {path = "../some-local-monorepo-dependencies-2", develop = true}
+
+[tool.poetry.group.monorepo.dependencies]
+some-local-monorepo-dependencies-3 = {path = "../some-local-monorepo-dependencies-3", develop = true}
+some-local-monorepo-dependencies-4 = {path = "../some-local-monorepo-dependencies-4", develop = true}
+
+[tool.poetry.dev-dependencies]
+black = "^18.0.0"
+
+[build-system]
+requires = ["poetry-core>=1.6.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-no-monorepo-group.toml b/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-no-monorepo-group.toml
new file mode 100644
index 0000000..dde98b2
--- /dev/null
+++ b/habushu-maven-plugin/src/test/resources/migration/monorepo/with-dependencies-no-monorepo-group.toml
@@ -0,0 +1,18 @@
+[tool.poetry]
+name = "with-dependencies-no-monorepo-group"
+version = "2.9.0.dev"
+description = "Test moving monorepo dependecies from main to monorepo group"
+authors = ["Test "]
+license = "MIT License"
+
+[tool.poetry.dependencies]
+python = "^3.11"
+some-local-monorepo-dependencies-1 = {path = "../some-local-monorepo-dependencies-1", develop = true}
+some-local-monorepo-dependencies-2 = {path = "../some-local-monorepo-dependencies-2", develop = true}
+
+[tool.poetry.dev-dependencies]
+black = "^18.0.0"
+
+[build-system]
+requires = ["poetry-core>=1.6.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/habushu-maven-plugin/src/test/resources/specifications/monorepo-migrations.feature b/habushu-maven-plugin/src/test/resources/specifications/monorepo-migrations.feature
new file mode 100644
index 0000000..da4bf80
--- /dev/null
+++ b/habushu-maven-plugin/src/test/resources/specifications/monorepo-migrations.feature
@@ -0,0 +1,23 @@
+Feature: Test automatic migration of monorepo dependencies into their own pyproject.toml group
+
+ Scenario: Migrate monorepo dependencies when existing monorepo group DOES NOT exists
+ Given an existing pyproject.toml file with two monorepo dependencies in the tool.poetry.dependencies group
+ When Habushu migrations execute
+ Then 2 pyproject.toml monorepo dependencies exist in the tool.poetry.group.monorepo.dependencies group
+ And 0 pyproject.toml monorepo dependencies exist in the tool.poetry.dependencies group
+
+ Scenario: Migrate monorepo dependencies when existing monorepo group DOES exists
+ Given an existing pyproject.toml file with two monorepo dependencies each in the tool.poetry.dependencies and tool.poetry.group.monorepo.dependencies groups
+ When Habushu migrations execute
+ Then 4 pyproject.toml monorepo dependencies exist in the tool.poetry.group.monorepo.dependencies group
+ And 0 pyproject.toml monorepo dependencies exist in the tool.poetry.dependencies group
+
+ Scenario: Migrate monorepo dependencies does not change a file with non applicable dependencies
+ Given an existing pyproject.toml without any monorepo dependencies
+ When Habushu migrations execute
+ Then no migration was performed
+
+ Scenario: Migrate monorepo dependencies does not change a file with all monorepo dependencies already in monorepo group
+ Given an existing pyproject.toml file with three monorepo dependencies already in the tool.poetry.group.monorepo.dependencies group
+ When Habushu migrations execute
+ Then no migration was performed
\ No newline at end of file