Core apis #528
@@ -1 +1,2 @@
 | 
			
		||||
export * as listVersion from "./listVersion.js";
 | 
			
		||||
export * as listVersion from "./listVersion.js";
 | 
			
		||||
export * from "./main.js";
 | 
			
		||||
@@ -1,20 +1,27 @@
 | 
			
		||||
import { http, Github } from "@sirherobrine23/http";
 | 
			
		||||
import { versionsStorages } from "../../serverRun.js";
 | 
			
		||||
import util from "node:util";
 | 
			
		||||
import xml from "xml-js";
 | 
			
		||||
 | 
			
		||||
export interface mojangInfo {
 | 
			
		||||
  releaseDate: Date;
 | 
			
		||||
  release: "oficial" | "beta";
 | 
			
		||||
  files: { [P in NodeJS.Platform]?: { [A in NodeJS.Architecture]?: string } }
 | 
			
		||||
  date: Date,
 | 
			
		||||
  release?: "oficial" | "preview",
 | 
			
		||||
  url: {
 | 
			
		||||
    [platform in NodeJS.Platform]?: {
 | 
			
		||||
      [arch in NodeJS.Architecture]?: {
 | 
			
		||||
        [ext in "tgz" | "zip"]?: string;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const mojangCache = new Map<string, mojangInfo>();
 | 
			
		||||
export const mojangCache = new versionsStorages<mojangInfo>();
 | 
			
		||||
export async function listMojang() {
 | 
			
		||||
  const versions = await http.jsonRequestBody<{ version: string, release?: "stable" | "preview", date: string, url: { [P in NodeJS.Platform]?: { [A in NodeJS.Architecture]?: string } } }[]>("https://raw.githubusercontent.com/Sirherobrine23/BedrockFetch/main/versions/all.json");
 | 
			
		||||
  versions.forEach(rel => mojangCache.has(rel.version) ? null : mojangCache.set(rel.version, {
 | 
			
		||||
    releaseDate: new Date(rel.release),
 | 
			
		||||
    release: !rel.release ? "oficial" : rel.release === "preview" ? "beta" : "oficial",
 | 
			
		||||
    files: rel.url
 | 
			
		||||
  const versions = await http.jsonRequestBody<({version: string} & mojangInfo)[]>("https://raw.githubusercontent.com/Sirherobrine23/BedrockFetch/main/versions/all.json");
 | 
			
		||||
  versions.filter(ver => !(mojangCache.has(ver.version))).forEach(rel => mojangCache.set(rel.version, {
 | 
			
		||||
    date: new Date(rel.date),
 | 
			
		||||
    release: rel.release,
 | 
			
		||||
    url: rel.url
 | 
			
		||||
  }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +41,7 @@ export interface powernukkitDownload {
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const powernukkitCache = new Map<string, powernukkitDownload>();
 | 
			
		||||
export const powernukkitCache = new versionsStorages<powernukkitDownload>();
 | 
			
		||||
export async function listPowernukkitProject() {
 | 
			
		||||
  const releases = await http.jsonRequestBody<{[k in "releases"|"snapshots"]: powerNukkitRelease[]}>("https://raw.githubusercontent.com/PowerNukkit/powernukkit-version-aggregator/master/powernukkit-versions.json");
 | 
			
		||||
  const releasesData = (Object.keys(releases) as (keyof typeof releases)[]).map(releaseType => releases[releaseType].map(data => ({...data, releaseType}))).flat(1).sort((b, a) => Math.min(1, Math.max(-1, a.releaseTime - b.releaseTime)));
 | 
			
		||||
@@ -70,8 +77,8 @@ export interface cloudburstDownload {
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const nukkitCache = new Map<string, cloudburstDownload>();
 | 
			
		||||
export const cloudburstCache = new Map<string, cloudburstDownload>();
 | 
			
		||||
export const nukkitCache = new versionsStorages<cloudburstDownload>();
 | 
			
		||||
export const cloudburstCache = new versionsStorages<cloudburstDownload>();
 | 
			
		||||
export async function listCloudburstProject() {
 | 
			
		||||
  const Projects = [ "Nukkit", "Server" ] as const;
 | 
			
		||||
  for (const Project of Projects) {
 | 
			
		||||
@@ -142,7 +149,7 @@ export interface pocketmineDownload {
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const pocketmineCache = new Map<string, pocketmineDownload>();
 | 
			
		||||
export const pocketmineCache = new versionsStorages<pocketmineDownload>();
 | 
			
		||||
const pocketmineGithub = await Github.repositoryManeger("pmmp", "PocketMine-MP");
 | 
			
		||||
export async function listPocketmineProject() {
 | 
			
		||||
  const pocketmineReleases = (await pocketmineGithub.release.getRelease()).filter(rel => (rel.assets.find(assert => assert.name.endsWith(".phar")) ?? {}).browser_download_url);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,210 @@
 | 
			
		||||
import { getCacheVersions } from "./listVersion.js";
 | 
			
		||||
import { serverManeger } from "../../serverRun.js";
 | 
			
		||||
import semver from "semver";
 | 
			
		||||
import { extendsFS } from "@sirherobrine23/extends";
 | 
			
		||||
import { http } from "@sirherobrine23/http";
 | 
			
		||||
import AdmZip from "adm-zip";
 | 
			
		||||
import child_process from "node:child_process";
 | 
			
		||||
import { createWriteStream } from "node:fs";
 | 
			
		||||
import fs from "node:fs/promises";
 | 
			
		||||
import { tmpdir } from "node:os";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import readline from "node:readline";
 | 
			
		||||
import { finished } from "node:stream/promises";
 | 
			
		||||
import tar from "tar";
 | 
			
		||||
import { bdsFilesBucket } from "../../internalClouds.js";
 | 
			
		||||
import { customEvent, defineEvents } from "../../serverRun.js";
 | 
			
		||||
import * as bedrockVersions from "./listVersion.js";
 | 
			
		||||
 | 
			
		||||
export type platforms = "mojang"|"pocketmine"|"powernukkit"|"nukkit"|"cloudburst";
 | 
			
		||||
export async function installServer({platform, version}: {platform?: platforms, version?: string} = {}) {
 | 
			
		||||
  if (!platform) platform = "mojang";
 | 
			
		||||
  else if ((!(["mojang", "pocketmine", "powernukkit", "nukkit", "cloudburst"]).includes(platform))) throw new Error("Invalid platform");
 | 
			
		||||
  const versions = getCacheVersions();
 | 
			
		||||
  const getLatest = (keys: IterableIterator<string>|string[]) => Array.from(keys).sort((b, a) => semver.compare(semver.valid(semver.coerce(a)), semver.valid(semver.coerce(b)))).at(0);
 | 
			
		||||
  if (!version) version = "latest";
 | 
			
		||||
  if (platform === "mojang") {
 | 
			
		||||
    if (version === "latest") version = getLatest(Object.keys(versions.mojang));
 | 
			
		||||
    const release = versions.mojang[version];
 | 
			
		||||
    if (!release) throw new Error("Not valid Release");
 | 
			
		||||
export type platforms = "mojang" | "pocketmine" | "powernukkit" | "nukkit" | "cloudburst";
 | 
			
		||||
 | 
			
		||||
export interface bedrockPorts { }
 | 
			
		||||
 | 
			
		||||
export interface playerInfo {
 | 
			
		||||
  connected: boolean;
 | 
			
		||||
  banned: boolean;
 | 
			
		||||
  historic: {
 | 
			
		||||
    action: "connected" | "spawned" | "disconnected";
 | 
			
		||||
    actionDate: Date;
 | 
			
		||||
  }[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class playerListen extends Map<string, playerInfo> {
 | 
			
		||||
  constructor() { super(); }
 | 
			
		||||
  toJSON() {
 | 
			
		||||
    return Array.from(this.keys()).reduce<{ [playerName: string]: playerInfo }>((acc, player) => {
 | 
			
		||||
      acc[player] = this.get(player);
 | 
			
		||||
      return acc;
 | 
			
		||||
    }, {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateState(playerName: string, state: playerInfo["historic"][number]["action"]) {
 | 
			
		||||
    const actionDate = new Date();
 | 
			
		||||
    if (!(this.has(playerName))) throw new Error("Set Player");
 | 
			
		||||
    const playerData = super.get(playerName);
 | 
			
		||||
    if (state === "disconnected") playerData.connected = false; else playerData.connected = true;
 | 
			
		||||
    playerData.historic.push({ action: state, actionDate });
 | 
			
		||||
    super.set(playerName, playerData);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function runServer() {}
 | 
			
		||||
/**
 | 
			
		||||
 * Return boolean if Class input is Bedrock class server
 | 
			
		||||
 * @param event - Bedrock class
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function isBedrock(event: Bedrock<any>): event is Bedrock<any> {
 | 
			
		||||
  return event instanceof Bedrock;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type bedrockEvents = defineEvents<{
 | 
			
		||||
  logLine(lineString: string): void;
 | 
			
		||||
  portListen(info: bedrockPorts): void
 | 
			
		||||
}>;
 | 
			
		||||
 | 
			
		||||
export class Bedrock<P extends platforms> extends customEvent<bedrockEvents> {
 | 
			
		||||
  readonly serverFolder: string;
 | 
			
		||||
  readonly rootServer: string;
 | 
			
		||||
  readonly platform: P;
 | 
			
		||||
  constructor(rootServer: string, platform: P) {
 | 
			
		||||
    super();
 | 
			
		||||
    this.platform = platform;
 | 
			
		||||
    this.rootServer = rootServer;
 | 
			
		||||
    this.serverFolder = path.join(rootServer, "server");
 | 
			
		||||
    Object.defineProperty(this, "rootServer", { writable: false });
 | 
			
		||||
    Object.defineProperty(this, "serverFolder", { writable: false });
 | 
			
		||||
    if ((!(["mojang", "pocketmine", "powernukkit", "nukkit", "cloudburst"]).includes(platform))) throw new Error("Invalid platform");
 | 
			
		||||
    Object.defineProperty(this, "platform", { writable: false });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async installServer(version: string | number) {
 | 
			
		||||
    const { platform } = this;
 | 
			
		||||
    if (!(await extendsFS.exists(this.serverFolder))) await fs.mkdir(this.serverFolder, { recursive: true });
 | 
			
		||||
    if (platform === "mojang") {
 | 
			
		||||
      const release = bedrockVersions.mojangCache.get(version);
 | 
			
		||||
      if (!release) throw new Error("Not valid Release");
 | 
			
		||||
      const serverURL = release.url[process.platform]?.[process.arch]?.tgz;
 | 
			
		||||
      if (!serverURL) throw new Error("Current platform not support mojang server");
 | 
			
		||||
      let backupFiles = [{ file: "allowlist.json", data: "" }, { file: "permissions.json", data: "" }, { file: "server.properties", data: "" }];
 | 
			
		||||
      backupFiles = await Promise.all(backupFiles.map(async file => { file.data = await fs.readFile(path.join(this.serverFolder, file.file), "utf8").catch(() => ""); return file }));
 | 
			
		||||
      await finished((await http.streamRequest(serverURL)).pipe(tar.extract({ cwd: this.serverFolder, preserveOwner: true, keep: false })));
 | 
			
		||||
      await Promise.all(backupFiles.filter(file => !!(file.data.trim())).map(async file => fs.writeFile(path.join(this.serverFolder, file.file), file.data)));
 | 
			
		||||
    } else if (platform === "pocketmine") {
 | 
			
		||||
      const release = bedrockVersions.pocketmineCache.get(version);
 | 
			
		||||
      if (!release) throw new Error("Not valid Release");
 | 
			
		||||
      await finished(await http.streamRequest(release.url), createWriteStream(path.join(this.serverFolder, "server.phar")));
 | 
			
		||||
      let phpFiles = (await bdsFilesBucket.listFiles("php_bin/")).map(file => ({ name: file.name.slice(8).toLowerCase(), data: file })).filter(file => file.name.startsWith(`${process.platform}_${process.arch}`));
 | 
			
		||||
      if (!phpFiles.length) throw new Error("Cannot get php binary to current platform");
 | 
			
		||||
      const phpFile = phpFiles.sort((b, a) => b.data.Dates.Modified.getTime() - a.data.Dates.Modified.getTime()).at(0);
 | 
			
		||||
      await fs.rm(path.join(this.serverFolder, "bin"), { recursive: true, force: true });
 | 
			
		||||
      if (phpFile.name.endsWith(".tar.gz") || phpFile.name.endsWith(".tgz")) await finished((await phpFile.data.getFile()).pipe(tar.extract({ cwd: path.join(this.serverFolder, "bin") })));
 | 
			
		||||
      else {
 | 
			
		||||
        const tmpFile = path.join(tmpdir(), Date.now() + "_" + phpFile.name);
 | 
			
		||||
        await finished((await phpFile.data.getFile()).pipe(createWriteStream(tmpFile)));
 | 
			
		||||
        await new Promise<void>((done, reject) => (new AdmZip(tmpFile)).extractAllToAsync(path.join(this.serverFolder, "bin"), true, true, err => err ? reject(err) : done()));
 | 
			
		||||
        await fs.rm(tmpFile, { force: true });
 | 
			
		||||
      }
 | 
			
		||||
    } else if (platform === "powernukkit") {
 | 
			
		||||
      const release = bedrockVersions.powernukkitCache.get(version);
 | 
			
		||||
      if (!release) throw new Error("Not valid Release");
 | 
			
		||||
      await finished(await http.streamRequest(release.url), createWriteStream(path.join(this.serverFolder, "server.jar")));
 | 
			
		||||
    } else if (platform === "cloudburst" || platform === "nukkit") {
 | 
			
		||||
      const platformVersions = platform === "cloudburst" ? bedrockVersions.cloudburstCache : bedrockVersions.nukkitCache;
 | 
			
		||||
      const release = platformVersions.get(version);
 | 
			
		||||
      if (!release) throw new Error("Not valid Release");
 | 
			
		||||
      await finished(await http.streamRequest(release.url), createWriteStream(path.join(this.serverFolder, "server.jar")));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ports: bedrockPorts[] = [];
 | 
			
		||||
  readonly players = new playerListen();
 | 
			
		||||
 | 
			
		||||
  serverProcess?: child_process.ChildProcess;
 | 
			
		||||
  async runServer() {
 | 
			
		||||
    const { platform } = this;
 | 
			
		||||
    if (platform === "nukkit" || platform === "powernukkit" || platform === "cloudburst") {
 | 
			
		||||
      const serverProcess = this.serverProcess = child_process.spawn("java", [
 | 
			
		||||
        "-XX:+UseG1GC",
 | 
			
		||||
        "-XX:+ParallelRefProcEnabled",
 | 
			
		||||
        "-XX:MaxGCPauseMillis=200",
 | 
			
		||||
        "-XX:+UnlockExperimentalVMOptions",
 | 
			
		||||
        "-XX:+DisableExplicitGC",
 | 
			
		||||
        "-XX:+AlwaysPreTouch",
 | 
			
		||||
        "-XX:G1NewSizePercent=30",
 | 
			
		||||
        "-XX:G1MaxNewSizePercent=40",
 | 
			
		||||
        "-XX:G1HeapRegionSize=8M",
 | 
			
		||||
        "-XX:G1ReservePercent=20",
 | 
			
		||||
        "-XX:G1HeapWastePercent=5",
 | 
			
		||||
        "-XX:G1MixedGCCountTarget=4",
 | 
			
		||||
        "-XX:InitiatingHeapOccupancyPercent=15",
 | 
			
		||||
        "-XX:G1MixedGCLiveThresholdPercent=90",
 | 
			
		||||
        "-XX:G1RSetUpdatingPauseTimePercent=5",
 | 
			
		||||
        "-XX:SurvivorRatio=32",
 | 
			
		||||
        "-XX:+PerfDisableSharedMem",
 | 
			
		||||
        "-XX:MaxTenuringThreshold=1",
 | 
			
		||||
        "-Dusing.aikars.flags=https://mcflags.emc.gs",
 | 
			
		||||
        "-Daikars.new.flags=true",
 | 
			
		||||
        "-jar", "server.jar",
 | 
			
		||||
      ], {
 | 
			
		||||
        cwd: this.serverFolder,
 | 
			
		||||
        stdio: ["pipe", "pipe", "pipe"],
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      serverProcess;
 | 
			
		||||
    } else if (platform === "pocketmine") {
 | 
			
		||||
      const phpBin = (await extendsFS.readdirV2(this.serverFolder)).find(file => file.endsWith("php")||file.endsWith("php.exe"));
 | 
			
		||||
      if (!phpBin) throw new Error("Fist install php binary in server folder");
 | 
			
		||||
      this.serverProcess = child_process.spawn(phpBin, ["server.phar", "--no-wizard"], {
 | 
			
		||||
        cwd: this.serverFolder,
 | 
			
		||||
        stdio: ["pipe", "pipe", "pipe"],
 | 
			
		||||
      });
 | 
			
		||||
    } else if (platform === "mojang") {
 | 
			
		||||
      const fileExec = path.join(this.serverFolder, (await fs.readdir(this.serverFolder)).find(file => file.startsWith("bedrock_server")));
 | 
			
		||||
      const serverProcess = this.serverProcess = child_process.spawn(fileExec, {
 | 
			
		||||
        cwd: this.serverFolder,
 | 
			
		||||
        stdio: ["pipe", "pipe", "pipe"]
 | 
			
		||||
      });
 | 
			
		||||
      serverProcess;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ([
 | 
			
		||||
      readline.createInterface(this.serverProcess.stdout),
 | 
			
		||||
      readline.createInterface(this.serverProcess.stderr)
 | 
			
		||||
    ]).map(inter => inter.on("error", err => this.emit("error", err)).on("line", data => this.emit("logLine", typeof data === "string" ? data : data[0])));
 | 
			
		||||
 | 
			
		||||
    return this.serverProcess;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  writeLn(data: string|Buffer) {
 | 
			
		||||
    this.serverProcess.stdin.write(data);
 | 
			
		||||
    if (typeof data === "string" && !(data.trim().endsWith("\n"))) this.serverProcess.stdin.write("\n");
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async stopServer() {
 | 
			
		||||
    this.writeLn("stop");
 | 
			
		||||
    return new Promise<{code: number, signal: NodeJS.Signals}>((done, reject) => this.serverProcess.once("error", reject).once("exit", (code, signal) => done({code, signal})));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async setPlayerPermission(playername: string, permission: P extends "mojang" ? "operator"|"member"|"visitor" : "admin"|"user") {
 | 
			
		||||
    if (this.platform === "mojang") {
 | 
			
		||||
      const permissions: {permission: "operator"|"member"|"visitor", xuid: string}[] = JSON.parse(await fs.readFile(path.join(this.serverFolder, "permissions.json"), "utf8"));
 | 
			
		||||
      permissions.push({
 | 
			
		||||
        permission: permission as any,
 | 
			
		||||
        xuid: playername
 | 
			
		||||
      });
 | 
			
		||||
      await fs.writeFile(path.join(this.serverFolder, "permissions.json"), JSON.stringify(permissions));
 | 
			
		||||
      if (this.serverProcess) this.writeLn("permission reload");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async allowList(playername: string, options?: {xuid?: string, ignoresPlayerLimit?: boolean}) {
 | 
			
		||||
    if (this.platform === "mojang") {
 | 
			
		||||
      const permissions: {ignoresPlayerLimit: boolean, name: string, xuid?: string}[] = JSON.parse(await fs.readFile(path.join(this.serverFolder, "allowlist.json"), "utf8"));
 | 
			
		||||
      await fs.writeFile(path.join(this.serverFolder, "allowlist.json"), JSON.stringify(permissions));
 | 
			
		||||
      permissions.push({
 | 
			
		||||
        name: playername,
 | 
			
		||||
        ignoresPlayerLimit: options?.ignoresPlayerLimit ?? false,
 | 
			
		||||
        xuid: options?.xuid
 | 
			
		||||
      });
 | 
			
		||||
      if (this.serverProcess) this.writeLn("allowlist reload");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
// Bedrock platform
 | 
			
		||||
export * as Bedrock from "./platform/bedrock/index.js";
 | 
			
		||||
export * as bedrock from "./platform/bedrock/index.js";
 | 
			
		||||
export { isBedrock } from "./platform/bedrock/index.js";
 | 
			
		||||
 | 
			
		||||
// Java platform
 | 
			
		||||
export * as Java from "./platform/java/index.js";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,15 @@
 | 
			
		||||
import { extendsFS } from "@sirherobrine23/extends";
 | 
			
		||||
import child_process from "node:child_process";
 | 
			
		||||
import EventEmitter from "node:events";
 | 
			
		||||
import { createWriteStream, promises as fs } from "node:fs";
 | 
			
		||||
import { createInterface as readline } from "node:readline";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import stream from "node:stream";
 | 
			
		||||
import tar from "tar";
 | 
			
		||||
 | 
			
		||||
export interface ManegerOptions {
 | 
			
		||||
  rootPath: string;
 | 
			
		||||
  runServer: {
 | 
			
		||||
    command: string;
 | 
			
		||||
    args?: (string | number | boolean)[];
 | 
			
		||||
    env?: Map<string, string | number | boolean> | { [K: string]: string | number | boolean },
 | 
			
		||||
    stdio?: child_process.StdioPipeNamed | child_process.StdioPipe[];
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type EventMap = Record<string, (...args: any[]) => void>;
 | 
			
		||||
export type EventMap = Record<string, (...args: any[]) => void>;
 | 
			
		||||
type EventKey<T extends EventMap> = string & keyof T;
 | 
			
		||||
export type defaultEvents = {
 | 
			
		||||
  "onServerSpawn": (child: child_process.ChildProcess) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Extens
 | 
			
		||||
 */
 | 
			
		||||
export class serverManeger<T extends EventMap = {}> extends EventEmitter {
 | 
			
		||||
  #io: ManegerOptions;
 | 
			
		||||
  #serverStorage: string;
 | 
			
		||||
  #logStorage: string;
 | 
			
		||||
  constructor(options: ManegerOptions) {
 | 
			
		||||
    super({ captureRejections: true });
 | 
			
		||||
    this.#io = options;
 | 
			
		||||
    this.#io.rootPath = path.resolve(process.cwd(), this.#io.rootPath);
 | 
			
		||||
    this.#serverStorage = path.join(this.#io.rootPath, "storage");
 | 
			
		||||
    this.#logStorage = path.join(this.#io.rootPath, "logs");
 | 
			
		||||
export type defineEvents<T extends EventMap> = T;
 | 
			
		||||
 | 
			
		||||
export class customEvent<T extends EventMap> extends EventEmitter {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super({captureRejections: true});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  on<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
 | 
			
		||||
  on<K extends EventKey<defaultEvents>>(eventName: K, fn: defaultEvents[K]): this;
 | 
			
		||||
  on(eventName: "line", fn: (line: string) => void): this;
 | 
			
		||||
  on(eventName: "error", fn: (err: Error) => void): this;
 | 
			
		||||
  on(eventName: string, fn: (...args: any) => void): this {
 | 
			
		||||
    super.on(eventName, fn);
 | 
			
		||||
@@ -48,95 +17,42 @@ export class serverManeger<T extends EventMap = {}> extends EventEmitter {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  once<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
 | 
			
		||||
  once<K extends EventKey<defaultEvents>>(eventName: K, fn: defaultEvents[K]): this;
 | 
			
		||||
  once(eventName: "line", fn: (line: string) => void): this;
 | 
			
		||||
  once(eventName: "error", fn: (err: Error) => void): this;
 | 
			
		||||
  once(eventName: string, fn: (...args: any) => void): this {
 | 
			
		||||
    super.once(eventName, fn);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeListener<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
 | 
			
		||||
  removeListener(eventName: "error", fn: (err: Error) => void): this;
 | 
			
		||||
  removeListener(eventName: string, listener: (...args: any[]) => void): this {
 | 
			
		||||
    super.removeListener(eventName, listener);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  off<K extends EventKey<T>>(eventName: K, fn: T[K]): this;
 | 
			
		||||
  off(eventName: "error", fn: (err: Error) => void): this;
 | 
			
		||||
  off(eventName: string, listener: (...args: any[]) => void): this {
 | 
			
		||||
    super.off(eventName, listener);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeAllListeners<K extends EventKey<T>>(eventName: K): this;
 | 
			
		||||
  removeAllListeners(event?: string): this {
 | 
			
		||||
    super.removeAllListeners(event);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  emit<K extends EventKey<T>>(eventName: K, ...args: Parameters<T[K]>): boolean;
 | 
			
		||||
  emit<K extends EventKey<defaultEvents>>(eventName: K, ...args: Parameters<defaultEvents[K]>): boolean;
 | 
			
		||||
  emit(name: "line", line: string): boolean;
 | 
			
		||||
  emit(name: "error", err: Error): boolean;
 | 
			
		||||
  emit(eventName: string, ...args: any): boolean {
 | 
			
		||||
    return super.emit(eventName, args);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  /** Get root from paths */
 | 
			
		||||
  getRoot(): string { return this.#io.rootPath; };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create tar.gz from server Storage, if server running create "snapshot" from server running state.
 | 
			
		||||
   *
 | 
			
		||||
   * @returns - Gzip tar
 | 
			
		||||
   */
 | 
			
		||||
  hotBackup(): stream.Readable {
 | 
			
		||||
    return tar.create({
 | 
			
		||||
      gzip: true,
 | 
			
		||||
      cwd: this.#serverStorage
 | 
			
		||||
    }, []);
 | 
			
		||||
export class versionsStorages<T> extends Map<string, T> {
 | 
			
		||||
  get(key: string|number): T {
 | 
			
		||||
    if (typeof key === "number") return super.get(Array.from(this.keys()).at(key));
 | 
			
		||||
    return super.get(key);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #severProcess: child_process.ChildProcess;
 | 
			
		||||
  getStdout() {
 | 
			
		||||
    return this.#severProcess.stdout;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getStderr() {
 | 
			
		||||
    return this.#severProcess.stderr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getStdin() {
 | 
			
		||||
    return this.#severProcess.stdin;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Start server
 | 
			
		||||
   *
 | 
			
		||||
   * @returns get from server actions
 | 
			
		||||
   */
 | 
			
		||||
  async startServer() {
 | 
			
		||||
    if (this.#severProcess) return;
 | 
			
		||||
    let processEnv: { [k: string]: string } = {};
 | 
			
		||||
    if (this.#io.runServer.env) {
 | 
			
		||||
      const { env } = this.#io.runServer;
 | 
			
		||||
      if (env instanceof Map) {
 | 
			
		||||
        processEnv = Array.from(env.keys()).reduce<typeof processEnv>((acc, keyName) => {
 | 
			
		||||
          if (env.get(keyName) !== undefined) acc[keyName] = String(env.get(keyName));
 | 
			
		||||
          return acc;
 | 
			
		||||
        }, {});
 | 
			
		||||
      } else {
 | 
			
		||||
        processEnv = Object.keys(env).reduce<typeof processEnv>((acc, keyName) => {
 | 
			
		||||
          if (env[keyName] !== undefined) acc[keyName] = String(env[keyName]);
 | 
			
		||||
          return acc;
 | 
			
		||||
        }, {});
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const runDate = new Date();
 | 
			
		||||
    this.#severProcess = child_process.spawn(this.#io.runServer.command, (this.#io.runServer.args || []).map(String), {
 | 
			
		||||
      env: { ...process.env, ...processEnv },
 | 
			
		||||
      cwd: this.#serverStorage,
 | 
			
		||||
      stdio: this.#io.runServer.stdio,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const logPath = path.join(this.#logStorage, String(runDate.getFullYear()), String(runDate.getMonth() + 1), String(runDate.getDate()));
 | 
			
		||||
    const logpathRoot = createWriteStream(path.join(this.#logStorage, "all.log"));
 | 
			
		||||
    if (!(await extendsFS.exists(logPath))) await fs.mkdir(logPath, {recursive: true});
 | 
			
		||||
    if (this.#severProcess.stdout) {
 | 
			
		||||
      this.#severProcess.stdout.pipe(createWriteStream(path.join(logPath, "stdout.log")));
 | 
			
		||||
      this.#severProcess.stdout.pipe(logpathRoot);
 | 
			
		||||
      readline(this.#severProcess.stdout).on("line", line => this.emit("line", line));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.#severProcess.stderr) {
 | 
			
		||||
      this.#severProcess.stderr.pipe(createWriteStream(path.join(logPath, "stderr.log")));
 | 
			
		||||
      this.#severProcess.stderr.pipe(logpathRoot);
 | 
			
		||||
      readline(this.#severProcess.stderr).on("line", line => this.emit("line", line));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.emit("onServerSpawn", this.#severProcess);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								packages/core/tests/bedrock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/core/tests/bedrock.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import { listVersion, Bedrock } from "../src/platform/bedrock/index.js";
 | 
			
		||||
import { homedir } from "node:os";
 | 
			
		||||
await listVersion.listMojang();
 | 
			
		||||
 | 
			
		||||
const mojang = new Bedrock(path.join(homedir(), ".bdsmaneger/playgroud/mojang"), "mojang");
 | 
			
		||||
const version = Array.from(listVersion.mojangCache.keys()).at(9);
 | 
			
		||||
console.log("Installing %s", version);
 | 
			
		||||
await mojang.installServer(version);
 | 
			
		||||
mojang.on("logLine", (line) => console.log(line[0]));
 | 
			
		||||
const pr = await mojang.runServer();
 | 
			
		||||
process.stdin.pipe(pr.stdin);
 | 
			
		||||
		Reference in New Issue
	
	Block a user