206 lines
6.5 KiB
JavaScript
206 lines
6.5 KiB
JavaScript
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);
|
|
}
|
|
}
|