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
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Shared TFMC XML formatting; see Docs/POM-CONVENTIONS.md.
[*.xml]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
2 changes: 2 additions & 0 deletions .github/dependencies.sha256
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
14850d7454374d7312305d3e84a391be720301e5964963f869b27ae97f6264eb libs/MMOCore-1.13.1.jar
660ff2a6ec86bc8d7779948cf65c1637e271a16e1ef812d6a273f4a5c5eec73c libs/MythicLib-1.7.jar
12 changes: 12 additions & 0 deletions .github/scripts/install-local-dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
# Run from the repository root after downloading the pinned JARs.
# Hash-qualified versions prevent different private JARs sharing a Maven cache key.
sha256sum --check .github/dependencies.sha256

mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
-Dfile="libs/MMOCore-1.13.1.jar" -DgroupId="local" -DartifactId="MMOCore" \
-Dversion="1.13.1-tfmc-14850d745437" -Dpackaging=jar -DgeneratePom=true "$@"
mvn -B --no-transfer-progress org.apache.maven.plugins:maven-install-plugin:3.1.4:install-file \
-Dfile="libs/MythicLib-1.7.jar" -DgroupId="local" -DartifactId="MythicLib" \
-Dversion="1.7-tfmc-660ff2a6ec86" -Dpackaging=jar -DgeneratePom=true "$@"
70 changes: 70 additions & 0 deletions .github/scripts/plugin-artifact.py
Original file line number Diff line number Diff line change
@@ -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 re.fullmatch(r"[a-z][a-z0-9]*-" + re.escape(version) + r"\.jar", source.name):
raise ValueError("Runtime JAR filename must use a lowercase alphanumeric name followed by 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()
8 changes: 8 additions & 0 deletions .github/scripts/prepare-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
: "${GH_TOKEN:?Set DEPS_TOKEN with Contents read access to TF-Minecraft/ServerAssets}"
ref=183a187cf6128371a31ad20f3b524f6f30d537b9
mkdir -p libs
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/14850d745437/MMOCore-1.13.1.jar?ref=$ref" > "libs/MMOCore-1.13.1.jar"
curl --fail --location --silent --show-error --retry 3 -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github.raw+json" "https://api.github.com/repos/TF-Minecraft/ServerAssets/contents/jars/660ff2a6ec86/MythicLib-1.7.jar?ref=$ref" > "libs/MythicLib-1.7.jar"
bash .github/scripts/install-local-dependencies.sh "$@"
77 changes: 77 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Build

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
- name: Install shared plugin dependencies
uses: TF-Minecraft/TLibs/.github/actions/setup-plugins@4f7a3545063cb2e8d06a034687c817ab863f0835
with:
mode: pinned
private-token: ${{ secrets.DEPS_TOKEN }}

- name: Prepare pinned dependencies
env:
GH_TOKEN: ${{ secrets.DEPS_TOKEN }}
run: bash .github/scripts/prepare-release.sh
- name: Set build version
id: dev
shell: bash
run: |
dev_version="main-SNAPSHOT"
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
dev_version="DEV-$(date -u +%Y%m%d-%H%M)"
fi
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 -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 -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"

- if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v7
with:
name: ${{ steps.dev.outputs.name }}-${{ github.run_id }}-${{ github.run_attempt }}
path: |
${{ steps.dev.outputs.jar }}
.build/plugin-dependencies.json
include-hidden-files: true
if-no-files-found: error

- name: Upload unit test reports
if: ${{ !cancelled() && hashFiles('target/surefire-reports/*.xml') != '' }}
uses: actions/upload-artifact@v7
with:
name: unit-test-reports-${{ github.run_id }}-${{ github.run_attempt }}
path: target/surefire-reports/
if-no-files-found: error
138 changes: 138 additions & 0 deletions .github/workflows/maven-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: Maven plugin release

on:
workflow_call:
inputs:
java-version:
required: true
type: string
secrets:
DEPS_TOKEN:
required: false

permissions:
contents: read

concurrency:
group: plugin-release-${{ github.repository }}-${{ github.ref }}
cancel-in-progress: false

defaults:
run:
shell: bash

jobs:
build:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: ${{ inputs.java-version }}

- name: Validate release version
env:
TAG: ${{ github.ref_name }}
run: |
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]] || {
echo '::error::Expected vMAJOR.MINOR or vMAJOR.MINOR.PATCH, with an optional prerelease suffix.'
exit 1
}
[[ "${TAG^^}" != *SNAPSHOT* ]] || exit 1

