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
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/gg/grounds/platform/GroundsPlatformPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 12 additions & 1 deletion src/main/kotlin/gg/grounds/platform/PlatformEnv.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ 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?
Expand All @@ -35,6 +45,7 @@ fun readPlatformEnv(reader: EnvReader = SystemEnvReader): PlatformEnv? {
projectId = projectId,
projectName = projectName,
forgeUrl = forgeUrl.trimEnd('/'),
pushId = reader["GROUNDS_PUSH_ID"]?.trim()?.takeIf { it.isNotEmpty() },
)
}

Expand Down
31 changes: 23 additions & 8 deletions src/main/kotlin/gg/grounds/platform/motd/MotdSetter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,33 @@ 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<projectName> §8<short pushId>`
* - Line two: `§8powered by Grounds Developer Platform`
*
* <Project Name> §8via Grounds
*
* 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.
* 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) {

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
}
}
18 changes: 17 additions & 1 deletion src/test/kotlin/gg/grounds/platform/PlatformEnvTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,23 @@ 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
Expand Down
43 changes: 43 additions & 0 deletions src/test/kotlin/gg/grounds/platform/motd/MotdSetterTest.kt
Original file line number Diff line number Diff line change
@@ -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<String>()
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)
}
}