From dd0122c54365fec3140d07292992af081f1cf8b6 Mon Sep 17 00:00:00 2001 From: Monikon Date: Fri, 28 Aug 2026 15:39:21 +0300 Subject: [PATCH 1/6] build(example-plugin): track a stand start.sh launcher outside generated/ generated/ is gitignored, so CI has nothing to launch the stand Paper server with. Commit the launcher example_plugin/README.md already told developers to write by hand, under stand-run/, and pin *.sh to LF via .gitattributes so a Windows checkout doesn't break the shebang. --- .gitattributes | 1 + example_plugin/README.md | 2 +- example_plugin/src/test/e2e/stand-run/start.sh | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100755 example_plugin/src/test/e2e/stand-run/start.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/example_plugin/README.md b/example_plugin/README.md index 30f425d..344d3b4 100644 --- a/example_plugin/README.md +++ b/example_plugin/README.md @@ -24,7 +24,7 @@ This one connects to a server that is already running and leaves it running. Pro cd src/test/e2e/generated/local/run && ./start.sh ``` -`generated/` is not in version control, so `start.sh` is yours to write. Anything that starts the jar with Java 21 will do: +`generated/` is not in version control, so `start.sh` is yours to write. Anything that starts the jar with Java 21 will do — CI keeps a tracked copy at `src/test/e2e/stand-run/start.sh` and copies it in before starting the server: ```sh #!/usr/bin/env sh diff --git a/example_plugin/src/test/e2e/stand-run/start.sh b/example_plugin/src/test/e2e/stand-run/start.sh new file mode 100755 index 0000000..481c160 --- /dev/null +++ b/example_plugin/src/test/e2e/stand-run/start.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env sh +# Tracked copy of the launcher example_plugin/README.md tells you to write yourself for a local +# `stand` run. CI copies this into generated/local/run/ (see ci.yml) since that directory is +# gitignored and can't hold a script of its own. Keep this in sync with the README snippet. +set -e + +cd "$(dirname "$0")" + +JAVA_BIN="${JAVA_BIN:-java}" +JVM_ARGS="${JVM_ARGS:--Xmx2G}" + +exec "$JAVA_BIN" $JVM_ARGS -Dcom.mojang.eula.agree=true -jar server.jar --nogui From 2592328f8eec378567b848f3315cdca507c15fc6 Mon Sep 17 00:00:00 2001 From: Monikon Date: Fri, 28 Aug 2026 15:39:27 +0300 Subject: [PATCH 2/6] ci(#61): run the stand environment suite after LocalMode test-example-plugin only ever exercised LocalMode - the stand (ExternalMode) env is excluded from the matrix since it needs an already-running server, so the RCON console channel, account-pool leasing and the justCreated registration flow had zero CI coverage. The LocalMode run plugwright-action already does leaves Paper, cache and libraries under generated/local/run/ - stand points at the same localhost:25565. Copy in the tracked launcher, start it in the background, retry plugwrightPingStand until it answers, then run plugwrightTestStand. Kill the server and upload its log on failure regardless of outcome. Closes #61. --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5e1dab..7a108f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,10 @@ jobs: test-example-plugin: if: "!contains(github.event.head_commit.message, 'skip-ci')" runs-on: ubuntu-latest + env: + # Test-only credentials for the Paper server this job starts and tears down itself. + PLUGWRIGHT_RCON_PASSWORD: plugwright + PLUGWRIGHT_BOT_PASSWORD: plugwright steps: - uses: actions/checkout@v4 @@ -32,3 +36,41 @@ jobs: java-version: "17" node-version: "24" working-directory: "./example_plugin" + # The LocalMode run above leaves Paper, cache and libraries under generated/local/run/ - + # stand reuses that same directory (build.gradle.kts points it at localhost:25565). Only + # start.sh is missing: generated/ is gitignored, so we copy in the tracked launcher. + - name: Start the stand Paper server + working-directory: ./example_plugin/src/test/e2e/generated/local/run + run: | + cp "$GITHUB_WORKSPACE/example_plugin/src/test/e2e/stand-run/start.sh" . + chmod +x start.sh + nohup ./start.sh > stand-server.log 2>&1 & + echo $! > stand-server.pid + - name: Wait for the stand server + working-directory: ./example_plugin + run: | + for i in $(seq 1 30); do + if ./gradlew plugwrightPingStand; then + exit 0 + fi + sleep 2 + done + echo "stand server did not come up in time" >&2 + exit 1 + - name: Run the stand suite + working-directory: ./example_plugin + run: ./gradlew plugwrightTestStand + - name: Stop the stand Paper server + if: always() + working-directory: ./example_plugin/src/test/e2e/generated/local/run + run: | + [ -f stand-server.pid ] && kill "$(cat stand-server.pid)" 2>/dev/null || true + - name: Upload stand server log + if: failure() + uses: actions/upload-artifact@v4 + with: + name: stand-server-log + path: | + example_plugin/src/test/e2e/generated/local/run/stand-server.log + example_plugin/build/reports/plugwright/stand.* + if-no-files-found: ignore From 879762101d6fc2532cfee1d2b95070a97c0cdb2e Mon Sep 17 00:00:00 2001 From: Monikon Date: Fri, 28 Aug 2026 15:50:46 +0300 Subject: [PATCH 3/6] ci(#61): install JDK 21 before starting the stand server start.sh just runs whatever "java" is on PATH, which the earlier plugwright-action step pinned to 17 for the Gradle daemon. Paper 1.21.11 needs 21 (example_plugin/build.gradle.kts pins the toolchain there) - LocalMode never hit this because Gradle resolves and downloads that toolchain JDK itself for its own server launch, but a plain shell script has no such resolution. CI run 33172008840 confirmed the crash: UnsupportedClassVersionError, class file version 65.0 vs runtime's 61.0. --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a108f4..c2edc98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,14 @@ jobs: java-version: "17" node-version: "24" working-directory: "./example_plugin" + # LocalMode's own server launch resolves a Java 21 toolchain (build.gradle.kts pins + # languageVersion 21 for Paper 1.21.11) regardless of the Gradle-daemon JDK above - Gradle + # downloads it on demand. start.sh has no such resolution, it just runs whatever "java" is + # on PATH, so put a real JDK 21 there before it needs one. + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" # The LocalMode run above leaves Paper, cache and libraries under generated/local/run/ - # stand reuses that same directory (build.gradle.kts points it at localhost:25565). Only # start.sh is missing: generated/ is gitignored, so we copy in the tracked launcher. From 2ab3e7d4572adb2162a76090f1686ef5948b2eab Mon Sep 17 00:00:00 2001 From: Monikon Date: Fri, 28 Aug 2026 16:07:39 +0300 Subject: [PATCH 4/6] ci(#61): split stand into its own parallel job test-example-plugin and test-example-plugin-stand now run on separate runners concurrently instead of stand chaining off the end of a single sequential job. Stand can no longer reuse the local job's generated/local/run/ (different runner, different filesystem), so it provisions its own Paper server via plugwright-action's gradle-args input (plugwrightProvisionLocal only - not the full plugwrightTest, which would also run and duplicate the local suite). Everything after that (JDK 21 setup, start.sh, ping retry-loop, plugwrightTestStand, teardown, failure log upload) is unchanged, just moved into the new job. Trades a duplicate Paper/plugin download for roughly half the wall-clock time versus running sequentially in one job. job names: test-example-plugin keeps its existing name (a maintainer required-status-check on it, if any, keeps matching); the new job is test-example-plugin-stand. --- .github/workflows/ci.yml | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2edc98..d6ea046 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,24 @@ jobs: test-example-plugin: if: "!contains(github.event.head_commit.message, 'skip-ci')" runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + # The example links all three by path, and npm cannot build a linked package: it packs + # the directory into a staging copy that never gets its dev dependencies. + - name: Build the local npm packages + run: | + npm run install:packages + npm run build:packages + - uses: drownek/plugwright-action@v1 + with: + java-version: "17" + node-version: "24" + working-directory: "./example_plugin" + + test-example-plugin-stand: + if: "!contains(github.event.head_commit.message, 'skip-ci')" + runs-on: ubuntu-latest env: # Test-only credentials for the Paper server this job starts and tears down itself. PLUGWRIGHT_RCON_PASSWORD: plugwright @@ -25,28 +43,32 @@ jobs: steps: - uses: actions/checkout@v4 - # The example links all three by path, and npm cannot build a linked package: it packs - # the directory into a staging copy that never gets its dev dependencies. + # Same duplicated build as test-example-plugin: this job runs on its own runner, in + # parallel with it, so it can't share that job's npm install/build output. - name: Build the local npm packages run: | npm run install:packages npm run build:packages + # Running in parallel with test-example-plugin means this job can't reuse its + # generated/local/run/ either - provision the same Paper server here instead of just + # running plugwrightTest, which would also run (and duplicate) the local suite. - uses: drownek/plugwright-action@v1 with: java-version: "17" node-version: "24" working-directory: "./example_plugin" - # LocalMode's own server launch resolves a Java 21 toolchain (build.gradle.kts pins - # languageVersion 21 for Paper 1.21.11) regardless of the Gradle-daemon JDK above - Gradle - # downloads it on demand. start.sh has no such resolution, it just runs whatever "java" is - # on PATH, so put a real JDK 21 there before it needs one. + gradle-args: plugwrightProvisionLocal + # The provisioning above resolves a Java 21 toolchain for itself (build.gradle.kts pins + # languageVersion 21 for Paper 1.21.11), but that's Gradle's own on-demand download, not + # something start.sh can reach - it just runs whatever "java" is on PATH, which + # plugwright-action pinned to 17 above for the Gradle daemon. Put a real JDK 21 there. - uses: actions/setup-java@v4 with: distribution: temurin java-version: "21" - # The LocalMode run above leaves Paper, cache and libraries under generated/local/run/ - - # stand reuses that same directory (build.gradle.kts points it at localhost:25565). Only - # start.sh is missing: generated/ is gitignored, so we copy in the tracked launcher. + # generated/ is gitignored, so start.sh - the launcher example_plugin/README.md tells + # developers to hand-write - never made it into the provisioned run dir. Copy in the + # tracked copy. - name: Start the stand Paper server working-directory: ./example_plugin/src/test/e2e/generated/local/run run: | From 1051650e66c754c8719846c4fbc389f0916bcb71 Mon Sep 17 00:00:00 2001 From: Monikon Date: Fri, 28 Aug 2026 16:14:40 +0300 Subject: [PATCH 5/6] ci(#61): install console-rcon before pinging the stand server plugwrightPingStand/plugwrightTestStand don't depend on plugwrightCompileTests (ExternalMode registers no prepareTask - registerTasks in ExternalMode.kt assumes the stand is already up and node_modules already has what it needs). The old sequential job got this for free as a side effect of plugwrightTest's dependsOn chain running first; the new parallel stand job only ran plugwrightProvisionLocal, so @plugwright/console-rcon was never installed and every ping failed with 'no console channel could be reached'. Verified locally: a clean node_modules, then ./gradlew plugwrightCompileTests, installs console-rcon/auth-authme/runner as expected. --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6ea046..9d3509f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,11 +52,16 @@ jobs: # Running in parallel with test-example-plugin means this job can't reuse its # generated/local/run/ either - provision the same Paper server here instead of just # running plugwrightTest, which would also run (and duplicate) the local suite. + # plugwrightCompileTests is the other half: plugwrightPingStand/plugwrightTestStand + # don't depend on it themselves (ExternalMode assumes the stand is already up, nothing + # to provision - see registerTasks in ExternalMode.kt), they just expect node_modules + # to already have what npm(...) plugin refs and console { rcon {} } need. - uses: drownek/plugwright-action@v1 with: java-version: "17" node-version: "24" working-directory: "./example_plugin" + gradle-args: plugwrightProvisionLocal plugwrightCompileTests gradle-args: plugwrightProvisionLocal # The provisioning above resolves a Java 21 toolchain for itself (build.gradle.kts pins # languageVersion 21 for Paper 1.21.11), but that's Gradle's own on-demand download, not From 41d7173c1021769c2598f02442b6d445602204a3 Mon Sep 17 00:00:00 2001 From: Monikon Date: Fri, 28 Aug 2026 16:16:05 +0300 Subject: [PATCH 6/6] ci(#61): fix duplicate gradle-args key from the previous commit The console-rcon fix's replace missed the pre-existing gradle-args line, leaving two under the same 'with:' block. Plain YAML parsers silently keep the last one (which is why local yaml.safe_load passed), but GitHub Actions' own parser rejects it outright - the run failed in 0s with zero jobs registered, no logs at all. --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d3509f..31fd7bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,6 @@ jobs: node-version: "24" working-directory: "./example_plugin" gradle-args: plugwrightProvisionLocal plugwrightCompileTests - gradle-args: plugwrightProvisionLocal # The provisioning above resolves a Java 21 toolchain for itself (build.gradle.kts pins # languageVersion 21 for Paper 1.21.11), but that's Gradle's own on-demand download, not # something start.sh can reach - it just runs whatever "java" is on PATH, which