diff --git a/README.md b/README.md index 85288cc..ac5bc0f 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ This repository owns the shared runtime platform: `runtime-catalog/grounds-runtime-libraries.json` is the source of truth for shared runtime library versions. The BOM and runtime diagnostics are generated from that catalog. +`/grounds-runtime` player output uses the general system-message category at info level. The +diagnostic field and library rows use the same metadata as the localized headings, while console +output remains plain and copyable. + ## Adding or updating a shared dependency Use this flow when a library should be provided by `plugin-grounds-runtime` instead of bundled into each consumer plugin. diff --git a/build.gradle.kts b/build.gradle.kts index cacaf14..fecc78a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,4 +5,17 @@ val versionOverride = providers.gradleProperty("versionOverride").orNull allprojects { group = "gg.grounds" version = versionOverride ?: "0.1.0" + + repositories { + maven { + url = uri("https://maven.pkg.github.com/groundsgg/library-i18n") + credentials { + username = + providers.gradleProperty("github.user").orNull ?: System.getenv("GITHUB_ACTOR") + password = + providers.gradleProperty("github.token").orNull ?: System.getenv("GITHUB_TOKEN") + } + content { includeModule("gg.grounds", "library-i18n") } + } + } } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 44cb0b6..41579a4 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -7,7 +7,15 @@ plugins { id("gg.grounds.kotlin-conventions") } -dependencies { testImplementation(kotlin("test")) } +dependencies { + api("gg.grounds:library-i18n:0.4.0") + compileOnly("net.kyori:adventure-api:4.17.0") + + testImplementation(kotlin("test")) + testImplementation("net.kyori:adventure-api:4.17.0") + testImplementation("net.kyori:adventure-text-minimessage:4.17.0") + testImplementation("net.kyori:adventure-text-serializer-plain:4.17.0") +} val runtimeCatalogFile = rootProject.layout.projectDirectory.file("runtime-catalog/grounds-runtime-libraries.json") diff --git a/common/src/main/kotlin/gg/grounds/runtime/RuntimeDiagnostics.kt b/common/src/main/kotlin/gg/grounds/runtime/RuntimeDiagnostics.kt index f0346ce..e3abb8f 100644 --- a/common/src/main/kotlin/gg/grounds/runtime/RuntimeDiagnostics.kt +++ b/common/src/main/kotlin/gg/grounds/runtime/RuntimeDiagnostics.kt @@ -1,7 +1,7 @@ package gg.grounds.runtime object RuntimeDiagnostics { - fun lines(runtimeId: String, version: String, platform: String): List { + fun report(runtimeId: String, version: String, platform: String): RuntimeDiagnosticsReport { val manifestStatus = if (RuntimeManifestLoader.loadRaw() == null) { "missing" @@ -9,15 +9,24 @@ object RuntimeDiagnostics { "found" } - return buildList { - add("Grounds Runtime") - add("runtimeId=$runtimeId") - add("version=$version") - add("platform=$platform") - add("manifestPath=${RuntimeManifestLoader.defaultPath}") - add("manifestStatus=$manifestStatus") - add("libraries:") - RuntimeLibraries.provided.forEach { library -> add("- ${library.coordinate()}") } - } + return RuntimeDiagnosticsReport( + fields = + listOf( + "runtimeId=$runtimeId", + "version=$version", + "platform=$platform", + "manifestPath=${RuntimeManifestLoader.defaultPath}", + "manifestStatus=$manifestStatus", + ), + libraries = RuntimeLibraries.provided.map { it.coordinate() }, + ) } + + fun lines(runtimeId: String, version: String, platform: String): List = + report(runtimeId, version, platform).consoleLines() +} + +data class RuntimeDiagnosticsReport(val fields: List, val libraries: List) { + fun consoleLines(): List = + listOf("Grounds Runtime") + fields + "libraries:" + libraries.map { "- $it" } } diff --git a/common/src/main/kotlin/gg/grounds/runtime/RuntimeDiagnosticsPresenter.kt b/common/src/main/kotlin/gg/grounds/runtime/RuntimeDiagnosticsPresenter.kt new file mode 100644 index 0000000..e434489 --- /dev/null +++ b/common/src/main/kotlin/gg/grounds/runtime/RuntimeDiagnosticsPresenter.kt @@ -0,0 +1,45 @@ +package gg.grounds.runtime + +import gg.grounds.i18n.SystemCategory +import gg.grounds.i18n.SystemLevel +import gg.grounds.i18n.SystemMessageKey +import gg.grounds.i18n.SystemMessages +import gg.grounds.i18n.Translations +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component + +enum class RuntimeDiagnosticMessage(override val id: String, override val level: SystemLevel) : + SystemMessageKey { + HEADING("diagnostics.heading", SystemLevel.INFO), + LIBRARIES("diagnostics.libraries", SystemLevel.INFO), +} + +object RuntimeDiagnosticsPresenter { + private val messages = + Translations.forBundle("gg.grounds.runtime.messages", javaClass.classLoader) + + fun sendPlayer(audience: Audience, report: RuntimeDiagnosticsReport) { + messages.send(audience, RuntimeDiagnosticMessage.HEADING) + report.fields.forEach { line -> + audience.sendMessage( + SystemMessages.format( + Component.text(line), + category = SystemCategory.GENERAL, + locale = messages.localeOf(audience), + level = SystemLevel.INFO, + ) + ) + } + messages.send(audience, RuntimeDiagnosticMessage.LIBRARIES) + report.libraries.forEach { coordinate -> + audience.sendMessage( + SystemMessages.format( + Component.text("- $coordinate"), + category = SystemCategory.GENERAL, + locale = messages.localeOf(audience), + level = SystemLevel.INFO, + ) + ) + } + } +} diff --git a/common/src/main/resources/gg/grounds/runtime/messages.properties b/common/src/main/resources/gg/grounds/runtime/messages.properties new file mode 100644 index 0000000..14b1a8a --- /dev/null +++ b/common/src/main/resources/gg/grounds/runtime/messages.properties @@ -0,0 +1,2 @@ +diagnostics.heading=Grounds Runtime +diagnostics.libraries=Libraries diff --git a/common/src/main/resources/gg/grounds/runtime/messages_de.properties b/common/src/main/resources/gg/grounds/runtime/messages_de.properties new file mode 100644 index 0000000..363441e --- /dev/null +++ b/common/src/main/resources/gg/grounds/runtime/messages_de.properties @@ -0,0 +1,2 @@ +diagnostics.heading=Grounds-Laufzeit +diagnostics.libraries=Bibliotheken diff --git a/common/src/main/resources/gg/grounds/runtime/messages_es.properties b/common/src/main/resources/gg/grounds/runtime/messages_es.properties new file mode 100644 index 0000000..a45b928 --- /dev/null +++ b/common/src/main/resources/gg/grounds/runtime/messages_es.properties @@ -0,0 +1,2 @@ +diagnostics.heading=Entorno de ejecución de Grounds +diagnostics.libraries=Bibliotecas diff --git a/common/src/main/resources/gg/grounds/runtime/messages_fr.properties b/common/src/main/resources/gg/grounds/runtime/messages_fr.properties new file mode 100644 index 0000000..08b7d13 --- /dev/null +++ b/common/src/main/resources/gg/grounds/runtime/messages_fr.properties @@ -0,0 +1,2 @@ +diagnostics.heading=Environnement d’exécution Grounds +diagnostics.libraries=Bibliothèques diff --git a/common/src/test/kotlin/gg/grounds/runtime/RuntimeDiagnosticMessagesTest.kt b/common/src/test/kotlin/gg/grounds/runtime/RuntimeDiagnosticMessagesTest.kt new file mode 100644 index 0000000..f1be87d --- /dev/null +++ b/common/src/test/kotlin/gg/grounds/runtime/RuntimeDiagnosticMessagesTest.kt @@ -0,0 +1,29 @@ +package gg.grounds.runtime + +import gg.grounds.i18n.BundleCheck +import gg.grounds.i18n.SystemCategory +import gg.grounds.i18n.SystemLevel +import kotlin.test.Test +import kotlin.test.assertEquals + +class RuntimeDiagnosticMessagesTest { + @Test + fun `message bundles are complete`() { + assertEquals( + emptyList(), + BundleCheck.problems( + "gg.grounds.runtime.messages", + javaClass.classLoader, + RuntimeDiagnosticMessage.entries, + ), + ) + } + + @Test + fun `diagnostic messages use general context at info level`() { + RuntimeDiagnosticMessage.entries.forEach { message -> + assertEquals(SystemCategory.GENERAL, message.category) + assertEquals(SystemLevel.INFO, message.level) + } + } +} diff --git a/common/src/test/kotlin/gg/grounds/runtime/RuntimeDiagnosticsPresenterTest.kt b/common/src/test/kotlin/gg/grounds/runtime/RuntimeDiagnosticsPresenterTest.kt new file mode 100644 index 0000000..126b08a --- /dev/null +++ b/common/src/test/kotlin/gg/grounds/runtime/RuntimeDiagnosticsPresenterTest.kt @@ -0,0 +1,61 @@ +package gg.grounds.runtime + +import java.util.Locale +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.identity.Identity +import net.kyori.adventure.pointer.Pointers +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer + +class RuntimeDiagnosticsPresenterTest { + @Test + fun `player output localizes headings and preserves exact diagnostics`() { + val audience = RecordingAudience(Locale.GERMAN) + val report = + RuntimeDiagnostics.report( + runtimeId = "plugin-grounds-runtime-velocity", + version = "0.1.1", + platform = "velocity", + ) + + RuntimeDiagnosticsPresenter.sendPlayer(audience, report) + + val messages = audience.sent.map(PlainTextComponentSerializer.plainText()::serialize) + assertEquals("[Grounds] Allgemein › Grounds-Laufzeit", messages.first()) + assertEquals("[Grounds] Allgemein › runtimeId=plugin-grounds-runtime-velocity", messages[1]) + assertEquals("[Grounds] Allgemein › Bibliotheken", messages[6]) + assertContains(messages, "[Grounds] Allgemein › - io.grpc:grpc-api:1.81.0") + } + + @Test + fun `console output remains plain and copyable`() { + val report = + RuntimeDiagnostics.report( + runtimeId = "plugin-grounds-runtime-paper", + version = "0.1.1", + platform = "paper", + ) + + val lines = report.consoleLines() + assertEquals("Grounds Runtime", lines.first()) + assertEquals("runtimeId=plugin-grounds-runtime-paper", lines[1]) + assertEquals("version=0.1.1", lines[2]) + assertEquals("platform=paper", lines[3]) + assertEquals("libraries:", lines[6]) + assertContains(lines, "- io.grpc:grpc-api:1.81.0") + } +} + +private class RecordingAudience(locale: Locale) : Audience { + val sent = mutableListOf() + private val pointers = Pointers.builder().withStatic(Identity.LOCALE, locale).build() + + override fun pointers(): Pointers = pointers + + override fun sendMessage(message: Component) { + sent += message + } +} diff --git a/paper/src/main/kotlin/gg/grounds/runtime/paper/GroundsRuntimePaperPlugin.kt b/paper/src/main/kotlin/gg/grounds/runtime/paper/GroundsRuntimePaperPlugin.kt index b7231ee..d9045da 100644 --- a/paper/src/main/kotlin/gg/grounds/runtime/paper/GroundsRuntimePaperPlugin.kt +++ b/paper/src/main/kotlin/gg/grounds/runtime/paper/GroundsRuntimePaperPlugin.kt @@ -1,9 +1,11 @@ package gg.grounds.runtime.paper import gg.grounds.runtime.RuntimeDiagnostics +import gg.grounds.runtime.RuntimeDiagnosticsPresenter import gg.grounds.runtime.RuntimeManifestLoader import org.bukkit.command.Command import org.bukkit.command.CommandSender +import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin class GroundsRuntimePaperPlugin : JavaPlugin() { @@ -23,12 +25,14 @@ class GroundsRuntimePaperPlugin : JavaPlugin() { return false } - RuntimeDiagnostics.lines( + val report = + RuntimeDiagnostics.report( runtimeId = "plugin-grounds-runtime-paper", version = pluginMeta.version, platform = "paper", ) - .forEach(sender::sendMessage) + if (sender is Player) RuntimeDiagnosticsPresenter.sendPlayer(sender, report) + else report.consoleLines().forEach(sender::sendMessage) return true } } diff --git a/velocity/src/main/kotlin/gg/grounds/runtime/velocity/GroundsRuntimeVelocityPlugin.kt b/velocity/src/main/kotlin/gg/grounds/runtime/velocity/GroundsRuntimeVelocityPlugin.kt index f83afe6..7f7447f 100644 --- a/velocity/src/main/kotlin/gg/grounds/runtime/velocity/GroundsRuntimeVelocityPlugin.kt +++ b/velocity/src/main/kotlin/gg/grounds/runtime/velocity/GroundsRuntimeVelocityPlugin.kt @@ -6,11 +6,12 @@ import com.velocitypowered.api.event.Subscribe import com.velocitypowered.api.event.proxy.ProxyInitializeEvent import com.velocitypowered.api.plugin.Plugin import com.velocitypowered.api.proxy.ConsoleCommandSource +import com.velocitypowered.api.proxy.Player import com.velocitypowered.api.proxy.ProxyServer import gg.grounds.BuildInfo import gg.grounds.runtime.RuntimeDiagnostics +import gg.grounds.runtime.RuntimeDiagnosticsPresenter import gg.grounds.runtime.RuntimeManifestLoader -import net.kyori.adventure.text.Component import org.slf4j.Logger @Plugin( @@ -40,12 +41,15 @@ constructor(private val proxyServer: ProxyServer, private val logger: Logger) { private class RuntimeInfoCommand : SimpleCommand { override fun execute(invocation: SimpleCommand.Invocation) { - RuntimeDiagnostics.lines( + val report = + RuntimeDiagnostics.report( runtimeId = "plugin-grounds-runtime-velocity", version = BuildInfo.VERSION, platform = "velocity", ) - .forEach { line -> invocation.source().sendMessage(Component.text(line)) } + val source = invocation.source() + if (source is Player) RuntimeDiagnosticsPresenter.sendPlayer(source, report) + else report.consoleLines().forEach { line -> source.sendPlainMessage(line) } } override fun hasPermission(invocation: SimpleCommand.Invocation): Boolean {