From e4bc296ce1d138e19f3ff774ed9435d466d09b85 Mon Sep 17 00:00:00 2001 From: Dylan Garvis Date: Sat, 12 Sep 2026 14:41:49 -0400 Subject: [PATCH] feat(ignis): rename Purpur distribution and scope release access BREAKING CHANGE: New release artifacts use purpur-ignis-.jar. Java 25/Purpur requirements, runtime SpigotIgnis, data folder, PDC/recipe namespaces and gameplay remain unchanged. Replace the old JAR rather than installing both distributions. The existing credential helper now permits only the renamed HTTPS repository path. --- .gitea/scripts/git-credential-ignis.sh | 2 +- .gitea/workflows/ci.yml | 2 +- .gitea/workflows/release.yml | 12 ++-- AGENTS.md | 4 +- README.md | 10 ++-- build.gradle.kts | 7 ++- settings.gradle.kts | 2 +- .../spigotignis/PurpurDistributionTest.java | 56 +++++++++++++++++++ .../spigotignis/ReleaseCredentialTest.java | 7 ++- .../dmg/spigotignis/ReleaseWorkflowTest.java | 4 +- 10 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 src/test/java/games/dmg/spigotignis/PurpurDistributionTest.java diff --git a/.gitea/scripts/git-credential-ignis.sh b/.gitea/scripts/git-credential-ignis.sh index bc845e5..ec56f94 100755 --- a/.gitea/scripts/git-credential-ignis.sh +++ b/.gitea/scripts/git-credential-ignis.sh @@ -13,6 +13,6 @@ while IFS='=' read -r key value && [ -n "$key" ]; do done [ "$protocol" = https ] || exit 0 [ "$host" = git.garvis.dev ] || exit 0 -[ "$path" = dmg/spigot-ignis.git ] || exit 0 +[ "$path" = dmg/purpur-ignis.git ] || exit 0 [ -n "${IGNIS_RELEASE_TOKEN:-}" ] || exit 0 printf 'username=dmg\npassword=%s\n\n' "$IGNIS_RELEASE_TOKEN" diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 68f6c5c..d373e52 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -35,6 +35,6 @@ jobs: - name: Upload development artifact uses: actions/upload-artifact@v3 with: - name: spigot-ignis-${{ github.sha }} + name: purpur-ignis-${{ github.sha }} path: build/libs/*.jar if-no-files-found: error diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 7e12340..5a65787 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -47,7 +47,7 @@ jobs: set -euo pipefail test -n "$IGNIS_RELEASE_TOKEN" || { echo 'RELEASE_TOKEN is required'; exit 1; } test "$GITEA_SERVER_URL" = 'https://git.garvis.dev' - test "$REPOSITORY" = 'dmg/spigot-ignis' + test "$REPOSITORY" = 'dmg/purpur-ignis' # semantic-release overrides GIT_ASKPASS; use Git's credential protocol instead. # Token stays in environment/credential stdout, never in the remote URL or argv. export GIT_TERMINAL_PROMPT=0 @@ -85,8 +85,8 @@ jobs: if: steps.release.outputs.created == 'true' uses: actions/upload-artifact@v3 with: - name: spigot-ignis-${{ steps.release.outputs.version }} - path: build/libs/spigot-ignis-${{ steps.release.outputs.version }}.jar + name: purpur-ignis-${{ steps.release.outputs.version }} + path: build/libs/purpur-ignis-${{ steps.release.outputs.version }}.jar if-no-files-found: error - name: Publish versioned Gitea asset @@ -102,9 +102,9 @@ jobs: run: | set -euo pipefail test "$GITEA_SERVER_URL" = 'https://git.garvis.dev' - test "$REPOSITORY" = 'dmg/spigot-ignis' + test "$REPOSITORY" = 'dmg/purpur-ignis' api_url="${GITEA_SERVER_URL}/api/v1/repos/${REPOSITORY}/releases" - jar="build/libs/spigot-ignis-${VERSION}.jar" + jar="build/libs/purpur-ignis-${VERSION}.jar" payload=$(mktemp) response=$(mktemp) trap 'rm -f "$payload" "$response"' EXIT @@ -127,4 +127,4 @@ jobs: curl --fail --silent --show-error --max-time 120 --proto '=https' \ --header @- --header 'Content-Type: application/octet-stream' \ --data-binary "@$jar" --output /dev/null \ - "${api_url}/${release_id}/assets?name=spigot-ignis-${VERSION}.jar" + "${api_url}/${release_id}/assets?name=purpur-ignis-${VERSION}.jar" diff --git a/AGENTS.md b/AGENTS.md index d87a7d8..f89d70d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,8 @@ -# Spigot Ignis agent entrypoint +# Purpur Ignis agent entrypoint The canonical knowledge bundle is the sibling `../somc-okf/` repository, remotely at https://git.garvis.dev/dmg/somc-okf/src/branch/main/index.md. -Before changes read its root index, `processes/development.md`, `runbooks/development-cycle.md`, `projects/spigot-ignis/index.md`, `projects/spigot-ignis/engineering.md`, and the relevant `user-stories/spigot-ignis/` documents. Follow the parent workspace instructions. Do not create a local knowledge/design bundle. +Before changes read its root index, `processes/development.md`, `runbooks/development-cycle.md`, `projects/purpur-ignis/index.md`, `projects/purpur-ignis/engineering.md`, and the relevant `user-stories/purpur-ignis/` documents. Follow the parent workspace instructions. Do not create a local knowledge/design bundle. Use Java 25 and the declared Purpur API. Test behavior first with meaningful failures, implement minimally, then run `./gradlew clean check jar`. Never mark server/client behavior verified through domain tests alone. diff --git a/README.md b/README.md index 008b8b4..4c047d8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -# Spigot Ignis +# Purpur Ignis Server-wide earned shadow progression for Social Minecraft. **Echo of the Abyss, advancement-name scrambling, The Mask, The Dagger, and The Heart of Shadows are implemented in source; live-server/client verification is pending.** +The repository, checkout and new artifacts use `purpur-ignis`. Runtime `SpigotIgnis`, Java packages/entrypoint, `plugins/SpigotIgnis/state.bin`, all `spigotignis:` item/recipe/provenance keys and existing gameplay are retained. Replace the old JAR rather than installing both distributions. Old tags/assets remain available. This rename does not close known Echo consumption/recovery gaps. + ## Build Requires Java 17+ to launch Gradle; the wrapper resolves a Java 25 toolchain for compilation/testing. Toolchain downloads require network access when no compatible cache is available. @@ -11,13 +13,13 @@ Requires Java 17+ to launch Gradle; the wrapper resolves a Java 25 toolchain for ./gradlew clean jar -PreleaseVersion=0.1.0 ``` -Targets Purpur `26.2.build.2618-stable` and requires **ProtocolLib 5.4.0** for Mask helmet projection. Neither server API nor ProtocolLib is bundled. Source builds do not require the private wiki. +The runtime requires Java 25. New JARs are `build/libs/purpur-ignis-.jar`. Targets Purpur `26.2.build.2618-stable` and requires **ProtocolLib 5.4.0** for Mask helmet projection. Neither server API nor ProtocolLib is bundled. Source builds do not require the private wiki. ## Knowledge and delivery -The [canonical project](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/spigot-ignis/index.md) and stories live in the shared private SoMC wiki. There is no repository-local knowledge bundle. +The [canonical project](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/purpur-ignis/index.md) and stories live in the shared private SoMC wiki. There is no repository-local knowledge bundle. -CI/release workflows publish versioned source artifacts; verify their exact run/commit before using any artifact. Release publication requires the repository Actions secret `RELEASE_TOKEN`, provisioned separately. The release workflow uses a repository-scoped Git credential helper and curl stdin for authentication rather than embedding a release credential in URLs or process arguments. Semantic-release overrides `GIT_ASKPASS`, so askpass is not used for release authentication. Never enable tracing. GitOps and production release need separate approvals. +CI/release workflows publish versioned source artifacts; verify their exact run/commit before using any artifact. Release publication requires the repository Actions secret `RELEASE_TOKEN`, provisioned separately. The release workflow uses a Git credential helper scoped exclusively to `https://git.garvis.dev/dmg/purpur-ignis.git` and curl stdin for authentication rather than embedding a release credential in URLs or process arguments. Semantic-release overrides `GIT_ASKPASS`, so askpass is not used for release authentication. Never enable tracing. GitOps and production release need separate approvals. ## Foundation interfaces and state diff --git a/build.gradle.kts b/build.gradle.kts index b82ee76..fdd613f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,7 +24,12 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-launcher") } -tasks.test { useJUnitPlatform() } +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 tasks.processResources { filesMatching("plugin.yml") { expand("version" to pluginVersion) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0ef9ae6..8b1feb5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,2 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } -rootProject.name = "spigot-ignis" +rootProject.name = "purpur-ignis" diff --git a/src/test/java/games/dmg/spigotignis/PurpurDistributionTest.java b/src/test/java/games/dmg/spigotignis/PurpurDistributionTest.java new file mode 100644 index 0000000..90617ba --- /dev/null +++ b/src/test/java/games/dmg/spigotignis/PurpurDistributionTest.java @@ -0,0 +1,56 @@ +package games.dmg.spigotignis; + +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-ignis-" + 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("SpigotIgnis", metadata.get("name"), "Retain the data directory and persistent namespace"); + assertEquals("games.dmg.spigotignis.SpigotIgnisPlugin", metadata.get("main")); + assertTrue(((Map) metadata.get("permissions")).containsKey("ignis.admin")); + assertEquals(java.util.List.of("ProtocolLib"), metadata.get("depend")); + var descriptor = new org.bukkit.plugin.PluginDescriptionFile(jar.getInputStream(jar.getJarEntry("plugin.yml"))); + assertEquals("spigotignis", descriptor.namespace()); + assertFalse(jar.stream().anyMatch(entry -> entry.getName().startsWith("org/bukkit/") + || entry.getName().startsWith("org/purpurmc/") || entry.getName().startsWith("com/comphenix/") || 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/spigotignis/SpigotIgnisPlugin.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-ignis-"), workflow); + assertFalse(content.contains("spigot-ignis-"), workflow); + } + } +} diff --git a/src/test/java/games/dmg/spigotignis/ReleaseCredentialTest.java b/src/test/java/games/dmg/spigotignis/ReleaseCredentialTest.java index 80ff3a5..9540cda 100644 --- a/src/test/java/games/dmg/spigotignis/ReleaseCredentialTest.java +++ b/src/test/java/games/dmg/spigotignis/ReleaseCredentialTest.java @@ -11,8 +11,9 @@ class ReleaseCredentialTest { void credentialHelperRefusesOtherHostsProtocolsRepositoriesAndOperations() throws Exception { Path helper = Path.of(".gitea/scripts/git-credential-ignis.sh").toAbsolutePath(); for (String request : java.util.List.of( - "protocol=https\nhost=evil.example\npath=dmg/spigot-ignis.git\n\n", - "protocol=http\nhost=git.garvis.dev\npath=dmg/spigot-ignis.git\n\n", + "protocol=https\nhost=evil.example\npath=dmg/purpur-ignis.git\n\n", + "protocol=http\nhost=git.garvis.dev\npath=dmg/purpur-ignis.git\n\n", + "protocol=https\nhost=git.garvis.dev\npath=dmg/spigot-ignis.git\n\n", "protocol=https\nhost=git.garvis.dev\npath=dmg/other.git\n\n")) { var builder = new ProcessBuilder("sh", helper.toString(), "get"); builder.environment().put("IGNIS_RELEASE_TOKEN", "synthetic-test-token"); @@ -38,7 +39,7 @@ class ReleaseCredentialTest { builder.environment().put("GIT_TERMINAL_PROMPT", "0"); Process process = builder.start(); try (var input = process.getOutputStream()) { - input.write("protocol=https\nhost=git.garvis.dev\npath=dmg/spigot-ignis.git\n\n".getBytes(StandardCharsets.UTF_8)); + input.write("protocol=https\nhost=git.garvis.dev\npath=dmg/purpur-ignis.git\n\n".getBytes(StandardCharsets.UTF_8)); } assertTrue(process.waitFor(3, TimeUnit.SECONDS)); assertEquals(0, process.exitValue()); diff --git a/src/test/java/games/dmg/spigotignis/ReleaseWorkflowTest.java b/src/test/java/games/dmg/spigotignis/ReleaseWorkflowTest.java index 7d00f43..f76bc25 100644 --- a/src/test/java/games/dmg/spigotignis/ReleaseWorkflowTest.java +++ b/src/test/java/games/dmg/spigotignis/ReleaseWorkflowTest.java @@ -12,7 +12,9 @@ class ReleaseWorkflowTest { Path path = Path.of(".gitea/workflows/release.yml"); assertTrue(Files.exists(path), "Release workflow must exist"); String workflow = Files.readString(path); - assertTrue(workflow.contains("spigot-ignis-${VERSION}.jar")); + assertTrue(workflow.contains("purpur-ignis-${VERSION}.jar")); + assertEquals(2, workflow.split("test \\\"\\$REPOSITORY\\\" = 'dmg/purpur-ignis'", -1).length - 1); + assertFalse(workflow.contains("dmg/spigot-ignis")); assertTrue(workflow.contains("--header @-")); assertTrue(workflow.contains("git-credential-ignis.sh")); assertTrue(workflow.contains("credential.useHttpPath true"));