Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/me/itzg/helpers/McImageHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import me.itzg.helpers.singles.Asciify;
import me.itzg.helpers.singles.HashCommand;
import me.itzg.helpers.singles.NetworkInterfacesCommand;
import me.itzg.helpers.singles.TestLoggingCommand;
import me.itzg.helpers.singles.YamlPathCmd;
import me.itzg.helpers.sync.InterpolateCommand;
import me.itzg.helpers.sync.MulitCopyCommand;
Expand Down Expand Up @@ -82,6 +83,7 @@
SetPropertiesCommand.class,
Sync.class,
SyncAndInterpolate.class,
TestLoggingCommand.class,
YamlPathCmd.class,
VanillaTweaksCommand.class,
}
Expand Down
46 changes: 33 additions & 13 deletions src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public class ModrinthCommand implements Callable<Integer> {
description = "Default is ${DEFAULT-VALUE}\nValid values: ${COMPLETION-CANDIDATES}")
DownloadDependencies downloadDependencies;

@Option(names = "--skip-existing", defaultValue = "${env:MODRINTH_SKIP_EXISTING}")
boolean skipExisting = true;

@Option(names = "--skip-up-to-date", defaultValue = "${env:MODRINTH_SKIP_UP_TO_DATE}")
boolean skipUpToDate = true;

