feat(ignis): rename Purpur distribution and scope release access
Release / release (push) Successful in 4m58s
CI / build (push) Successful in 1m13s

BREAKING CHANGE: New release artifacts use purpur-ignis-<version>.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.
This commit is contained in:
dmg committed 2026-09-12 14:41:49 -04:00
1 parent 7edfe71d2d
commit e4bc296ce1
10 files changed
+86 -20

No files matched your search

+1 -1
View File
@@ -13,6 +13,6 @@ while IFS='=' read -r key value && [ -n "$key" ]; do
done done
[ "$protocol" = https ] || exit 0 [ "$protocol" = https ] || exit 0
[ "$host" = git.garvis.dev ] || 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 [ -n "${IGNIS_RELEASE_TOKEN:-}" ] || exit 0
printf 'username=dmg\npassword=%s\n\n' "$IGNIS_RELEASE_TOKEN" printf 'username=dmg\npassword=%s\n\n' "$IGNIS_RELEASE_TOKEN"
+1 -1
View File
@@ -35,6 +35,6 @@ jobs:
- name: Upload development artifact - name: Upload development artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: spigot-ignis-${{ github.sha }} name: purpur-ignis-${{ github.sha }}
path: build/libs/*.jar path: build/libs/*.jar
if-no-files-found: error if-no-files-found: error
+6 -6
View File
@@ -47,7 +47,7 @@ jobs:
set -euo pipefail set -euo pipefail
test -n "$IGNIS_RELEASE_TOKEN" || { echo 'RELEASE_TOKEN is required'; exit 1; } test -n "$IGNIS_RELEASE_TOKEN" || { echo 'RELEASE_TOKEN is required'; exit 1; }
test "$GITEA_SERVER_URL" = 'https://git.garvis.dev' 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. # semantic-release overrides GIT_ASKPASS; use Git's credential protocol instead.
# Token stays in environment/credential stdout, never in the remote URL or argv. # Token stays in environment/credential stdout, never in the remote URL or argv.
export GIT_TERMINAL_PROMPT=0 export GIT_TERMINAL_PROMPT=0
@@ -85,8 +85,8 @@ jobs:
if: steps.release.outputs.created == 'true' if: steps.release.outputs.created == 'true'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: spigot-ignis-${{ steps.release.outputs.version }} name: purpur-ignis-${{ steps.release.outputs.version }}
path: build/libs/spigot-ignis-${{ steps.release.outputs.version }}.jar path: build/libs/purpur-ignis-${{ steps.release.outputs.version }}.jar
if-no-files-found: error if-no-files-found: error
- name: Publish versioned Gitea asset - name: Publish versioned Gitea asset
@@ -102,9 +102,9 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
test "$GITEA_SERVER_URL" = 'https://git.garvis.dev' 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" 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) payload=$(mktemp)
response=$(mktemp) response=$(mktemp)
trap 'rm -f "$payload" "$response"' EXIT trap 'rm -f "$payload" "$response"' EXIT
@@ -127,4 +127,4 @@ jobs:
curl --fail --silent --show-error --max-time 120 --proto '=https' \ curl --fail --silent --show-error --max-time 120 --proto '=https' \
--header @- --header 'Content-Type: application/octet-stream' \ --header @- --header 'Content-Type: application/octet-stream' \
--data-binary "@$jar" --output /dev/null \ --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"
+2 -2
View File
@@ -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. 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. 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.
+6 -4
View File
@@ -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.** 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 ## 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. 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 ./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-<version>.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 ## 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 ## Foundation interfaces and state
+6 -1
View File
@@ -24,7 +24,12 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher") 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 val pluginVersion = version
tasks.processResources { tasks.processResources {
filesMatching("plugin.yml") { expand("version" to pluginVersion) } filesMatching("plugin.yml") { expand("version" to pluginVersion) }
+1 -1
View File
@@ -1,2 +1,2 @@
plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" }
rootProject.name = "spigot-ignis" rootProject.name = "purpur-ignis"
@@ -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);
}
}
}
@@ -11,8 +11,9 @@ class ReleaseCredentialTest {
void credentialHelperRefusesOtherHostsProtocolsRepositoriesAndOperations() throws Exception { void credentialHelperRefusesOtherHostsProtocolsRepositoriesAndOperations() throws Exception {
Path helper = Path.of(".gitea/scripts/git-credential-ignis.sh").toAbsolutePath(); Path helper = Path.of(".gitea/scripts/git-credential-ignis.sh").toAbsolutePath();
for (String request : java.util.List.of( for (String request : java.util.List.of(
"protocol=https\nhost=evil.example\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/spigot-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")) { "protocol=https\nhost=git.garvis.dev\npath=dmg/other.git\n\n")) {
var builder = new ProcessBuilder("sh", helper.toString(), "get"); var builder = new ProcessBuilder("sh", helper.toString(), "get");
builder.environment().put("IGNIS_RELEASE_TOKEN", "synthetic-test-token"); builder.environment().put("IGNIS_RELEASE_TOKEN", "synthetic-test-token");
@@ -38,7 +39,7 @@ class ReleaseCredentialTest {
builder.environment().put("GIT_TERMINAL_PROMPT", "0"); builder.environment().put("GIT_TERMINAL_PROMPT", "0");
Process process = builder.start(); Process process = builder.start();
try (var input = process.getOutputStream()) { 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)); assertTrue(process.waitFor(3, TimeUnit.SECONDS));
assertEquals(0, process.exitValue()); assertEquals(0, process.exitValue());
@@ -12,7 +12,9 @@ class ReleaseWorkflowTest {
Path path = Path.of(".gitea/workflows/release.yml"); Path path = Path.of(".gitea/workflows/release.yml");
assertTrue(Files.exists(path), "Release workflow must exist"); assertTrue(Files.exists(path), "Release workflow must exist");
String workflow = Files.readString(path); 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("--header @-"));
assertTrue(workflow.contains("git-credential-ignis.sh")); assertTrue(workflow.contains("git-credential-ignis.sh"));
assertTrue(workflow.contains("credential.useHttpPath true")); assertTrue(workflow.contains("credential.useHttpPath true"));