From 053190c9f78e1bbb29fa4db1df05f3d306ddefb0 Mon Sep 17 00:00:00 2001 From: Hendrik Brombeer Date: Wed, 6 May 2026 19:50:40 +0200 Subject: [PATCH 1/2] feat(motd): show project + push id, attribute on second line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructures the in-pod MOTD per operator preference: Line 1: §f §8 Line 2: §8powered by Grounds Developer Platform PlatformEnv gains an optional pushId field sourced from GROUNDS_PUSH_ID (already wired by forge's renderer for every MC workload). Whitelist sync is unaffected — pushId is purely cosmetic. Adds MotdSetterTest covering the formatting + the null-pushId fallback. testImplementation paper-api so Mockito can proxy org.bukkit.Server at runtime. --- build.gradle.kts | 5 +++ .../grounds/platform/GroundsPlatformPlugin.kt | 5 ++- .../kotlin/gg/grounds/platform/PlatformEnv.kt | 14 +++++- .../gg/grounds/platform/motd/MotdSetter.kt | 29 ++++++++++--- .../gg/grounds/platform/PlatformEnvTest.kt | 21 ++++++++- .../grounds/platform/motd/MotdSetterTest.kt | 43 +++++++++++++++++++ 6 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 src/test/kotlin/gg/grounds/platform/motd/MotdSetterTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index c28b715..2c52711 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,11 @@ repositories { dependencies { compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + // Tests need Paper on both compile + runtime classpaths because + // Mockito loads the target class to generate the proxy. testCompileOnly + // alone passes compilation but throws ClassNotFoundException when + // mock(Server::class) runs. + testImplementation("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") // Compact JSON parser — sufficient for the small whitelist payloads diff --git a/src/main/kotlin/gg/grounds/platform/GroundsPlatformPlugin.kt b/src/main/kotlin/gg/grounds/platform/GroundsPlatformPlugin.kt index 6cc5844..44e7b6a 100644 --- a/src/main/kotlin/gg/grounds/platform/GroundsPlatformPlugin.kt +++ b/src/main/kotlin/gg/grounds/platform/GroundsPlatformPlugin.kt @@ -36,9 +36,10 @@ class GroundsPlatformPlugin : JavaPlugin() { ) } - MotdSetter(server).apply(env.projectName) + MotdSetter(server).apply(env.projectName, env.pushId) logger.info( - "MOTD set from platform context (projectId=${env.projectId}, projectName=${env.projectName})" + "MOTD set from platform context (projectId=${env.projectId}, " + + "projectName=${env.projectName}, pushId=${env.pushId ?: "n/a"})" ) val sync = WhitelistSync(server, logger) diff --git a/src/main/kotlin/gg/grounds/platform/PlatformEnv.kt b/src/main/kotlin/gg/grounds/platform/PlatformEnv.kt index cf7ca8d..b59d902 100644 --- a/src/main/kotlin/gg/grounds/platform/PlatformEnv.kt +++ b/src/main/kotlin/gg/grounds/platform/PlatformEnv.kt @@ -9,7 +9,18 @@ package gg.grounds.platform * Token is read separately from a secret-mounted env var rather than from this struct, so we never * accidentally include it in a `toString` or log line. */ -data class PlatformEnv(val projectId: String, val projectName: String, val forgeUrl: String) +data class PlatformEnv( + val projectId: String, + val projectName: String, + val forgeUrl: String, + /** + * Push ID of the deployment that produced this pod (the renderer's + * `GROUNDS_PUSH_ID` env). Optional — older deployments may not have + * it. Surfaced in the MOTD as a "version" tag for operator + * orientation; whitelist sync doesn't depend on it. + */ + val pushId: String? = null, +) interface EnvReader { operator fun get(name: String): String? @@ -35,6 +46,7 @@ fun readPlatformEnv(reader: EnvReader = SystemEnvReader): PlatformEnv? { projectId = projectId, projectName = projectName, forgeUrl = forgeUrl.trimEnd('/'), + pushId = reader["GROUNDS_PUSH_ID"]?.trim()?.takeIf { it.isNotEmpty() }, ) } diff --git a/src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt b/src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt index 3d180d3..e0af561 100644 --- a/src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt +++ b/src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt @@ -5,16 +5,33 @@ import org.bukkit.Server /** * Sets the server MOTD to a project-aware string at startup. Format: * - * §8via Grounds + * §f §8 + * §8powered by Grounds Developer Platform * - * Two lines because Paper's MOTD field accepts a single newline; the second line uses §8 (dark - * grey) to keep the platform attribution subtle. Operators can override post-startup via `/motd` if - * Paper supports it on their server, or a future per-project MOTD setting. + * Two lines because Paper's MOTD field accepts a single newline. Line one shows the project name in + * bright white with the (optional) push-id tail in dim grey so operators can tell at a glance which + * deployment is currently running. Line two is a subtle attribution. Color codes use the §-prefix + * legacy form which Paper renders consistently across vanilla + modded clients. */ class MotdSetter(private val server: Server) { - fun apply(projectName: String) { - val motd = "$projectName\n§8via Grounds" + fun apply(projectName: String, pushId: String? = null) { + val versionSuffix = + pushId + ?.let { it.replace("-", "").take(SHORT_PUSH_ID_LEN) } + ?.takeIf { it.isNotEmpty() } + ?.let { " §8$it" } + ?: "" + val motd = "§f$projectName$versionSuffix\n§8powered by Grounds Developer Platform" server.setMotd(motd) } + + companion object { + /** + * Push IDs are UUIDs. We strip dashes and keep the first 8 chars to mirror the portal's push- + * row convention (`p.id.slice(0, 8)` in `pushes-table.tsx`). Long enough to be near-unique + * within a project, short enough not to dominate the MOTD line. + */ + private const val SHORT_PUSH_ID_LEN = 8 + } } diff --git a/src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt b/src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt index ab4b2f5..b275c45 100644 --- a/src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt +++ b/src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt @@ -23,7 +23,26 @@ class PlatformEnvTest { ) ) ) - assertEquals(PlatformEnv("p-1", "Demo Project", "http://forge:8080"), env) + assertEquals( + PlatformEnv("p-1", "Demo Project", "http://forge:8080", pushId = null), + env, + ) + } + + @Test + fun `pushId surfaces when GROUNDS_PUSH_ID is set`() { + val env = + readPlatformEnv( + reader( + mapOf( + "GROUNDS_PROJECT_ID" to "p-1", + "GROUNDS_PROJECT_NAME" to "P", + "GROUNDS_FORGE_URL" to "http://forge", + "GROUNDS_PUSH_ID" to "abc12345-6789-0abc-def0-123456789abc", + ) + ) + ) + assertEquals("abc12345-6789-0abc-def0-123456789abc", env?.pushId) } @Test diff --git a/src/test/kotlin/gg/grounds/platform/motd/MotdSetterTest.kt b/src/test/kotlin/gg/grounds/platform/motd/MotdSetterTest.kt new file mode 100644 index 0000000..5729946 --- /dev/null +++ b/src/test/kotlin/gg/grounds/platform/motd/MotdSetterTest.kt @@ -0,0 +1,43 @@ +package gg.grounds.platform.motd + +import org.bukkit.Server +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class MotdSetterTest { + + private fun captureMotd(projectName: String, pushId: String? = null): String { + val server: Server = mock() + MotdSetter(server).apply(projectName, pushId) + val captor = argumentCaptor() + verify(server).setMotd(captor.capture()) + return captor.firstValue + } + + @Test + fun `formats project name and short push id on line one with attribution on line two`() { + val motd = captureMotd("Demo Project", "abc12345-6789-0abc-def0-123456789abc") + assertEquals("§fDemo Project §8abc12345\n§8powered by Grounds Developer Platform", motd) + } + + @Test + fun `omits push-id suffix when pushId is null`() { + val motd = captureMotd("Demo Project", pushId = null) + assertEquals("§fDemo Project\n§8powered by Grounds Developer Platform", motd) + } + + @Test + fun `omits push-id suffix when pushId is blank after dash-strip`() { + val motd = captureMotd("Demo Project", pushId = "----") + assertEquals("§fDemo Project\n§8powered by Grounds Developer Platform", motd) + } + + @Test + fun `truncates short pushId without underflow`() { + val motd = captureMotd("P", pushId = "abc") + assertEquals("§fP §8abc\n§8powered by Grounds Developer Platform", motd) + } +} From b43f19126af46917e8743c28d270d4ddc525c05d Mon Sep 17 00:00:00 2001 From: Hendrik Brombeer Date: Wed, 6 May 2026 19:52:29 +0200 Subject: [PATCH 2/2] style: apply spotless + restore MotdSetter kdoc layout --- .../kotlin/gg/grounds/platform/PlatformEnv.kt | 7 +++--- .../gg/grounds/platform/motd/MotdSetter.kt | 24 +++++++++---------- .../gg/grounds/platform/PlatformEnvTest.kt | 5 +--- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/gg/grounds/platform/PlatformEnv.kt b/src/main/kotlin/gg/grounds/platform/PlatformEnv.kt index b59d902..f802cf9 100644 --- a/src/main/kotlin/gg/grounds/platform/PlatformEnv.kt +++ b/src/main/kotlin/gg/grounds/platform/PlatformEnv.kt @@ -14,10 +14,9 @@ data class PlatformEnv( val projectName: String, val forgeUrl: String, /** - * Push ID of the deployment that produced this pod (the renderer's - * `GROUNDS_PUSH_ID` env). Optional — older deployments may not have - * it. Surfaced in the MOTD as a "version" tag for operator - * orientation; whitelist sync doesn't depend on it. + * Push ID of the deployment that produced this pod (the renderer's `GROUNDS_PUSH_ID` env). + * Optional — older deployments may not have it. Surfaced in the MOTD as a "version" tag for + * operator orientation; whitelist sync doesn't depend on it. */ val pushId: String? = null, ) diff --git a/src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt b/src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt index e0af561..d53cc85 100644 --- a/src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt +++ b/src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt @@ -3,15 +3,14 @@ package gg.grounds.platform.motd import org.bukkit.Server /** - * Sets the server MOTD to a project-aware string at startup. Format: + * Sets the server MOTD to a project-aware string at startup. Two lines, joined by a single newline + * (the only separator Paper's MOTD field accepts): + * - Line one: `§f §8` + * - Line two: `§8powered by Grounds Developer Platform` * - * §f §8 - * §8powered by Grounds Developer Platform - * - * Two lines because Paper's MOTD field accepts a single newline. Line one shows the project name in - * bright white with the (optional) push-id tail in dim grey so operators can tell at a glance which - * deployment is currently running. Line two is a subtle attribution. Color codes use the §-prefix - * legacy form which Paper renders consistently across vanilla + modded clients. + * Project name in bright white, push-id tail in dim grey so operators can tell at a glance which + * deployment is running. Color codes use the §-prefix legacy form which Paper renders consistently + * across vanilla + modded clients. */ class MotdSetter(private val server: Server) { @@ -20,17 +19,16 @@ class MotdSetter(private val server: Server) { pushId ?.let { it.replace("-", "").take(SHORT_PUSH_ID_LEN) } ?.takeIf { it.isNotEmpty() } - ?.let { " §8$it" } - ?: "" + ?.let { " §8$it" } ?: "" val motd = "§f$projectName$versionSuffix\n§8powered by Grounds Developer Platform" server.setMotd(motd) } companion object { /** - * Push IDs are UUIDs. We strip dashes and keep the first 8 chars to mirror the portal's push- - * row convention (`p.id.slice(0, 8)` in `pushes-table.tsx`). Long enough to be near-unique - * within a project, short enough not to dominate the MOTD line. + * Push IDs are UUIDs. We strip dashes and keep the first 8 chars to mirror the portal's + * push- row convention (`p.id.slice(0, 8)` in `pushes-table.tsx`). Long enough to be + * near-unique within a project, short enough not to dominate the MOTD line. */ private const val SHORT_PUSH_ID_LEN = 8 } diff --git a/src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt b/src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt index b275c45..3e101da 100644 --- a/src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt +++ b/src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt @@ -23,10 +23,7 @@ class PlatformEnvTest { ) ) ) - assertEquals( - PlatformEnv("p-1", "Demo Project", "http://forge:8080", pushId = null), - env, - ) + assertEquals(PlatformEnv("p-1", "Demo Project", "http://forge:8080", pushId = null), env) } @Test