public enum DownloadDependencies {
NONE,
REQUIRED,
Expand Down Expand Up @@ -151,30 +157,43 @@ private ModrinthManifest loadManifest() throws IOException {
return Manifests.load(outputDirectory, ModrinthManifest.ID, ModrinthManifest.class);
}

private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient, Version version) {
private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient, Project project, Version version) {
log.debug("Expanding dependencies of version={}", version);
return version.getDependencies().stream()
.filter(this::filterDependency)
.filter(dep -> projectsProcessed.add(dep.getProjectId()))
.flatMap(dep -> {
projectsProcessed.add(dep.getProjectId());

final Version depVersion;
if (dep.getVersionId() == null) {
log.debug("Fetching versions of dep={} and picking", dep);
depVersion = pickVersion(
getVersionsForProject(modrinthApiClient, dep.getProjectId())
try {
if (dep.getVersionId() == null) {
log.debug("Fetching versions of dep={} and picking", dep);
depVersion = pickVersion(
getVersionsForProject(modrinthApiClient, dep.getProjectId())
);
}
else {
log.debug("Fetching version for dep={}", dep);
depVersion = modrinthApiClient.getVersionFromId(dep.getVersionId())
.block();
}
} catch (GenericException e) {
throw new GenericException(String.format("Failed to expand %s of project '%s'",
dep, project.getTitle()), e);
} catch (NoFilesAvailableException e) {
throw new InvalidParameterException(
String.format("No matching files for %s of project '%s': %s",
dep, project.getTitle(), e.getMessage()
), e
);
}
else {
log.debug("Fetching version for dep={}", dep);
depVersion = modrinthApiClient.getVersionFromId(dep.getVersionId())
.block();
}

if (depVersion != null) {
log.debug("Resolved version={} for dep={}", depVersion.getVersionNumber(), dep);
return Stream.concat(
Stream.of(depVersion),
expandDependencies(modrinthApiClient, depVersion)
expandDependencies(modrinthApiClient, project, depVersion)
)
.peek(expandedVer -> log.debug("Expanded dependency={} into version={}", dep, expandedVer));
}
Expand Down Expand Up @@ -243,7 +262,8 @@ private Path download(boolean isDatapack, VersionFile versionFile) {
return fetch(URI.create(versionFile.getUrl()))
.userAgentCommand("modrinth")
.toFile(outPath)
.skipUpToDate(true)
.skipExisting(skipExisting)
.skipUpToDate(skipUpToDate)
.handleStatus(Fetch.loggingDownloadStatusHandler(log))
.execute();
} catch (IOException e) {
Expand Down Expand Up @@ -293,7 +313,7 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project

return Stream.concat(
Stream.of(version),
expandDependencies(modrinthApiClient, version)
expandDependencies(modrinthApiClient, project, version)
)
.map(ModrinthApiClient::pickVersionFile)
.map(versionFile -> download(isDatapack, versionFile))
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/me/itzg/helpers/modrinth/model/ProjectType.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.itzg.helpers.modrinth.model;

public enum ProjectType {
mod,
modpack
mod,
modpack,
resourcepack,
}
21 changes: 21 additions & 0 deletions src/main/java/me/itzg/helpers/singles/TestLoggingCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package me.itzg.helpers.singles;

import lombok.extern.slf4j.Slf4j;
import picocli.CommandLine.Command;
import picocli.CommandLine.ExitCode;

import java.util.concurrent.Callable;

@Command(name = "test-logging-levels")
@Slf4j
public class TestLoggingCommand implements Callable<Integer> {

@Override
public Integer call() throws Exception {
log.error("This is an error");
log.warn("This is a warning");
log.info("This is an info");
log.debug("This is a debug");
return ExitCode.OK;
}
}
83 changes: 77 additions & 6 deletions src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
Expand All @@ -14,8 +15,12 @@
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import java.nio.file.Path;
import java.util.function.Consumer;
import me.itzg.helpers.LatchingExecutionExceptionHandler;
import me.itzg.helpers.errors.InvalidParameterException;
import me.itzg.helpers.json.ObjectMappers;
import me.itzg.helpers.modrinth.ModrinthCommand.DownloadDependencies;
import me.itzg.helpers.modrinth.model.Project;
import me.itzg.helpers.modrinth.model.ProjectType;
import org.assertj.core.api.AbstractPathAssert;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -108,12 +113,7 @@ void downloadsOnlyRequestedDependencyTypes(ModrinthCommand.DownloadDependencies
stubVersionRequest(requiredDepProjectId, requiredVersionId, deps -> {});
stubVersionRequest(optionalDepProjectId, optionalVersionId, deps -> {});

stubFor(get(urlPathMatching("/cdn/(.+)"))
.willReturn(aResponse()
.withBody("{{request.pathSegments.[1]}}")
.withTransformers("response-template")
)
);
stubDownload();

final int exitCode = new CommandLine(
new ModrinthCommand()
Expand Down Expand Up @@ -150,6 +150,47 @@ else if (downloadDependencies == DownloadDependencies.OPTIONAL) {
}
}

@Test
void failsWhenNoDependenciesForModLoader(@TempDir Path tempDir) throws JsonProcessingException {
final String projectId = randomAlphanumeric(6);
final String projectSlug = randomAlphabetic(5);
final String versionId = randomAlphanumeric(6);
final String requiredDepProjectId = randomAlphanumeric(6);

stubProjectBulkRequest(projectId, projectSlug);

stubVersionRequest(projectId, versionId, deps -> {
deps.addObject()
.put("project_id", requiredDepProjectId)
.put("dependency_type", "required");
});
stubVersionRequestEmptyResponse(requiredDepProjectId);
stubGetProject(requiredDepProjectId, new Project().setProjectType(ProjectType.resourcepack));

stubDownload();

final LatchingExecutionExceptionHandler executionExceptionHandler = new LatchingExecutionExceptionHandler();

final int exitCode = new CommandLine(
new ModrinthCommand()
)
.setExecutionExceptionHandler(executionExceptionHandler)
.execute(
"--api-base-url", wm.getRuntimeInfo().getHttpBaseUrl(),
"--output-directory", tempDir.toString(),
"--game-version", "1.21.1",
"--loader", "paper",
"--projects", projectId,
"--download-dependencies", DownloadDependencies.REQUIRED.name()
);

assertThat(exitCode).isNotEqualTo(ExitCode.OK);

assertThat(executionExceptionHandler.getExecutionException())
.isInstanceOf(InvalidParameterException.class)
.hasCauseInstanceOf(NoFilesAvailableException.class);
}

@Test
void errorWhenNoApplicableVersion(@TempDir Path tempDir) {
stubFor(
Expand Down Expand Up @@ -325,6 +366,15 @@ private void stubProjectBulkRequest(String projectId, String projectSlug) {
);
}

private void stubGetProject(String projectIdOrSlug, Project project) throws JsonProcessingException {
stubFor(get(urlPathEqualTo("/v2/project/"+projectIdOrSlug))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(objectMapper.writeValueAsBytes(project))
)
);
}

private void stubVersionRequest(String projectId, String versionId, Consumer<ArrayNode> depsAdder) {
final ArrayNode versionResp = objectMapper.createArrayNode();
final ObjectNode versionNode = versionResp
Expand All @@ -347,7 +397,28 @@ private void stubVersionRequest(String projectId, String versionId, Consumer<Arr
.withJsonBody(versionResp)
)
);
}

private void stubVersionRequestEmptyResponse(String projectId) {
final ArrayNode versionResp = objectMapper.createArrayNode();

stubFor(get(urlPathEqualTo("/v2/project/" + projectId + "/version"))
.withQueryParam("loaders", equalTo("[\"paper\",\"spigot\"]"))
.withQueryParam("game_versions", equalTo("[\"1.21.1\"]"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withJsonBody(versionResp)
)
);
}

private static void stubDownload() {
stubFor(get(urlPathMatching("/cdn/(.+)"))
.willReturn(aResponse()
.withBody("{{request.pathSegments.[1]}}")
.withTransformers("response-template")
)
);
}

private static void setupStubs() {
Expand Down