Port to 1.19.4 and libjf-config. Also remove the unused API as mods can still use mixin
ci/woodpecker/push/jfmod Pipeline is pending Details
ci/woodpecker/tag/jfmod Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2023-03-14 20:58:21 +01:00
parent 79ff7b6279
commit e9dd5644e8
Signed by: Johannes
GPG Key ID: E76429612C2929F4
18 changed files with 151 additions and 219 deletions

View File

@ -6,9 +6,11 @@ plugins {
dependencies { dependencies {
include(modImplementation("io.gitlab.jfronny.libjf:libjf-base:${prop("libjf_version")}")!!) // for JfCommons include(modImplementation("io.gitlab.jfronny.libjf:libjf-base:${prop("libjf_version")}")!!) // for JfCommons
include(modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v1:${prop("libjf_version")}")!!) // for JfCommons
include(modImplementation(fabricApi.module("fabric-resource-loader-v0", prop("fabric_version")))!!) include(modImplementation(fabricApi.module("fabric-resource-loader-v0", prop("fabric_version")))!!)
// Dev env // Dev env
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-ui-tiny-v1:${prop("libjf_version")}")
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil:${prop("libjf_version")}") modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil:${prop("libjf_version")}")
modLocalRuntime("com.terraformersmc:modmenu:${prop("modmenu_version")}") modLocalRuntime("com.terraformersmc:modmenu:${prop("modmenu_version")}")
} }

View File

@ -1,6 +1,6 @@
# https://fabricmc.net/develop # https://fabricmc.net/develop
minecraft_version=1.19.4-pre1 minecraft_version=1.19.4
yarn_mappings=build.6 yarn_mappings=build.1
loader_version=0.14.17 loader_version=0.14.17
maven_group=io.gitlab.jfronny maven_group=io.gitlab.jfronny
@ -8,6 +8,6 @@ archives_base_name=resclone
modrinth_id=resclone modrinth_id=resclone
fabric_version=0.75.1+1.19.4 fabric_version=0.75.3+1.19.4
libjf_version=3.5.0-SNAPSHOT libjf_version=3.6.0
modmenu_version=6.1.0-beta.3 modmenu_version=6.1.0-rc.1

View File

@ -2,11 +2,10 @@ package io.gitlab.jfronny.resclone;
import io.gitlab.jfronny.commons.log.Logger; import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders; import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders;
import io.gitlab.jfronny.resclone.api.*;
import io.gitlab.jfronny.resclone.data.PackMetaLoaded; import io.gitlab.jfronny.resclone.data.PackMetaLoaded;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded; import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
import io.gitlab.jfronny.resclone.processors.RemoveEmptyProcessor; import io.gitlab.jfronny.resclone.fetchers.*;
import io.gitlab.jfronny.resclone.processors.RootPathProcessor; import io.gitlab.jfronny.resclone.processors.*;
import io.gitlab.jfronny.resclone.util.PackUrlCache; import io.gitlab.jfronny.resclone.util.PackUrlCache;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
@ -20,8 +19,7 @@ import java.util.concurrent.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public class Resclone implements ModInitializer, RescloneApi { public class Resclone implements ModInitializer {
public static final Set<PackMetaUnloaded> CONF = new LinkedHashSet<>();
public static final Map<String, PackFetcher> FETCHER_INSTANCES = new LinkedHashMap<>(); public static final Map<String, PackFetcher> FETCHER_INSTANCES = new LinkedHashMap<>();
public static final Set<PackProcessor> PROCESSORS = new LinkedHashSet<>(); public static final Set<PackProcessor> PROCESSORS = new LinkedHashSet<>();
public static final Set<PackMetaLoaded> DOWNLOADED_PACKS = new LinkedHashSet<>(); public static final Set<PackMetaLoaded> DOWNLOADED_PACKS = new LinkedHashSet<>();
@ -38,7 +36,6 @@ public class Resclone implements ModInitializer, RescloneApi {
public static PackUrlCache urlCache; public static PackUrlCache urlCache;
public static int packCount = 0; public static int packCount = 0;
private boolean pruneUnused = false;
@Override @Override
public void onInitialize() { public void onInitialize() {
@ -46,75 +43,66 @@ public class Resclone implements ModInitializer, RescloneApi {
GsonHolders.registerSerializer(); GsonHolders.registerSerializer();
urlCache = new PackUrlCache(getConfigPath().resolve("urlCache.properties")); urlCache = new PackUrlCache(getConfigPath().resolve("urlCache.properties"));
CONF.clear();
FETCHER_INSTANCES.clear(); FETCHER_INSTANCES.clear();
PROCESSORS.clear(); PROCESSORS.clear();
DOWNLOADED_PACKS.clear(); DOWNLOADED_PACKS.clear();
addProcessor(new RootPathProcessor()); //This should be run before any other processor to make sure the path is valid addProcessor(new RootPathProcessor()); //This should be run before any other processor to make sure the path is valid
for (RescloneEntry entry : FabricLoader.getInstance().getEntrypoints(MOD_ID, RescloneEntry.class)) { addFetcher(new BasicFileFetcher());
try { addFetcher(new GitHubFetcher());
entry.init(this); addFetcher(new CurseforgeFetcher());
} catch (Exception e) { addFetcher(new ModrinthFetcher());
Resclone.LOGGER.error("Could not initialize resclone pack supplier", e); if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) addProcessor(new PruneVanillaProcessor());
}
}
addProcessor(new RemoveEmptyProcessor()); addProcessor(new RemoveEmptyProcessor());
reload(); reload();
LOGGER.info("Installed {} resource pack{}.", packCount, packCount == 1 ? "" : "s"); LOGGER.info("Installed {} resource pack{}.", packCount, packCount == 1 ? "" : "s");
} }
@Override
public void addFetcher(PackFetcher fetcher) { public void addFetcher(PackFetcher fetcher) {
FETCHER_INSTANCES.put(fetcher.getSourceTypeName(), fetcher); FETCHER_INSTANCES.put(fetcher.getSourceTypeName(), fetcher);
} }
@Override
public void addProcessor(PackProcessor processor) { public void addProcessor(PackProcessor processor) {
PROCESSORS.add(processor); PROCESSORS.add(processor);
} }
@Override
public void addPack(String fetcher, String pack, String name) { public void addPack(String fetcher, String pack, String name) {
addPack(fetcher, pack, name, false); addPack(fetcher, pack, name, false);
} }
@Override
public void addPack(String fetcher, String pack, String name, boolean forceRedownload) { public void addPack(String fetcher, String pack, String name, boolean forceRedownload) {
addPack(fetcher, pack, name, forceRedownload, false); addPack(fetcher, pack, name, forceRedownload, false);
} }
@Override
public void addPack(String fetcher, String pack, String name, boolean forceRedownload, boolean forceEnable) { public void addPack(String fetcher, String pack, String name, boolean forceRedownload, boolean forceEnable) {
CONF.add(new PackMetaUnloaded(fetcher, pack, name, forceRedownload, forceEnable)); RescloneConfig.packs.add(new PackMetaUnloaded(fetcher, pack, name, forceRedownload, forceEnable));
} }
@Override
public void reload() { public void reload() {
Set<PackMetaLoaded> metas = new LinkedHashSet<>(); Set<PackMetaLoaded> metas = new LinkedHashSet<>();
try { if (RescloneConfig.packs.isEmpty()) {
if (CONF.isEmpty()) { LOGGER.info("No resclone pack was specified, add one");
LOGGER.info("No resclone pack was specified, add one"); }
} else {
else { try {
ExecutorService pool = Executors.newFixedThreadPool(CONF.size()); ExecutorService pool = Executors.newFixedThreadPool(RescloneConfig.packs.size());
for (PackMetaUnloaded s : CONF) { for (PackMetaUnloaded s : RescloneConfig.packs) {
pool.submit(generateTask(s, metas)); pool.submit(generateTask(s, metas));
} }
pool.shutdown(); pool.shutdown();
if (!pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)) { if (!pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)) {
LOGGER.error("Download timed out. This shouldn't be possible"); LOGGER.error("Download timed out. This shouldn't be possible");
} }
} catch (InterruptedException e) {
LOGGER.error("Could not execute pack download task", e);
} }
} catch (InterruptedException e) {
LOGGER.error("Could not execute pack download task", e);
} }
urlCache.save(); urlCache.save();
DOWNLOADED_PACKS.clear(); DOWNLOADED_PACKS.clear();
DOWNLOADED_PACKS.addAll(metas); DOWNLOADED_PACKS.addAll(metas);
if (pruneUnused) pruneCache(); if (RescloneConfig.pruneUnused) pruneCache();
} }
private Runnable generateTask(PackMetaUnloaded meta, Set<PackMetaLoaded> metas) { private Runnable generateTask(PackMetaUnloaded meta, Set<PackMetaLoaded> metas) {
@ -175,9 +163,8 @@ public class Resclone implements ModInitializer, RescloneApi {
} }
} }
@Override public static Path getConfigPath() {
public Path getConfigPath() { Path configPath = FabricLoader.getInstance().getConfigDir().resolve(MOD_ID);
Path configPath = FabricLoader.getInstance().getConfigDir().resolve("resclone");
if (!Files.isDirectory(configPath.resolve("cache"))) { if (!Files.isDirectory(configPath.resolve("cache"))) {
try { try {
Files.createDirectories(configPath.resolve("cache")); Files.createDirectories(configPath.resolve("cache"));
@ -187,9 +174,4 @@ public class Resclone implements ModInitializer, RescloneApi {
} }
return configPath; return configPath;
} }
@Override
public void setPruneUnused(boolean pruneUnused) {
this.pruneUnused = pruneUnused;
}
} }

View File

@ -0,0 +1,111 @@
package io.gitlab.jfronny.resclone;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders;
import io.gitlab.jfronny.gson.JsonParseException;
import io.gitlab.jfronny.gson.stream.*;
import io.gitlab.jfronny.libjf.config.api.v1.JfCustomConfig;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
public class RescloneConfig implements JfCustomConfig {
public static Set<PackMetaUnloaded> packs;
public static boolean pruneUnused;
private static final String PACKS = "packs";
private static final String PRUNE_UNUSED = "pruneUnused";
private static final Type META_SET = new TypeToken<Set<PackMetaUnloaded>>(){}.getType();
private static void load(Path path) throws IOException {
if (!Files.exists(path)) {
packs = new HashSet<>();
pruneUnused = true;
write(path);
return;
}
boolean updateRequired = false;
try (BufferedReader br = Files.newBufferedReader(path);
JsonReader reader = GsonHolders.CONFIG.getGson().newJsonReader(br)) {
if (reader.peek() == JsonToken.BEGIN_ARRAY) {
// Legacy format compatibility
packs = GsonHolders.CONFIG.getGson().fromJson(reader, META_SET);
updateRequired = true;
} else if (reader.peek() == JsonToken.BEGIN_OBJECT) {
// New format
reader.beginObject();
Set<PackMetaUnloaded> packs = null;
Boolean pruneUnused = null;
while (reader.peek() != JsonToken.END_OBJECT) {
final String name = reader.nextName();
switch (name) {
case PACKS -> {
if (packs != null) throw new JsonParseException("Unexpected duplicate \"" + PACKS + "\" in Resclone config");
packs = GsonHolders.CONFIG.getGson().fromJson(reader, META_SET);
}
case PRUNE_UNUSED -> {
if (pruneUnused != null) throw new JsonParseException("Unexpected duplicate \"" + PRUNE_UNUSED + "\" in Resclone config");
pruneUnused = reader.nextBoolean();
}
default -> throw new JsonParseException("Unexpected element: \"" + name + "\" in Resclone config");
}
}
reader.endObject();
if (packs == null) throw new JsonParseException("Expected Resclone config object to contain packs");
if (pruneUnused == null) {
pruneUnused = true;
updateRequired = true;
}
RescloneConfig.packs = packs;
RescloneConfig.pruneUnused = pruneUnused;
} else throw new JsonParseException("Expected Resclone config to be an object or array");
}
if (updateRequired) write(path);
}
private static void write(Path path) throws IOException {
try (BufferedWriter bw = Files.newBufferedWriter(path);
JsonWriter writer = GsonHolders.CONFIG.getGson().newJsonWriter(bw)) {
writer.beginObject()
.comment("The packs to be loaded by resclone")
.name(PACKS);
GsonHolders.CONFIG.getGson().toJson(packs, META_SET, writer);
writer.comment("Whether to prune unused packs from the cache")
.name(PRUNE_UNUSED)
.value(pruneUnused)
.endObject();
}
}
static {
Path path = Resclone.getConfigPath().resolve("config.json");
DSL.create(Resclone.MOD_ID).register(builder ->
builder.setLoadMethod(configInstance -> {
try {
load(path);
} catch (IOException e) {
Resclone.LOGGER.error("Could not load config", e);
}
}).setWriteMethod(configInstance -> {
try {
write(path);
} catch (IOException e) {
Resclone.LOGGER.error("Could not write config", e);
}
}).setPath(path)
.<Set<PackMetaUnloaded>>value(PACKS, new HashSet<>(), Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, io.gitlab.jfronny.libjf.config.api.v1.type.Type.ofClass(META_SET), 100, () -> packs, p -> packs = p)
.value(PRUNE_UNUSED, pruneUnused, () -> pruneUnused, p -> pruneUnused = p)
).load();
}
@Override
public void register(DSL.Defaulted dsl) {
}
}

View File

@ -1,24 +0,0 @@
package io.gitlab.jfronny.resclone;
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders;
import io.gitlab.jfronny.resclone.api.RescloneApi;
import io.gitlab.jfronny.resclone.api.RescloneEntry;
import io.gitlab.jfronny.resclone.fetchers.*;
import io.gitlab.jfronny.resclone.processors.PruneVanillaProcessor;
import io.gitlab.jfronny.resclone.util.config.*;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
public class RescloneEntryDefault implements RescloneEntry {
@Override
public void init(RescloneApi api) {
GsonHolders.registerTypeAdapter(RescloneConfig.class, new RescloneConfigTypeAdapter());
api.addFetcher(new BasicFileFetcher());
api.addFetcher(new GitHubFetcher());
api.addFetcher(new CurseforgeFetcher());
api.addFetcher(new ModrinthFetcher());
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
api.addProcessor(new PruneVanillaProcessor());
ConfigLoader.load(api);
}
}

View File

@ -1,21 +0,0 @@
package io.gitlab.jfronny.resclone.api;
import java.nio.file.Path;
public interface RescloneApi {
void addFetcher(PackFetcher fetcher);
void addProcessor(PackProcessor processor);
void addPack(String fetcher, String pack, String name);
void addPack(String fetcher, String pack, String name, boolean forceRedownload);
void addPack(String fetcher, String pack, String name, boolean forceRedownload, boolean forceEnable);
void reload();
Path getConfigPath();
void setPruneUnused(boolean pruneUnused);
}

View File

@ -1,5 +0,0 @@
package io.gitlab.jfronny.resclone.api;
public interface RescloneEntry {
void init(RescloneApi api) throws Exception;
}

View File

@ -2,7 +2,6 @@ package io.gitlab.jfronny.resclone.fetchers;
import io.gitlab.jfronny.commons.HttpUtils; import io.gitlab.jfronny.commons.HttpUtils;
import io.gitlab.jfronny.resclone.Resclone; import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.api.PackFetcher;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.resclone.api; package io.gitlab.jfronny.resclone.fetchers;
import java.nio.file.Path; import java.nio.file.Path;

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.resclone.api; package io.gitlab.jfronny.resclone.processors;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.resclone.processors; package io.gitlab.jfronny.resclone.processors;
import io.gitlab.jfronny.resclone.Resclone; import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.api.PackProcessor;
import io.gitlab.jfronny.resclone.util.io.PathPruneVisitor; import io.gitlab.jfronny.resclone.util.io.PathPruneVisitor;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.resclone.processors; package io.gitlab.jfronny.resclone.processors;
import io.gitlab.jfronny.resclone.Resclone; import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.api.PackProcessor;
import io.gitlab.jfronny.resclone.util.io.PathPruneVisitor; import io.gitlab.jfronny.resclone.util.io.PathPruneVisitor;
import java.io.IOException; import java.io.IOException;

View File

@ -1,6 +1,5 @@
package io.gitlab.jfronny.resclone.processors; package io.gitlab.jfronny.resclone.processors;
import io.gitlab.jfronny.resclone.api.PackProcessor;
import io.gitlab.jfronny.resclone.util.io.MoveDirVisitor; import io.gitlab.jfronny.resclone.util.io.MoveDirVisitor;
import java.io.IOException; import java.io.IOException;

View File

@ -1,42 +0,0 @@
package io.gitlab.jfronny.resclone.util.config;
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders;
import io.gitlab.jfronny.resclone.Resclone;
import io.gitlab.jfronny.resclone.api.RescloneApi;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
public class ConfigLoader {
public static void load(RescloneApi api) {
Path configPath = api.getConfigPath().resolve("config.json");
if (!Files.exists(configPath)) {
save(api, new HashSet<>(), true);
}
try (BufferedReader reader = Files.newBufferedReader(configPath)) {
RescloneConfig data = GsonHolders.CONFIG.getGson().fromJson(reader, RescloneConfig.class);
api.setPruneUnused(data.pruneUnused());
for (PackMetaUnloaded meta : data.packs()) {
api.addPack(meta.fetcher, meta.source, meta.name, meta.forceDownload, meta.forceEnable);
}
if (data.updateRequired()) {
save(api, data.packs(), data.pruneUnused());
}
} catch (IOException e) {
Resclone.LOGGER.error("Could not load config", e);
}
}
public static void save(RescloneApi api, Set<PackMetaUnloaded> packs, boolean pruneUnused) {
try (var writer = Files.newBufferedWriter(api.getConfigPath().resolve("config.json"))) {
GsonHolders.CONFIG.getGson().toJson(new RescloneConfig(packs, pruneUnused), writer);
} catch (IOException e) {
Resclone.LOGGER.error("Could not write config", e);
}
}
}

View File

@ -1,11 +0,0 @@
package io.gitlab.jfronny.resclone.util.config;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
import java.util.Set;
public record RescloneConfig(Set<PackMetaUnloaded> packs, boolean pruneUnused, boolean updateRequired) {
public RescloneConfig(Set<PackMetaUnloaded> packs, boolean pruneUnused) {
this(packs, pruneUnused, false);
}
}

View File

@ -1,65 +0,0 @@
package io.gitlab.jfronny.resclone.util.config;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders;
import io.gitlab.jfronny.gson.JsonParseException;
import io.gitlab.jfronny.gson.TypeAdapter;
import io.gitlab.jfronny.gson.stream.*;
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Set;
public class RescloneConfigTypeAdapter extends TypeAdapter<RescloneConfig> {
private static final String PACKS = "packs";
private static final String PRUNE_UNUSED = "pruneUnused";
private static final Type META_SET = new TypeToken<Set<PackMetaUnloaded>>(){}.getType();
@Override
public RescloneConfig read(JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
// Legacy format compatibility
return new RescloneConfig(GsonHolders.CONFIG.getGson().fromJson(jsonReader, META_SET), false, true);
} else if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
// New format
jsonReader.beginObject();
Set<PackMetaUnloaded> packs = null;
Boolean pruneUnused = null;
boolean updateRequired = false;
while (jsonReader.peek() != JsonToken.END_OBJECT) {
final String name = jsonReader.nextName();
switch (name) {
case PACKS -> {
if (packs != null) throw new JsonParseException("Unexpected duplicate \"" + PACKS + "\" in Resclone config");
packs = GsonHolders.CONFIG.getGson().fromJson(jsonReader, META_SET);
}
case PRUNE_UNUSED -> {
if (pruneUnused != null) throw new JsonParseException("Unexpected duplicate \"" + PRUNE_UNUSED + "\" in Resclone config");
pruneUnused = jsonReader.nextBoolean();
}
default -> throw new JsonParseException("Unexpected element: \"" + name + "\" in Resclone config");
}
}
jsonReader.endObject();
if (packs == null) throw new JsonParseException("Expected Resclone config object to contain packs");
if (pruneUnused == null) {
pruneUnused = true;
updateRequired = true;
}
return new RescloneConfig(packs, pruneUnused, updateRequired);
} else throw new JsonParseException("Expected Resclone config to be an object or array");
}
@Override
public void write(JsonWriter jsonWriter, RescloneConfig rescloneConfig) throws IOException {
jsonWriter.beginObject()
.comment("The packs to be loaded by resclone")
.name(PACKS);
GsonHolders.CONFIG.getGson().toJson(rescloneConfig.packs(), META_SET, jsonWriter);
jsonWriter.comment("Whether to prune unused packs from the cache")
.name(PRUNE_UNUSED)
.value(rescloneConfig.pruneUnused())
.endObject();
}
}

View File

@ -0,0 +1,7 @@
{
"resclone.jfconfig.title": "Resclone",
"resclone.jfconfig.packs": "Packs",
"resclone.jfconfig.packs.tooltip": "The packs to download and add",
"resclone.jfconfig.pruneUnused": "Prune Unused",
"resclone.jfconfig.pruneUnused.tooltip": "Automatically remove all downloaded packs that are not in the config to free up unneeded space"
}

View File

@ -16,7 +16,9 @@
"environment": "*", "environment": "*",
"entrypoints": { "entrypoints": {
"main": ["io.gitlab.jfronny.resclone.Resclone"], "main": ["io.gitlab.jfronny.resclone.Resclone"],
"resclone": ["io.gitlab.jfronny.resclone.RescloneEntryDefault"] "libjf:config": [
"io.gitlab.jfronny.resclone.RescloneConfig"
]
}, },
"mixins": [ "mixins": [
"resclone.mixins.json", "resclone.mixins.json",