Add Minecraft 26.x support and version Java artifacts
This commit is contained in:
205
scripts/test-minecraft-versions.mjs
Normal file
205
scripts/test-minecraft-versions.mjs
Normal file
@@ -0,0 +1,205 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawn } from "node:child_process";
|
||||
import { mkdir, readFile, writeFile, copyFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { createHash } from "node:crypto";
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import path from "node:path";
|
||||
import { stripVTControlCharacters } from "node:util";
|
||||
|
||||
// Disposable servers listen only on loopback. No fixture uploads or tokens are
|
||||
// sent to MineGeo. Keep exports/logs locally as compatibility evidence.
|
||||
const root = path.resolve(".cache/minecraft-compat");
|
||||
const java = process.env.JAVA_HOME
|
||||
? path.join(
|
||||
process.env.JAVA_HOME,
|
||||
"bin",
|
||||
process.platform === "win32" ? "java.exe" : "java",
|
||||
)
|
||||
: "java";
|
||||
async function serverJar(version) {
|
||||
await mkdir(root, { recursive: true });
|
||||
const file = `${root}/paper-${version}.jar`;
|
||||
if (version === "1.21.11" && !existsSync(file))
|
||||
await copyFile(".cache/paper-proof/paper.jar", file);
|
||||
if (existsSync(file)) return file;
|
||||
const response = await fetch(
|
||||
`https://fill.papermc.io/v3/projects/paper/versions/${version}/builds`,
|
||||
{
|
||||
headers: {
|
||||
"User-Agent": "MineGeo/0.1.3 local compatibility verification",
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
const builds = await response.json();
|
||||
const download = (builds.find((b) => b.channel === "STABLE") ?? builds[0])
|
||||
.downloads["server:default"];
|
||||
const bytes = Buffer.from(await (await fetch(download.url)).arrayBuffer());
|
||||
assert.equal(
|
||||
createHash("sha256").update(bytes).digest("hex"),
|
||||
download.checksums.sha256,
|
||||
);
|
||||
await writeFile(file, bytes);
|
||||
return file;
|
||||
}
|
||||
function monitor(child, label) {
|
||||
let output = "";
|
||||
const waiters = new Set();
|
||||
let ended = false;
|
||||
const record = (bytes) => {
|
||||
output += stripVTControlCharacters(bytes.toString());
|
||||
for (const w of waiters)
|
||||
if (w.pattern.test(output.slice(w.start))) {
|
||||
clearTimeout(w.timer);
|
||||
waiters.delete(w);
|
||||
w.resolve();
|
||||
}
|
||||
};
|
||||
child.stdout.on("data", record);
|
||||
child.stderr.on("data", record);
|
||||
const exit = new Promise((resolve) =>
|
||||
child.on("exit", (code) => {
|
||||
ended = true;
|
||||
for (const w of waiters) {
|
||||
clearTimeout(w.timer);
|
||||
w.reject(new Error(`${label} exited ${code}: ${output.slice(-5000)}`));
|
||||
}
|
||||
waiters.clear();
|
||||
resolve(code);
|
||||
}),
|
||||
);
|
||||
return {
|
||||
get output() {
|
||||
return output;
|
||||
},
|
||||
exit,
|
||||
wait(pattern, start = output.length, timeout = 180000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (pattern.test(output.slice(start))) return resolve();
|
||||
if (ended)
|
||||
return reject(
|
||||
new Error(`${label} already exited: ${output.slice(-5000)}`),
|
||||
);
|
||||
const w = {
|
||||
pattern,
|
||||
start,
|
||||
resolve,
|
||||
reject,
|
||||
timer: setTimeout(() => {
|
||||
waiters.delete(w);
|
||||
reject(
|
||||
new Error(
|
||||
`${label} timed out for ${pattern}: ${output.slice(-5000)}`,
|
||||
),
|
||||
);
|
||||
}, timeout),
|
||||
};
|
||||
waiters.add(w);
|
||||
});
|
||||
},
|
||||
async command(command, pattern) {
|
||||
const ready = this.wait(pattern);
|
||||
child.stdin.write(command + "\n");
|
||||
await ready;
|
||||
},
|
||||
};
|
||||
}
|
||||
if (process.argv.includes("--fabric"))
|
||||
throw new Error(
|
||||
"This check runs PaperMC servers only; Minecraft clients are disabled.",
|
||||
);
|
||||
const selected = process.argv
|
||||
.find((arg) => arg.startsWith("--version="))
|
||||
?.split("=")[1];
|
||||
const matrix = ["1.21.11", "26.1.2", "26.2"].filter(
|
||||
(v) => !selected || v === selected,
|
||||
);
|
||||
for (const version of matrix) {
|
||||
const directory = `${root}/plugin-${version}-${Date.now()}`;
|
||||
await mkdir(`${directory}/plugins`, { recursive: true });
|
||||
await copyFile(await serverJar(version), `${directory}/paper.jar`);
|
||||
await copyFile(
|
||||
"public/tools/plugin/minegeo-plugin-0.1.3.jar",
|
||||
`${directory}/plugins/minegeo.jar`,
|
||||
);
|
||||
await writeFile(`${directory}/eula.txt`, "eula=true\n");
|
||||
await writeFile(
|
||||
`${directory}/server.properties`,
|
||||
[
|
||||
"server-ip=127.0.0.1",
|
||||
`server-port=25582`,
|
||||
"online-mode=false",
|
||||
"level-type=minecraft:flat",
|
||||
'generator-settings={"layers":[{"block":"minecraft:bedrock","height":1},{"block":"minecraft:dirt","height":2},{"block":"minecraft:grass_block","height":1}],"biome":"minecraft:plains"}',
|
||||
"generate-structures=false",
|
||||
"view-distance=3",
|
||||
"simulation-distance=2",
|
||||
"max-players=1",
|
||||
"spawn-protection=0",
|
||||
].join("\n") + "\n",
|
||||
);
|
||||
const server = spawn(java, ["-Xmx1536M", "-jar", "paper.jar", "--nogui"], {
|
||||
cwd: directory,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
});
|
||||
const logs = monitor(server, `Paper ${version}`);
|
||||
try {
|
||||
await logs.wait(/Done \(/, 0);
|
||||
console.log(`Paper ${version} started.`);
|
||||
|
||||
await logs.command("version MineGeo", /MineGeo version 0\.1\.3/);
|
||||
await logs.command(
|
||||
"minegeo website",
|
||||
/MineGeo website: https:\/\/minegeo\.vercel\.app/,
|
||||
);
|
||||
await logs.command(
|
||||
"minegeo website http://127.0.0.1:9999",
|
||||
/website is fixed to https:\/\/minegeo\.vercel\.app/,
|
||||
);
|
||||
await logs.command(
|
||||
"minegeo server play.compat.example",
|
||||
/Capture server address saved/,
|
||||
);
|
||||
await logs.command("minegeo area world -16 -16 16 16", /area selected/);
|
||||
await logs.command("forceload add -16 -16 15 15", /Marked|already marked/);
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
await logs.command(
|
||||
"minegeo export Compatibility World",
|
||||
/Export complete:/,
|
||||
);
|
||||
const relative = (
|
||||
await readFile(`${directory}/plugins/MineGeo/latest-export.txt`, "utf8")
|
||||
).trim();
|
||||
const manifest = JSON.parse(
|
||||
await readFile(
|
||||
`${directory}/plugins/MineGeo/${relative}/manifest.json`,
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
assert.equal(manifest.minecraftVersion, version);
|
||||
assert.equal(manifest.viewpoints.length, 24);
|
||||
const terrain = JSON.parse(
|
||||
gunzipSync(
|
||||
await readFile(
|
||||
`${directory}/plugins/MineGeo/${relative}/terrain.json.gz`,
|
||||
),
|
||||
).toString(),
|
||||
);
|
||||
assert.ok(terrain.blocks.length > 0);
|
||||
await logs.command(
|
||||
"minegeo upload",
|
||||
/Create a token in the website dashboard/,
|
||||
);
|
||||
console.log(
|
||||
`PLUGIN ${version} PASSED: fixed endpoint, commands, real export, correct version, ${terrain.blocks.length} blocks.`,
|
||||
);
|
||||
} finally {
|
||||
server.stdin.write("stop\n");
|
||||
const timeout = setTimeout(() => server.kill(), 20000);
|
||||
await logs.exit;
|
||||
clearTimeout(timeout);
|
||||
await writeFile(`${directory}/server.log`, logs.output);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user