diff --git a/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java b/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java index 7bc8e0b2..82d9dac8 100644 --- a/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java +++ b/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java @@ -111,23 +111,38 @@ public enum DownloadDependencies { public Integer call() throws Exception { Files.createDirectories(outputDirectory); - final ModrinthManifest prevManifest = loadManifest(); + // Unlike @Parameters, the argument of @Option could be a list with a single empty string + // when --projects="". So, trim away any blanks. + final List trimmedProjects = projects == null ? Collections.emptyList() : + projects.stream() + .filter(s -> !s.trim().isEmpty()) + .collect(Collectors.toList()); - final List outputFiles = processProjects(projects); + final List outputFiles = processProjects(trimmedProjects); final ModrinthManifest newManifest = ModrinthManifest.builder() .files(Manifests.relativizeAll(outputDirectory, outputFiles)) .projects(projects) .build(); + final ModrinthManifest prevManifest = loadManifest(); Manifests.cleanup(outputDirectory, prevManifest, newManifest, log); - Manifests.save(outputDirectory, ModrinthManifest.ID, newManifest); + if (!outputFiles.isEmpty()) { + Manifests.save(outputDirectory, ModrinthManifest.ID, newManifest); + } + else { + Manifests.remove(outputDirectory, ModrinthManifest.ID); + } return ExitCode.OK; } private List processProjects(List projects) { + if (projects == null || projects.isEmpty()) { + return Collections.emptyList(); + } + try (ModrinthApiClient modrinthApiClient = new ModrinthApiClient(baseUrl, "modrinth", sharedFetchArgs.options())) { //noinspection DataFlowIssue since it thinks block() may return null return diff --git a/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java b/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java index 8e495f4b..24977e2c 100644 --- a/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java +++ b/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java @@ -3,8 +3,6 @@ import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemErrNormalized; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; -import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.core.JsonProcessingException; @@ -24,6 +22,7 @@ import me.itzg.helpers.modrinth.ModrinthCommand.DownloadDependencies; import me.itzg.helpers.modrinth.model.Project; import me.itzg.helpers.modrinth.model.ProjectType; +import org.apache.commons.lang3.RandomStringUtils; import org.assertj.core.api.AbstractPathAssert; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -48,6 +47,8 @@ class ModrinthCommandTest { .configureStaticDsl(true) .build(); + private final RandomStringUtils randomStringUtils = RandomStringUtils.insecure(); + @Test void commaNewlineDelimited(@TempDir Path tempDir) { setupStubs(); @@ -153,6 +154,15 @@ else if (downloadDependencies == DownloadDependencies.OPTIONAL) { } } + private String randomAlphanumeric(int n) { + return randomStringUtils.nextAlphanumeric(n); + } + + @SuppressWarnings("SameParameterValue") + private String randomAlphabetic(int n) { + return randomStringUtils.nextAlphabetic(n); + } + @Test void failsWhenNoDependenciesForModLoader(@TempDir Path tempDir) throws JsonProcessingException { final String projectId = randomAlphanumeric(6); @@ -162,11 +172,11 @@ void failsWhenNoDependenciesForModLoader(@TempDir Path tempDir) throws JsonProce stubProjectBulkRequest(projectId, projectSlug); - stubVersionRequest(projectId, versionId, deps -> { + stubVersionRequest(projectId, versionId, deps -> deps.addObject() - .put("project_id", requiredDepProjectId) - .put("dependency_type", "required"); - }); + .put("project_id", requiredDepProjectId) + .put("dependency_type", "required") + ); stubVersionRequestEmptyResponse(requiredDepProjectId, "paper"); stubVersionRequestEmptyResponse(requiredDepProjectId, "spigot"); stubGetProject(requiredDepProjectId, new Project().setProjectType(ProjectType.resourcepack)); @@ -277,7 +287,6 @@ void handlesDatapacksSpecificVersion(boolean absoluteWorldDir, @TempDir Path tem stubProjectBulkRequest(projectId, projectSlug); - final ArrayNode versionResp = objectMapper.createArrayNode(); final ObjectNode versionNode = objectMapper.createObjectNode() .put("id", versionId) .put("project_id", projectId) @@ -412,6 +421,51 @@ void usingListingFile(@TempDir Path tempDir) throws Exception { assertThat(tempDir.resolve("mods/cloth-config-8.3.103-fabric.jar")).exists(); } + @Test + void removesAllWhenEmpty(@TempDir Path tempDir) { + setupStubs(); + + { + final int exitCode = new CommandLine( + new ModrinthCommand() + ) + .execute( + "--api-base-url", wm.getRuntimeInfo().getHttpBaseUrl(), + "--output-directory", tempDir.toString(), + "--game-version", "1.19.2", + "--loader", "fabric", + "--projects", "fabric-api,cloth-config" + ); + + assertThat(exitCode).isEqualTo(ExitCode.OK); + + assertThat(tempDir.resolve("mods/fabric-api-0.76.1+1.19.2.jar")).exists(); + assertThat(tempDir.resolve("mods/cloth-config-8.3.103-fabric.jar")).exists(); + } + + // now process an empty projects list to uninstall all + { + final int exitCode = new CommandLine( + new ModrinthCommand() + ) + .execute( + "--api-base-url", wm.getRuntimeInfo().getHttpBaseUrl(), + "--output-directory", tempDir.toString(), + "--game-version", "1.19.2", + "--loader", "fabric", + "--projects", "" + ); + + assertThat(exitCode).isEqualTo(ExitCode.OK); + + // assert files now do NOT exist + assertThat(tempDir.resolve("mods/fabric-api-0.76.1+1.19.2.jar")) + .doesNotExist(); + assertThat(tempDir.resolve("mods/cloth-config-8.3.103-fabric.jar")) + .doesNotExist(); + } + } + @NotNull private static RequestPatternBuilder projectVersionsRequest(String projectId) { return getRequestedFor(urlPathEqualTo("/v2/project/" + projectId + "/version"));