From e752f09f79c81ee3f596e8c4566e19451c0e227b Mon Sep 17 00:00:00 2001
From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com>
Date: Tue, 22 Sep 2026 22:15:43 +0000
Subject: [PATCH] ci: unify plugin artifact and ignore conventions
---
.github/scripts/plugin-artifact.py | 70 +++++++++++++++++++++++++++++
.github/workflows/build.yml | 15 +++++--
.github/workflows/maven-release.yml | 44 +++++-------------
.github/workflows/release.yml | 2 -
.gitignore | 5 +++
pom.xml | 2 +-
6 files changed, 100 insertions(+), 38 deletions(-)
create mode 100644 .github/scripts/plugin-artifact.py
diff --git a/.github/scripts/plugin-artifact.py b/.github/scripts/plugin-artifact.py
new file mode 100644
index 0000000..587ca45
--- /dev/null
+++ b/.github/scripts/plugin-artifact.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+"""Validate the Maven runtime JAR and optionally stage a release bundle."""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import shutil
+import zipfile
+
+
+def validate(source, version):
+ source = Path(source).resolve()
+ target = Path.cwd().resolve() / "target"
+ if not source.is_relative_to(target) or not source.is_file():
+ raise ValueError("Expected the Maven runtime JAR inside target/")
+ if not source.name.endswith(f"-{version}.jar"):
+ raise ValueError("Runtime JAR filename must end with the Maven version")
+ with zipfile.ZipFile(source) as jar:
+ descriptors = {"plugin.yml", "paper-plugin.yml"}.intersection(jar.namelist())
+ if not descriptors:
+ raise ValueError("Runtime JAR has no plugin descriptor")
+ if jar.testzip() is not None:
+ raise ValueError("Runtime JAR is corrupt")
+ for name in descriptors:
+ text = jar.read(name).decode("utf-8-sig")
+ versions = re.findall(
+ r'''^version:[ \t]*(?:"([^"\r\n]*)"|'([^'\r\n]*)'|([^\s#]+))(?:(?:[ \t]+\#[^\r\n]*)|[ \t]*)\r?$''',
+ text, re.MULTILINE,
+ )
+ if len(versions) != 1 or next((v for v in versions[0] if v), "") != version:
+ raise ValueError(f"{name} version must match Maven version {version}")
+ return source
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--jar", required=True, type=Path)
+ parser.add_argument("--version", required=True)
+ parser.add_argument("--stage", type=Path)
+ parser.add_argument("--dependencies", type=Path)
+ args = parser.parse_args()
+ try:
+ source = validate(args.jar, args.version)
+ if args.stage:
+ if os.environ["TAG"] != f"v{args.version}":
+ raise ValueError("Release tag must match the Maven version")
+ dependencies = json.loads(args.dependencies.read_text()) if args.dependencies else []
+ digest = hashlib.sha256(source.read_bytes()).hexdigest()
+ metadata = {
+ "repository": os.environ["GITHUB_REPOSITORY"],
+ "commit": os.environ["GITHUB_SHA"],
+ "tag": os.environ["TAG"],
+ "run": f"{os.environ['GITHUB_SERVER_URL']}/{os.environ['GITHUB_REPOSITORY']}/actions/runs/{os.environ['GITHUB_RUN_ID']}",
+ "plugin_dependencies": dependencies,
+ "artifact": source.name,
+ "sha256": digest,
+ }
+ args.stage.mkdir()
+ shutil.copyfile(source, args.stage / source.name)
+ (args.stage / "SHA256SUMS").write_text(f"{digest} {source.name}\n")
+ (args.stage / "build.json").write_text(json.dumps(metadata, indent=2) + "\n")
+ print(f"Verified {source.name}: filename and embedded version match {args.version}")
+ except (ValueError, OSError, KeyError, zipfile.BadZipFile) as error:
+ parser.exit(1, f"Plugin artifact validation failed: {error}\n")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4499876..fbac9c4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,16 +29,25 @@ jobs:
shell: bash
run: |
dev_version="DEV-$(date -u +%Y%m%d-%H%M)"
- mvn -B --no-transfer-progress org.codehaus.mojo:versions-maven-plugin:2.22.0:set \
+ mvn -B --no-transfer-progress -P'!deploy-live' org.codehaus.mojo:versions-maven-plugin:2.22.0:set \
-DnewVersion="$dev_version" -DgenerateBackupPoms=false
- mvn -B --no-transfer-progress help:evaluate \
+ mvn -B --no-transfer-progress -P'!deploy-live' help:evaluate \
-Dexpression=project.build.finalName -Doutput="$RUNNER_TEMP/final-name"
final_name=$(cat "$RUNNER_TEMP/final-name")
+ [[ "$final_name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || exit 1
+ echo "version=$dev_version" >> "$GITHUB_OUTPUT"
echo "name=$final_name" >> "$GITHUB_OUTPUT"
echo "jar=target/$final_name.jar" >> "$GITHUB_OUTPUT"
- name: Run unit tests and build
- run: mvn -B --no-transfer-progress clean verify -DskipTests=false -Dmaven.test.skip=false
+ run: mvn -B --no-transfer-progress -P'!deploy-live' clean verify -DskipTests=false -Dmaven.test.skip=false
+
+ - name: Verify runtime JAR
+ env:
+ ARTIFACT_PATH: ${{ steps.dev.outputs.jar }}
+ BUILD_VERSION: ${{ steps.dev.outputs.version }}
+ run: python3 .github/scripts/plugin-artifact.py --jar "$ARTIFACT_PATH" --version "$BUILD_VERSION"
+
- name: Upload plugin jar
uses: actions/upload-artifact@v7
diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml
index c2af865..b882b37 100644
--- a/.github/workflows/maven-release.yml
+++ b/.github/workflows/maven-release.yml
@@ -6,10 +6,6 @@ on:
java-version:
required: true
type: string
- artifact-path:
- description: Exact deployable JAR path, with optional {version} placeholder
- required: true
- type: string
secrets:
DEPS_TOKEN:
required: false
@@ -64,6 +60,7 @@ jobs:
fi
- name: Check Maven version and build
+ id: maven
env:
TAG: ${{ github.ref_name }}
run: |
@@ -74,39 +71,22 @@ jobs:
echo '::error::The tag must match project.version exactly.'
exit 1
}
- mvn -B --no-transfer-progress -P'!deploy-live' clean verify
+ mvn -B --no-transfer-progress -P'!deploy-live' help:evaluate \
+ -Dexpression=project.build.finalName -Doutput="$RUNNER_TEMP/final-name"
+ final_name=$(cat "$RUNNER_TEMP/final-name")
+ [[ "$final_name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || exit 1
+ echo "jar=target/$final_name.jar" >> "$GITHUB_OUTPUT"
+ mvn -B --no-transfer-progress -P'!deploy-live' clean verify -DskipTests=false -Dmaven.test.skip=false
- name: Stage only the release JAR
env:
- ARTIFACT_PATH: ${{ inputs.artifact-path }}
+ ARTIFACT_PATH: ${{ steps.maven.outputs.jar }}
TAG: ${{ github.ref_name }}
run: |
- python3 - <<'PY'
- import hashlib, json, os, pathlib, shutil, zipfile
- root = pathlib.Path.cwd().resolve()
- source = (root / os.environ['ARTIFACT_PATH'].replace('{version}', os.environ['TAG'][1:])).resolve()
- if not source.is_relative_to(root / 'target') or not source.is_file() or source.suffix != '.jar':
- raise SystemExit('Expected one existing JAR inside target/')
- with zipfile.ZipFile(source) as jar:
- if not {'plugin.yml', 'paper-plugin.yml'}.intersection(jar.namelist()):
- raise SystemExit('Release JAR has no plugin descriptor')
- if jar.testzip() is not None:
- raise SystemExit('Release JAR is corrupt')
- dest = pathlib.Path(os.environ['RUNNER_TEMP']) / 'plugin-release'
- dest.mkdir()
- shutil.copyfile(source, dest / source.name)
- digest = hashlib.sha256((dest / source.name).read_bytes()).hexdigest()
- (dest / 'SHA256SUMS').write_text(f'{digest} {source.name}\n')
- (dest / 'build.json').write_text(json.dumps({
- 'repository': os.environ['GITHUB_REPOSITORY'],
- 'commit': os.environ['GITHUB_SHA'],
- 'tag': os.environ['TAG'],
- 'run': f"{os.environ['GITHUB_SERVER_URL']}/{os.environ['GITHUB_REPOSITORY']}/actions/runs/{os.environ['GITHUB_RUN_ID']}",
- 'plugin_dependencies': json.loads(pathlib.Path('.build/plugin-dependencies.json').read_text()),
- 'artifact': source.name,
- 'sha256': digest,
- }, indent=2) + '\n')
- PY
+ python3 .github/scripts/plugin-artifact.py \
+ --jar "$ARTIFACT_PATH" --version "${TAG#v}" \
+ --stage "$RUNNER_TEMP/plugin-release" \
+ --dependencies .build/plugin-dependencies.json
- uses: actions/upload-artifact@v7
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4ab5494..349be05 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,5 +14,3 @@ jobs:
uses: ./.github/workflows/maven-release.yml
with:
java-version: '21'
- # Exact path, not a glob. {version} is replaced with the tag without v.
- artifact-path: target/surgery-{version}.jar
diff --git a/.gitignore b/.gitignore
index 68233c3..fee0cd8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,11 @@ nbbuild/
nbdist/
*.class
dependency-reduced-pom.xml
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
# Build dependencies
/.build/
diff --git a/pom.xml b/pom.xml
index 06b8fc3..803efa6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
net.tfminecraft
surgery
- 1.2.0
+ 1.2.1
surgery
tfminecraft.net