- name: Install shared plugin dependencies
uses: TF-Minecraft/TLibs/.github/actions/setup-plugins@4f7a3545063cb2e8d06a034687c817ab863f0835
with:
mode: pinned
private-token: ${{ secrets.DEPS_TOKEN }}

- name: Prepare pinned dependencies
env:
GH_TOKEN: ${{ secrets.DEPS_TOKEN }}
run: |
if [[ -f .github/scripts/prepare-release.sh ]]; then
bash .github/scripts/prepare-release.sh
fi

- name: Set version from tag and build
id: maven
env:
TAG: ${{ github.ref_name }}
run: |
mvn -B --no-transfer-progress -P'!deploy-live' org.codehaus.mojo:versions-maven-plugin:2.22.0:set \
-DnewVersion="${TAG#v}" -DgenerateBackupPoms=false
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: ${{ steps.maven.outputs.jar }}
TAG: ${{ github.ref_name }}
run: |
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:
name: plugin-release-${{ github.sha }}
path: ${{ runner.temp }}/plugin-release/
if-no-files-found: error
retention-days: 30

publish:
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/download-artifact@v8
with:
name: plugin-release-${{ github.sha }}
path: release

- name: Create draft release
working-directory: release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: |
sha256sum --check SHA256SUMS
flags=()
if [[ "$TAG" == *-* ]]; then flags+=(--prerelease); fi
gh api --method POST "repos/$GH_REPO/releases/generate-notes" \
-f tag_name="$TAG" --jq .body > "$RUNNER_TEMP/release-changes.md"
python3 - <<'PY'
import os, re
from pathlib import Path
from urllib.parse import quote
temp = Path(os.environ['RUNNER_TEMP'])
changes = (temp / 'release-changes.md').read_text().strip()
changes = re.sub(r"^## What's Changed\s*", '', changes)
changes = re.sub(r'^(#{1,5}) ', r'\1# ', changes, flags=re.MULTILINE)
lines = ['## Changes', '', changes or 'Initial release.', '', '## Downloads', '']
assets = sorted([*Path('.').glob('*.jar'), *Path('.').glob('*.tar.gz')])
assets += [Path('SHA256SUMS'), Path('build.json')]
base = f"https://github.com/{os.environ['GH_REPO']}/releases/download/{quote(os.environ['TAG'], safe='')}/"
for asset in assets:
if not asset.is_file():
raise SystemExit(f'Missing release asset: {asset}')
description = {'SHA256SUMS': ' — SHA-256 checksums.', 'build.json': ' — Build metadata.'}.get(asset.name, '')
lines.append(f'- [`{asset.name}`]({base}{quote(asset.name)}){description}')
(temp / 'release-notes.md').write_text('\n'.join(lines) + '\n')
PY
gh release create "$TAG" ./*.jar SHA256SUMS build.json \
--verify-tag --draft --title "$TAG" --notes-file "$RUNNER_TEMP/release-notes.md" "${flags[@]}"
18 changes: 18 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Release

on:
push:
tags: ['v*']

permissions:
contents: read

jobs:
release:
permissions:
contents: write
uses: ./.github/workflows/maven-release.yml
with:
java-version: '21'
secrets:
DEPS_TOKEN: ${{ secrets.DEPS_TOKEN }}
Loading