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
52 changes: 52 additions & 0 deletions src/main/java/io/moderne/devcenter/DevCenter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.moderne.devcenter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.moderne.devcenter.table.SecurityIssues;
import io.moderne.devcenter.table.UpgradesAndMigrations;
import lombok.EqualsAndHashCode;
Expand All @@ -29,6 +31,7 @@

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
Expand Down Expand Up @@ -122,6 +125,55 @@ public Card getCard(String name) {
throw new IllegalArgumentException("No card found with name: " + name);
}

/**
* Returns a stable JSON description of this DevCenter's structure for
* cross-classloader consumption by tools (e.g. the Moderne CLI). The
* format carries an {@code apiVersion} so consumers can detect schema
* compatibility. Schema (v1):
* <pre>{@code
* {
* "apiVersion": "v1",
* "upgradesAndMigrations": [
* {"name": "...", "fixRecipeId": "...", "measures": ["...", ...]},
* ...
* ],
* "security": {"name": "...", "fixRecipeId": "...", "measures": [...]} | null
* }
* }</pre>
* Card and measure ordering is preserved.
*/
public String getSpec() {
Map<String, Object> spec = new LinkedHashMap<>();
spec.put("apiVersion", "v1");

List<Map<String, Object>> upgrades = new ArrayList<>();
for (Card card : getUpgradesAndMigrations()) {
upgrades.add(cardToSpec(card));
}
spec.put("upgradesAndMigrations", upgrades);

Card securityCard = getSecurity();
spec.put("security", securityCard == null ? null : cardToSpec(securityCard));

try {
return new ObjectMapper().writeValueAsString(spec);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize DevCenter spec", e);
}
}

private static Map<String, Object> cardToSpec(Card card) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("name", card.getName());
map.put("fixRecipeId", card.getFixRecipeId());
List<String> measureNames = new ArrayList<>();
for (DevCenterMeasure m : card.getMeasures()) {
measureNames.add(m.getName());
}
map.put("measures", measureNames);
return map;
}

private List<Card> getUpgradesAndMigrationsRecursive(Recipe recipe, List<Card> upgradesAndMigrations) {
try {
Class<?> umcClass = Class.forName(
Expand Down
52 changes: 52 additions & 0 deletions src/test/java/io/moderne/devcenter/DevCenterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.moderne.devcenter;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.siegmar.fastcsv.reader.CommentStrategy;
import de.siegmar.fastcsv.reader.CsvReader;
import de.siegmar.fastcsv.reader.NamedCsvRecord;
Expand All @@ -41,6 +43,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
Expand Down Expand Up @@ -132,6 +135,55 @@ void validateStandAloneDevCenterRecipe() throws Exception {
devCenter.validate();
}

@Test
void getSpecMatchesStarterDevCenter() throws Exception {
var devCenter = new DevCenter(starterDevCenter);
JsonNode spec = new ObjectMapper().readTree(devCenter.getSpec());

assertThat(spec.get("apiVersion").asText()).isEqualTo("v1");

JsonNode upgrades = spec.get("upgradesAndMigrations");
assertThat(upgrades.isArray()).isTrue();
assertThat(upgrades.size()).isEqualTo(3);

JsonNode firstCard = upgrades.get(0);
assertThat(firstCard.get("name").asText()).isEqualTo(devCenter.getUpgradesAndMigrations().getFirst().getName());
List<String> measureNames = new ArrayList<>();
firstCard.get("measures").forEach(m -> measureNames.add(m.asText()));
assertThat(measureNames).containsExactly("Major", "Minor", "Patch", "Completed");

JsonNode security = spec.get("security");
assertThat(security.isNull()).isFalse();
assertThat(security.get("name").asText()).isEqualTo(devCenter.getSecurity().getName());
List<String> securityMeasures = new ArrayList<>();
security.get("measures").forEach(m -> securityMeasures.add(m.asText()));
assertThat(securityMeasures).contains("Zip slip");
}

@Test
void getSpecOmitsSecurityWhenAbsent() throws Exception {
//language=yaml
var recipe = """
type: specs.openrewrite.org/v1beta/recipe
name: io.moderne.devcenter.JavaOnly
displayName: Just an upgrade card
description: Upgrade Java version
recipeList:
- io.moderne.devcenter.JavaVersionUpgrade:
majorVersion: 21
""";
Recipe r = Environment.builder()
.load(new YamlResourceLoader(new ByteArrayInputStream(recipe.getBytes(StandardCharsets.UTF_8)),
URI.create("rewrite.yml"), new Properties()))
.build()
.activateRecipes("io.moderne.devcenter.JavaOnly");

JsonNode spec = new ObjectMapper().readTree(new DevCenter(r).getSpec());
assertThat(spec.get("apiVersion").asText()).isEqualTo("v1");
assertThat(spec.get("upgradesAndMigrations").size()).isEqualTo(1);
assertThat(spec.get("security").isNull()).isTrue();
}

@Test
void uniqueCardNames() {
//language=yaml
Expand Down
Loading