Add Minecraft 26.x support and version Java artifacts

This commit is contained in:
2026-10-07 13:18:31 +05:00
parent 48b34db6f1
commit dac8650ac1
50 changed files with 813 additions and 162 deletions

View File

@@ -1,10 +1,12 @@
import { createServer } from "node:http";
import { connect } from "node:net";
import { spawn } from "node:child_process";
import { spawn, execFile } from "node:child_process";
import { mkdir, copyFile, writeFile, readFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import { gunzipSync } from "node:zlib";
import assert from "node:assert/strict";
import path from "node:path";
import { promisify } from "node:util";
import { datasetSchema } from "../shared/protocol";
// Disposable, loopback-only Paper test. No game client is launched.
@@ -102,8 +104,50 @@ const api = createServer(async (req, res) => {
await new Promise<void>((r) => api.listen(0, "127.0.0.1", r));
await writeFile(
`${directory}/plugins/MineGeo/config.yml`,
`website: 'http://127.0.0.1:${(api.address() as any).port}'\ntoken: 'fixture-token'\nserver-name: 'Regression world'\nserver-address: 'play.regression.example.com'\nchunks-per-tick: 2\nviewpoints: 24\nmax-area-size: 2048\n`,
`website: 'http://127.0.0.1:${(api.address() as any).port}'\ntoken: ''\nserver-name: 'Regression world'\nserver-address: 'play.regression.example.com'\nchunks-per-tick: 2\nviewpoints: 24\nmax-area-size: 2048\n`,
);
// Exercise the shared upload transport against the loopback mock separately.
// The deployed plugin always uses its hardcoded production endpoint.
const execute = promisify(execFile);
let exportDir: string;
const pluginJar = path.resolve("public/tools/plugin/minegeo-plugin-0.1.3.jar");
await execute("javac", [
"-cp",
pluginJar,
"-d",
directory,
"tests/java/UploadSmoke.java",
]);
await writeFile(
directory + "/fixture-auth.json",
JSON.stringify({ token: "fixture-token" }),
);
const upload = async (
target: string | null,
expected: RegExp,
fails = false,
) => {
const args = [
"-cp",
directory + path.delimiter + pluginJar,
"UploadSmoke",
"http://127.0.0.1:" + (api.address() as any).port,
exportDir,
directory + "/fixture-auth.json",
];
if (target) args.push(target);
let output = "",
code = 0;
try {
const result = await execute("java", args, { windowsHide: true });
output = result.stdout + result.stderr;
} catch (error: any) {
output = error.stdout + error.stderr;
code = error.code;
}
assert.equal(code === 0, !fails, output);
assert.match(output, expected);
};
const child = spawn("java", ["-Xmx1536M", "-jar", "paper.jar", "--nogui"], {
cwd: directory,
stdio: ["pipe", "pipe", "pipe"],
@@ -165,46 +209,53 @@ const command = async (text: string, pattern: RegExp) => {
child.stdin.write(text + "\n");
await pending;
};
const rconCommand = (text: string, expected: RegExp) => new Promise<void>((resolve, reject) => {
const socket = connect(25581, "127.0.0.1");
let buffer = Buffer.alloc(0);
let authenticated = false;
const timeout = setTimeout(() => finish(new Error(`RCON timed out waiting for ${expected}`)), 10000);
const finish = (error?: Error) => {
clearTimeout(timeout);
socket.destroy();
if (error) reject(error);
else resolve();
};
const packet = (id: number, type: number, body: string) => {
const payload = Buffer.from(body);
const frame = Buffer.alloc(payload.length + 14);
frame.writeInt32LE(payload.length + 10, 0);
frame.writeInt32LE(id, 4);
frame.writeInt32LE(type, 8);
payload.copy(frame, 12);
return frame;
};
socket.on("connect", () => socket.write(packet(1, 3, "minegeo-fixture")));
socket.on("error", finish);
socket.on("data", (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
while (buffer.length >= 4 && buffer.length >= buffer.readInt32LE(0) + 4) {
const length = buffer.readInt32LE(0);
const frame = buffer.subarray(0, length + 4);
buffer = buffer.subarray(length + 4);
const id = frame.readInt32LE(4);
const type = frame.readInt32LE(8);
if (id === -1) return finish(new Error("RCON authentication failed"));
if (id === 1 && type === 2 && !authenticated) {
authenticated = true;
socket.write(packet(2, 2, text));
} else if (id === 2 && expected.test(frame.toString("utf8", 12, length + 2))) {
return finish();
const rconCommand = (text: string, expected: RegExp) =>
new Promise<void>((resolve, reject) => {
const socket = connect(25581, "127.0.0.1");
let buffer = Buffer.alloc(0);
let authenticated = false;
const timeout = setTimeout(
() => finish(new Error(`RCON timed out waiting for ${expected}`)),
10000,
);
const finish = (error?: Error) => {
clearTimeout(timeout);
socket.destroy();
if (error) reject(error);
else resolve();
};
const packet = (id: number, type: number, body: string) => {
const payload = Buffer.from(body);
const frame = Buffer.alloc(payload.length + 14);
frame.writeInt32LE(payload.length + 10, 0);
frame.writeInt32LE(id, 4);
frame.writeInt32LE(type, 8);
payload.copy(frame, 12);
return frame;
};
socket.on("connect", () => socket.write(packet(1, 3, "minegeo-fixture")));
socket.on("error", finish);
socket.on("data", (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
while (buffer.length >= 4 && buffer.length >= buffer.readInt32LE(0) + 4) {
const length = buffer.readInt32LE(0);
const frame = buffer.subarray(0, length + 4);
buffer = buffer.subarray(length + 4);
const id = frame.readInt32LE(4);
const type = frame.readInt32LE(8);
if (id === -1) return finish(new Error("RCON authentication failed"));
if (id === 1 && type === 2 && !authenticated) {
authenticated = true;
socket.write(packet(2, 2, text));
} else if (
id === 2 &&
expected.test(frame.toString("utf8", 12, length + 2))
) {
return finish();
}
}
}
});
});
});
try {
await wait(/Done \(/, 0, 180000);
await command("minegeo help", /MineGeo commands:/);
@@ -227,7 +278,7 @@ try {
let relative = (
await readFile(`${directory}/plugins/MineGeo/latest-export.txt`, "utf8")
).trim();
const exportDir = `${directory}/plugins/MineGeo/${relative}`;
exportDir = `${directory}/plugins/MineGeo/${relative}`;
const local = datasetSchema.parse(
JSON.parse(await readFile(`${exportDir}/manifest.json`, "utf8")),
);
@@ -243,44 +294,46 @@ try {
(p) => p.x >= -32 && p.x < 32 && p.z >= -32 && p.z < 32,
),
);
await command("minegeo upload", /Upload failed: Upload paused/);
await command("minegeo upload", /Upload complete\. Open/);
await upload(null, /Upload paused/, true);
await upload(null, /queueing panorama processing|Already uploaded/);
assert.equal(creates, 1);
assert.equal(finalizes, 1);
assert.equal(heads, 1);
await command("minegeo upload", /Upload complete\. Open/);
await upload(null, /queueing panorama processing|Already uploaded/);
assert.equal(finalizes, 1);
received = Buffer.alloc(0);
expectedServer = "wrong.example.com";
await command(
"minegeo contribute 00000000-0000-4000-8000-000000000024",
await upload(
"00000000-0000-4000-8000-000000000024",
/Wrong capture server/,
true,
);
assert.equal(creates, 1, "Mismatch must stop before creating a draft");
expectedServer = "PLAY.REGRESSION.EXAMPLE.COM.:25565";
await command(
"minegeo contribute 00000000-0000-4000-8000-000000000024",
/Upload complete\. Open/,
await upload(
"00000000-0000-4000-8000-000000000024",
/queueing panorama processing|Already uploaded/,
);
assert.equal(manifest.contributionTo, "00000000-0000-4000-8000-000000000024");
assert.equal(manifest.viewpoints.length, 12);
assert.equal(creates, 2);
expectedServer = "wrong.example.com";
await command(
"minegeo contribute 00000000-0000-4000-8000-000000000024",
await upload(
"00000000-0000-4000-8000-000000000024",
/Wrong capture server/,
true,
);
assert.equal(creates, 2, "A cached selection must recheck the server");
expectedServer = null;
await command(
"minegeo contribute 00000000-0000-4000-8000-000000000024",
/Upload complete\. Open/,
await upload(
"00000000-0000-4000-8000-000000000024",
/queueing panorama processing|Already uploaded/,
);
assert.equal(creates, 2, "Cleared server permits an idempotent retry");
assert.equal(finalizes, 2);
await command(
"minegeo contribute 00000000-0000-4000-8000-000000000024",
/Upload complete\. Open/,
await upload(
"00000000-0000-4000-8000-000000000024",
/queueing panorama processing|Already uploaded/,
);
assert.equal(creates, 2);
await command("minegeo export Random Positions", /Export complete:/);
@@ -321,7 +374,7 @@ try {
"repeated upload is idempotent",
"cancellation preserves completed export",
"24 random positions change on the next export",
"contribution command caps at 12 and retries idempotently",
"shared upload client caps contributions at 12 and retries idempotently",
"wrong server blocked before upload, including cached retry",
"owner override/default port and cleared address supported",
],