diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index ed318e7..5307ec4 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: temurin - java-version: 17 + java-version: 25 cache: gradle - name: Validate conventional commits @@ -35,6 +35,6 @@ jobs: - name: Upload development artifact uses: actions/upload-artifact@v3 with: - name: leaf-${{ github.sha }} + name: purpur-leaf-${{ github.sha }} path: build/libs/*.jar if-no-files-found: error diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 86d91cc..82e8673 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: temurin - java-version: 17 + java-version: 25 cache: gradle - name: Set up Node.js @@ -86,8 +86,8 @@ jobs: if: steps.release.outputs.created == 'true' uses: actions/upload-artifact@v3 with: - name: leaf-${{ steps.release.outputs.version }} - path: build/libs/leaf-${{ steps.release.outputs.version }}.jar + name: purpur-leaf-${{ steps.release.outputs.version }} + path: build/libs/purpur-leaf-${{ steps.release.outputs.version }}.jar if-no-files-found: error - name: Create Gitea release and upload plugin @@ -101,7 +101,7 @@ jobs: PREVIOUS_TAG: ${{ steps.previous_tag.outputs.value }} run: | api_url="${GITEA_SERVER_URL}/api/v1" - jar="build/libs/leaf-${VERSION}.jar" + jar="build/libs/purpur-leaf-${VERSION}.jar" export RELEASE_BODY if [ -n "$PREVIOUS_TAG" ]; then RELEASE_BODY=$(git log --pretty='format:- %s (%h)' "${PREVIOUS_TAG}..HEAD") @@ -138,4 +138,4 @@ jobs: -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${jar}" \ - "${api_url}/repos/${REPOSITORY}/releases/${release_id}/assets?name=leaf-${VERSION}.jar" + "${api_url}/repos/${REPOSITORY}/releases/${release_id}/assets?name=purpur-leaf-${VERSION}.jar" diff --git a/AGENTS.md b/AGENTS.md index 4c98660..0fca257 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,9 @@ -# spigot-leaf agent entrypoint +# purpur-leaf agent entrypoint The canonical stories, engineering guidance, and **all process documents** are in the private [SoMC OKF wiki](https://git.garvis.dev/dmg/somc-okf/src/branch/main/index.md). -Before work, read the sibling `../somc-okf/index.md`, `../somc-okf/processes/index.md`, `../somc-okf/projects/spigot-leaf/index.md`, `engineering.md` in that project section, and relevant `../somc-okf/user-stories/spigot-leaf/` stories. Also follow the parent workspace `AGENTS.md` when present. +Before work, read the sibling `../somc-okf/index.md`, `../somc-okf/processes/index.md`, `../somc-okf/projects/purpur-leaf/index.md`, `engineering.md` in that project section, and relevant `../somc-okf/user-stories/purpur-leaf/` stories. Also follow the parent workspace `AGENTS.md` when present. -For standalone checkouts, start at the [project page](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/spigot-leaf/index.md) and [shared process](https://git.garvis.dev/dmg/somc-okf/src/branch/main/processes/development.md). Obtain wiki access before feature work; do not recreate a local knowledge bundle. Source builds do not require private wiki access. +For standalone checkouts, start at the [project page](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/purpur-leaf/index.md) and [shared process](https://git.garvis.dev/dmg/somc-okf/src/branch/main/processes/development.md). Obtain wiki access before feature work; do not recreate a local knowledge bundle. Source builds do not require private wiki access. Development follows [Development cycle](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/development-cycle.md): approved stories, failing tests, passing implementation, verification, then source/wiki commit and push. GitOps updates are committed locally **without pushing**; only [Do release](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/do-release.md) authorizes a reviewed GitOps push. diff --git a/README.md b/README.md index 5e64e0d..df2e95c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ -# Leaf +# Purpur Leaf -Leaf is a Spigot 26.2 plugin that lets non-aggressive players opt into visible Resistance and Strength protection. +Leaf is a Purpur 26.2 plugin that lets non-aggressive players opt into visible Resistance and Strength protection. -The approved behavior is specified in the [SoMC OKF wiki](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/spigot-leaf/index.md). +The approved behavior is specified in the [SoMC OKF wiki](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/purpur-leaf/index.md). ## Requirements -- Spigot 26.2 -- Java 17 or newer +- Purpur 26.2 build 2618 +- Java 25 + +Distribution repository, checkout and artifacts use `purpur-leaf`. Runtime `Leaf`, Java packages/entrypoint, `plugins/Leaf/`, commands/permissions and `leaf_protected` scoreboard team are unchanged. Replace the old JAR instead of installing both distributions. Old release tags/assets remain available. New releases no longer support Java 17. ## Build @@ -15,7 +17,7 @@ The approved behavior is specified in the [SoMC OKF wiki](https://git.garvis.dev ./gradlew clean check jar ``` -The plugin JAR is written to `build/libs/`. +The plugin JAR is written to `build/libs/purpur-leaf-.jar`. ## Commands @@ -35,9 +37,9 @@ Administrative commands require `leaf.admin`, granted to server operators by def ## Compatibility -Leaf prefixes the standard Spigot display name (used by standard chat), player-list name, and a dedicated `leaf_protected` main-scoreboard team for overhead names. Legacy `&` color codes are supported. Leaf does not move players out of an existing scoreboard team, and per-viewer/custom scoreboards may not show its overhead prefix. Chat or tab-management plugins may replace Leaf's decorated names. During cleanup, Leaf removes exact copies of its configured prefix while preserving surrounding name formatting; another plugin should therefore not intentionally use identical prefix text. +Leaf prefixes the standard Bukkit-compatible display name (used by standard chat), player-list name, and a dedicated `leaf_protected` main-scoreboard team for overhead names. Legacy `&` color codes are supported. Leaf does not move players out of an existing scoreboard team, and per-viewer/custom scoreboards may not show its overhead prefix. Chat or tab-management plugins may replace Leaf's decorated names. During cleanup, Leaf removes exact copies of its configured prefix while preserving surrounding name formatting; another plugin should therefore not intentionally use identical prefix text. -Spigot identifies potion effects by type but does not expose their owning plugin. Leaf tracks the exact infinite, quiet Resistance and Strength effects it successfully installed and removes them only while each visible effect still matches. A distinct level, duration, or presentation is preserved. Spigot cannot distinguish an externally supplied effect with an identical fingerprint; Leaf therefore does not claim or later remove an identical effect that was already active when reconciliation ran. +The server API identifies potion effects by type but does not expose their owning plugin. Leaf tracks the exact infinite, quiet Resistance and Strength effects it successfully installed and removes them only while each visible effect still matches. A distinct level, duration, or presentation is preserved. The server API cannot distinguish an externally supplied effect with an identical fingerprint; Leaf therefore does not claim or later remove an identical effect that was already active when reconciliation ran. Leaf reconciles opted-in online players approximately once per second. Effects cleared by death, milk, commands, or another plugin are restored automatically. A temporary external Resistance or Strength effect is left untouched, the leaf prefix remains visible, and Leaf restores its configured effect after the external effect ends. diff --git a/build.gradle.kts b/build.gradle.kts index 97df943..bbe0a1e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,23 +9,23 @@ version = providers.gradleProperty("releaseVersion") repositories { mavenCentral() - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + maven("https://repo.purpurmc.org/snapshots/") } java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(25) } } tasks.withType().configureEach { - options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) + options.compilerArgs.addAll(listOf("-Xlint:all,-deprecation,-removal", "-Werror")) } dependencies { - compileOnly("org.spigotmc:spigot-api:26.2-R0.1-SNAPSHOT") + compileOnly("org.purpurmc.purpur:purpur-api:26.2.build.2618-stable") - testImplementation("org.spigotmc:spigot-api:26.2-R0.1-SNAPSHOT") + testImplementation("org.purpurmc.purpur:purpur-api:26.2.build.2618-stable") testImplementation(platform("org.junit:junit-bom:5.13.4")) testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core:5.18.0") @@ -35,6 +35,9 @@ dependencies { tasks.test { useJUnitPlatform() + dependsOn(tasks.jar) + systemProperty("distribution.jar", tasks.jar.get().archiveFile.get().asFile.absolutePath) + systemProperty("distribution.version", project.version.toString()) } val pluginVersion = version diff --git a/settings.gradle.kts b/settings.gradle.kts index b28c05f..2b86408 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "leaf" +rootProject.name = "purpur-leaf" diff --git a/src/test/java/games/dmg/leaf/PurpurDistributionTest.java b/src/test/java/games/dmg/leaf/PurpurDistributionTest.java new file mode 100644 index 0000000..35c5c29 --- /dev/null +++ b/src/test/java/games/dmg/leaf/PurpurDistributionTest.java @@ -0,0 +1,53 @@ +package games.dmg.leaf; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.DataInputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.jar.JarFile; +import org.junit.jupiter.api.Test; +import org.yaml.snakeyaml.Yaml; + +class PurpurDistributionTest { + @Test + void packagesNewNameWithoutChangingRuntimeIdentityOrBundlingTheApi() throws Exception { + Path artifact = Path.of(System.getProperty("distribution.jar")); + String version = System.getProperty("distribution.version"); + assertEquals("purpur-leaf-" + version + ".jar", artifact.getFileName().toString()); + try (var jar = new JarFile(artifact.toFile())) { + Map metadata = new Yaml().load(jar.getInputStream(jar.getJarEntry("plugin.yml"))); + assertEquals(version, metadata.get("version")); + assertEquals("Leaf", metadata.get("name"), "Retain the data directory and persistent namespace"); + assertEquals("games.dmg.leaf.LeafPlugin", metadata.get("main")); + assertTrue(((Map) metadata.get("permissions")).containsKey("leaf.admin")); + assertFalse(jar.stream().anyMatch(entry -> entry.getName().startsWith("org/bukkit/") + || entry.getName().startsWith("org/purpurmc/") || entry.getName().endsWith("Test.class"))); + } + } + + @Test + void pluginBytecodeTargetsJava25() throws Exception { + try (var jar = new JarFile(System.getProperty("distribution.jar")); + var bytecode = new DataInputStream(jar.getInputStream( + jar.getJarEntry("games/dmg/leaf/LeafPlugin.class")))) { + assertEquals(0xCAFEBABE, bytecode.readInt()); + bytecode.readUnsignedShort(); + assertEquals(69, bytecode.readUnsignedShort()); + } + } + + @Test + void releaseAndCiAgreeWithThePurpurDistributionNameAndToolchain() throws Exception { + String build = Files.readString(Path.of("build.gradle.kts")); + assertTrue(build.contains("purpur-api:26.2.build.2618-stable")); + assertFalse(build.contains("org.spigotmc:spigot-api")); + for (String workflow : new String[] {"ci.yml", "release.yml"}) { + String content = Files.readString(Path.of(".gitea/workflows", workflow)); + assertTrue(content.contains("java-version: 25")); + assertTrue(content.contains("purpur-leaf-"), workflow); + assertFalse(content.contains("build/libs/leaf-") || content.contains("name: leaf-"), workflow); + } + } +}