Update Platforms #390

Merged
Sirherobrine23 merged 10 commits from StashCode into main 2022-06-09 20:22:38 +00:00
34 changed files with 815 additions and 657 deletions
Showing only changes of commit cdc9345a33 - Show all commits

View File

@ -1,3 +0,0 @@
node_modules/
.git/
dist/

View File

@ -1,7 +1,22 @@
{
"recommendations": [
"github.copilot",
"formulahendry.code-runner",
"chrmarti.regex"
"chrmarti.regex",
"benshabatnoam.google-translate-ext",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml",
"ms-vscode-remote.remote-containers",
"wix.vscode-import-cost",
"eg2.vscode-npm-script",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense",
"aaron-bond.better-comments",
"vscode-icons-team.vscode-icons",
"me-dutour-mathieu.vscode-github-actions",
"cschleiden.vscode-github-actions",
"oderwat.indent-rainbow",
"ms-azuretools.vscode-docker"
]
}

16
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Bds Test",
"type": "node",
"request": "launch",
"runtimeExecutable": "node",
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
"args": ["testProject.ts"],
"cwd": "${workspaceRoot}",
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": ["<node_internals>/**", "node_modules/**"]
}
]
}

View File

@ -1,6 +1,11 @@
{
"files.eol": "\n",
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.defaultLanguage": "typescript",
"editor.minimap.enabled": false,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"files.exclude": {
"**/dist/": true,
"**/node_modules/": true

View File

@ -16,8 +16,7 @@
"scripts": {
"dev": "nodemon",
"build": "run-s build:*",
"test": "node testProject.js",
"test:docker": "docker build . -f test.Dockerfile",
"test": "ts-node testProject.ts",
"build:cjs": "tsc --outDir dist/cjs --module commonjs",
"build:esm": "tsc --outDir dist/esm --module es6 && node -e 'const fs = require(\"fs\");const path = require(\"path\");const read = (pathRe) => {for (const fileFolde of fs.readdirSync(pathRe)) {const filePath = path.join(pathRe, fileFolde);if (fs.statSync(filePath).isDirectory()) read(filePath);else {console.log(filePath, \"-->\", filePath.replace(/\\.js$/, \".mjs\"));fs.renameSync(filePath, filePath.replace(/\\.js$/, \".mjs\"));}}};read(\"dist/esm\");'"
},

View File

@ -1,23 +0,0 @@
import downloadServer from "./download_server";
export const name = "installServer";
export async function pocketmine() {
console.log("Installing pocketmine server");
const data = await downloadServer("pocketmine", "latest");
console.log(data);
return data;
}
export async function java() {
console.log("Installing java server");
const data = await downloadServer("java", "latest");
console.log(data);
return data;
}
export async function spigot() {
console.log("Installing spigot server");
const data = await downloadServer("spigot", "latest");
console.log(data);
return data;
}

View File

@ -4,6 +4,7 @@ import { gitBackupOption } from "./backup/git";
export type Platform = "bedrock"|"java"|"pocketmine"|"spigot";
export const PlatformArray = ["bedrock", "java", "pocketmine", "spigot"];
// Bds Session on declaretion function types
export type bdsSessionCommands = {
/** Exec any commands in server */
execCommand: (...command: Array<string|number>) => bdsSessionCommands;
@ -16,46 +17,55 @@ export type bdsSessionCommands = {
/** Stop Server */
stop: () => Promise<number|null>;
};
export type startServerOptions = {
/** Save only worlds/maps without server software - (Beta) */
storageOnlyWorlds?: boolean;
gitBackup?: gitBackupOption;
};
export type playerAction1 = {player: string, Date: Date; xuid?: string|undefined}
export type playerAction2 = playerAction1 & {action: "connect"|"disconnect"|"unknown"}
// Server events
export interface serverOn {
(act: "started", fn: (data: Date) => void);
(act: "err", fn: (data: Error|number) => void);
(act: "closed", fn: (data: number) => void);
(act: "player_ban", fn: (data: playerAction1) => void);
(act: "player", fn: (data: playerAction2) => void);
(act: "player_connect", fn: (data: playerAction1) => void);
(act: "player_disconnect", fn: (data: playerAction1) => void);
(act: "player_unknown", fn: (data: playerAction1) => void);
(act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void);
(act: "log", fn: (data: string) => void);
(act: "log_stdout", fn: (data: string) => void);
(act: "log_stderr", fn: (data: string) => void);
}
export type serverListen = {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"};
export type playerObject = {[player: string]: {action: "connect"|"disconnect"|"unknown"; date: Date; history: Array<{action: "connect"|"disconnect"|"unknown"; date: Date}>}};
export type BdsSession = {
/** Server Session ID */
id: string;
/** Server Started date */
startDate: Date;
logFile?: string;
/** register cron job to create backups */
creteBackup: (crontime: string|Date, option?: {type: "git"; config: gitBackupOption}|{type: "zip", pathStorage?: string}) => CronJob;
/** Get server players historic connections */
Player: playerObject;
/** Get Server ports. listening. */
ports: Array<serverListen>;
/** if exists server map get world seed, fist map not get seed */
seed?: string|number;
/** Server Started */
started: boolean;
/** Some platforms may have a plugin manager. */
addonManeger?: any;
/** register cron job to create backups */
creteBackup: (crontime: string|Date, option?: {type: "git"; config: gitBackupOption}|{type: "zip"}) => CronJob;
/** callback to log event */
log: {
on: (eventName: "all"|"err"|"out", listener: (data: string) => void) => void;
once: (eventName: "all"|"err"|"out", listener: (data: string) => void) => void;
};
/** If the server crashes or crashes, the callbacks will be called. */
onExit: (callback: (code: number) => void) => void;
/** Server actions, example on avaible to connect or banned¹ */
server: {
/** Server actions, example on avaible to connect or banned¹ */
on: (act: "started"|"ban", call: (...any) => void) => void;
on: serverOn;
/** Server actions, example on avaible to connect or banned¹ */
once: (act: "started"|"ban", call: (...any) => void) => void;
once: serverOn;
/** Server Started date */
startDate: Date;
/** Server Started */
started: boolean;
};
/** Get server players historic connections */
getPlayer: () => {[player: string]: {action: "connect"|"disconnect"|"unknown"; date: Date; history: Array<{action: "connect"|"disconnect"|"unknown"; date: Date}>}};
/** This is a callback that call a function, for some player functions */
onPlayer: (callback: (data: {player: string; action?: "connect"|"disconnect"|"unknown"; date: Date;}) => string) => void;
/** Get Server ports. listening. */
ports: () => Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}>;
/** Basic server functions. */
commands: bdsSessionCommands;
};

View File

@ -1,4 +1,11 @@
export default parse;
/**
* Parse Proprieties files and return a map of properties.
*
* @param Proper - String with the properties or similar files
* @returns
*/
export function parse(Proper: string): {[key: string]: string|number|true|false|null} {
const ProPri = {};
const ProperSplit = Proper.replace(/\r\n/g, "\n").replace(/\\\n/gi, "").split("\n").map(Line => Line.trim()).filter(line => /.*(\s+)?\=(\s+)?.*/.test(line) && !/^#/.test(line));
@ -15,6 +22,12 @@ export function parse(Proper: string): {[key: string]: string|number|true|false|
return ProPri;
}
/**
* Convert json to properities files.
*
* @param ProPri - String with properties file
* @returns
*/
export function stringify(ProPri: {[key: string]: any}): string {
const Proper = [];
for (const key in Object.keys(ProPri)) {

50
src/lib/customEvents.ts Normal file
View File

@ -0,0 +1,50 @@
import events from "node:events";
import { playerAction1, playerAction2 } from '../globalType';
declare interface bdsServerEvent {
// Emit
emit(act: "started", data: Date): boolean;
emit(act: "err", data: Error|number): boolean;
emit(act: "closed", data: number): boolean;
emit(act: "port_listen", data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}): boolean;
emit(act: "log", data: string): boolean;
emit(act: "log_stdout", data: string): boolean;
emit(act: "log_stderr", data: string): boolean;
emit(act: "player", data: playerAction2): boolean;
emit(act: "player_ban", data: playerAction1): boolean;
emit(act: "player_connect", data: playerAction1): boolean;
emit(act: "player_disconnect", data: playerAction1): boolean;
emit(act: "player_unknown", data: playerAction1): boolean;
// on
on(act: "started", fn: (data: Date) => void): this;
on(act: "err", fn: (data: Error|number) => void): this;
on(act: "closed", fn: (data: number) => void): this;
on(act: "player_ban", fn: (data: playerAction1) => void): this;
on(act: "player", fn: (data: playerAction2) => void): this;
on(act: "player_connect", fn: (data: playerAction1) => void): this;
on(act: "player_disconnect", fn: (data: playerAction1) => void): this;
on(act: "player_unknown", fn: (data: playerAction1) => void): this;
on(act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void): this;
on(act: "log", fn: (data: string) => void): this;
on(act: "log_stdout", fn: (data: string) => void): this;
on(act: "log_stderr", fn: (data: string) => void): this;
// Once
once(act: "started", fn: (data: Date) => void): this;
once(act: "err", fn: (data: Error|number) => void): this;
once(act: "closed", fn: (data: number) => void): this;
once(act: "player_ban", fn: (data: playerAction1) => void): this;
once(act: "player", fn: (data: playerAction2) => void): this;
once(act: "player_connect", fn: (data: playerAction1) => void): this;
once(act: "player_disconnect", fn: (data: playerAction1) => void): this;
once(act: "player_unknown", fn: (data: playerAction1) => void): this;
once(act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void): this;
once(act: "log", fn: (data: string) => void): this;
once(act: "log_stdout", fn: (data: string) => void): this;
once(act: "log_stderr", fn: (data: string) => void): this;
}
class bdsServerEvent extends events {}
export default bdsServerEvent;
export {bdsServerEvent};

View File

@ -0,0 +1,13 @@
import * as net from "node:net";
export default async function portIsAllocated(port: number): Promise<boolean> {
return new Promise<boolean>((resolve) => {
const tester = net.createServer()
tester.once("error", () => () => resolve(true));
tester.once("listening", () => {
tester.once("close", () => resolve(false));
tester.close();
});
tester.listen(port);
});
}

View File

@ -0,0 +1,30 @@
import * as bedrock from "./index";
export default async function bedrockTest() {
// Download Server
const download = await bedrock.DownloadServer(true);
console.log("Version: %s, Date: %o, url: %s", download.version, download.publishDate, download.url);
// Config
const currentConfig = await bedrock.config.getConfig();
console.log("Current config: %o", currentConfig);
currentConfig.gamemode = Math.floor(Math.random() * 2) > 3 ? "survival" : "creative";
currentConfig.worldName = "test"+Math.floor(Math.random()*100);
const newConfig = await bedrock.config.CreateServerConfig(currentConfig);
console.log("New config: %o", newConfig);
// Run Server
const server = await bedrock.server.startServer();
server.server.once("started", () => console.log("Server started"));
await new Promise((resolve, reject) => {
server.server.once("started", resolve);
server.server.once("closed", resolve);
server.server.once("err", reject);
});
await server.commands.stop();
// Backup
const zipFile = await bedrock.backup.CreateBackup();
console.log("Backup created: %o", zipFile.length);
return;
}

View File

@ -48,7 +48,7 @@ export type bedrockConfig = {
texturepackRequired?: true|false
};
export async function CreateServerConfig(config: bedrockConfig): Promise<bedrockConfig&{configArray: string}> {
export async function CreateServerConfig(config: bedrockConfig): Promise<bedrockConfig> {
if (!!config.difficulty) {
if (typeof config.difficulty === "number") {
if (config.difficulty === 1) config.difficulty = "peaceful";
@ -203,8 +203,7 @@ export async function CreateServerConfig(config: bedrockConfig): Promise<bedrock
tickDistance,
playerIdleTimeout,
maxCpuThreads,
texturepackRequired,
configArray: configFileArray.join("\n")
texturepackRequired
}
}
@ -221,6 +220,10 @@ type bedrockParsedConfig = {
difficulty: "peaceful"|"easy"|"normal"|"hard",
/** The seed to be used for randomizing the world (`If left empty a seed will be chosen at random`). */
worldSeed: string|number,
port: {
v4: number,
v6: number
},
/** World NBT */
nbtParsed: {parsed: NBT, type: NBTFormat, metadata: nbtData}
};
@ -232,6 +235,10 @@ export async function getConfig(): Promise<bedrockParsedConfig> {
difficulty: "normal",
maxPlayers: 0,
worldSeed: "",
port: {
v4: 19132,
v6: 19133
},
nbtParsed: undefined
};
if (fs.existsSync(path.join(serverPath, "server.properties"))) {
@ -241,6 +248,21 @@ export async function getConfig(): Promise<bedrockParsedConfig> {
if (ProPri["gamemode"] !== undefined) config.gamemode = String(ProPri["gamemode"]) as "survival"|"creative"|"adventure";
if (ProPri["max-players"] !== undefined) config.maxPlayers = Number(ProPri["max-players"]);
if (ProPri["difficulty"] !== undefined) config.difficulty = String(ProPri["difficulty"]) as "peaceful"|"easy"|"normal"|"hard";
if (ProPri["server-port"] !== undefined) config.port.v4 = Number(ProPri["server-port"]);
if (ProPri["server-portv6"] !== undefined) config.port.v6 = Number(ProPri["server-portv6"]);
if (ProPri["level-seed"] !== undefined) config.worldSeed = String(ProPri["level-seed"]);
// if (ProPri["allow-cheats"] !== undefined) config.allowCheats = Boolean(ProPri["allow-cheats"]);
// if (ProPri["allow-list"] !== undefined) config.allowList = Boolean(ProPri["allow-list"]);
// if (ProPri["texturepack-required"] !== undefined) config.texturepackRequired = Boolean(ProPri["texturepack-required"]);
// if (ProPri["view-distance"] !== undefined) config.viewDistance = Number(ProPri["view-distance"]);
// if (ProPri["tick-distance"] !== undefined) config.tickDistance = Number(ProPri["tick-distance"]);
// if (ProPri["player-idle-timeout"] !== undefined) config.playerIdleTimeout = Number(ProPri["player-idle-timeout"]);
// if (ProPri["max-threads"] !== undefined) config.maxCpuThreads = Number(ProPri["max-threads"]);
// if (ProPri["default-player-permission-level"] !== undefined) config.PlayerDefaultPermissionLevel = String(ProPri["default-player-permission-level"]);
// if (ProPri["emit-server-telemetry"] !== undefined) config.emitServerTelemetry = Boolean(ProPri["emit-server-telemetry"]);
// if (ProPri["content-log-file-enabled"] !== undefined) config.contentLogFileEnabled = Boolean(ProPri["content-log-file-enabled"]);
// if (ProPri["compression-threshold"] !== undefined) config.compressionThreshold = Number(ProPri["compression-threshold"]);
// if (ProPri["server-authoritative-movement"] !== undefined) config.
const worldDatePath = path.join(serverPath, "worlds", config.worldName, "level.dat");
if (fs.existsSync(worldDatePath)) config.nbtParsed = await nbtParse(await fsPromise.readFile(worldDatePath));
if (ProPri["level-seed"] !== undefined) config.worldSeed = String(ProPri["level-seed"]);

View File

@ -1,23 +0,0 @@
import { DownloadServer, config, server } from "./index";
export async function testBedrock() {
const downlaod = await DownloadServer("latest");
console.log(downlaod);
const currentConfig = await config.getConfig();
console.log(currentConfig);
currentConfig.worldName = "test";
const newConfig = await config.CreateServerConfig(currentConfig);
console.log(newConfig);
console.log("Starting bedrock server...");
const session = await server.startServer();
session.log.on("all", console.log);
await new Promise(res => {
session.server.once("started", (date: Date) => {
console.log("bedrock started at", date);
console.log("Stoping bedrock");
res("");
});
});
console.log("Stoping bedrock");
return session.commands.stop();
}

View File

@ -1,26 +1,27 @@
import path from "node:path";
import fs from "node:fs";
import events from "events";
import crypto from "crypto";
import node_cron from "cron";
import * as child_process from "../../childProcess";
import { backupRoot, serverRoot } from "../../pathControl";
import { BdsSession, bdsSessionCommands } from "../../globalType";
import { BdsSession, bdsSessionCommands, playerAction2 } from '../../globalType';
import { getConfig } from "./config";
import { gitBackup, gitBackupOption } from "../../backup/git";
import { createZipBackup } from "../../backup/zip";
import events from "../../lib/customEvents";
import portislisten from "../../lib/portIsAllocated";
const bedrockSesions: {[key: string]: BdsSession} = {};
export function getSessions() {return bedrockSesions;}
const ServerPath = path.join(serverRoot, "bedrock");
export async function startServer(): Promise<BdsSession> {
if (!(fs.existsSync(ServerPath))) throw new Error("Install server first");
const SessionID = crypto.randomUUID();
const Process: {command: string; args: Array<string>; env: {[env: string]: string};} = {
command: "",
args: [],
env: {...process.env}
};
const serverConfig = await getConfig();
if (await portislisten(serverConfig.port.v4)) throw new Error("Port is already in use");
if (await portislisten(serverConfig.port.v6)) throw new Error("Port is already in use");
const Process: {command: string; args: Array<string>; env: {[env: string]: string};} = {command: "", args: [], env: {...process.env}};
if (process.platform === "darwin") throw new Error("Run Docker image");
Process.command = path.resolve(ServerPath, "bedrock_server"+(process.platform === "win32"?".exe":""));
if (process.platform !== "win32") {
@ -37,68 +38,45 @@ export async function startServer(): Promise<BdsSession> {
}
// Start Server
const serverEvents = new events();
const StartDate = new Date();
const serverEvents = new events({captureRejections: false});
serverEvents.setMaxListeners(0);
const ServerProcess = await child_process.execServer({runOn: "host"}, Process.command, Process.args, {env: Process.env, cwd: ServerPath});
const { onExit } = ServerProcess;
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
const uid = crypto.randomUUID();
playerCallbacks[uid] = {callback: callback};
return uid;
};
// Log Server redirect to callbacks events and exit
ServerProcess.on("out", data => serverEvents.emit("log_stdout", data));
ServerProcess.on("err", data => serverEvents.emit("log_stderr", data));
ServerProcess.on("all", data => serverEvents.emit("log", data));
ServerProcess.Exec.on("exit", code => {
serverEvents.emit("closed", code);
if (code === null) serverEvents.emit("err", new Error("Server exited with code null"));
});
const playersConnections: {
[player: string]: {
action: "connect"|"disconnect"|"unknown";
date: Date;
history: Array<{
action: "connect"|"disconnect"|"unknown";
date: Date
}>
}
} = {};
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
// on start
serverEvents.on("log", lineData => {
// [2022-05-19 22:35:09:315 INFO] Server started.
if (/\[.*\]\s+Server\s+started\./.test(lineData)) serverEvents.emit("started", new Date());
});
// Port
onLog.on("all", data => {
serverEvents.on("log", data => {
const portParse = data.match(/(IPv[46])\s+supported,\s+port:\s+(.*)/);
if (!!portParse) {
ports.push({
port: parseInt(portParse[2]),
version: portParse[1] as "IPv4"|"IPv6",
protocol: "UDP"
});
}
if (!!portParse) serverEvents.emit("port_listen", {port: parseInt(portParse[2]), protocol: "UDP", version: portParse[1] as "IPv4"|"IPv6"});
});
// Player
onLog.on("all", data => {
serverEvents.on("log", data => {
if (/r\s+.*\:\s+.*\,\s+xuid\:\s+.*/gi.test(data)) {
const actionDate = new Date();
const [action, player, xuid] = (data.match(/r\s+(.*)\:\s+(.*)\,\s+xuid\:\s+(.*)/)||[]).slice(1, 4);
const __PlayerAction: {player: string, xuid: string|undefined, action: "connect"|"disconnect"|"unknown"} = {
player: player,
xuid: xuid,
action: "unknown"
};
if (action === "connected") __PlayerAction.action = "connect"; else if (action === "disconnected") __PlayerAction.action = "disconnect";
if (!playersConnections[__PlayerAction.player]) playersConnections[__PlayerAction.player] = {
action: __PlayerAction.action,
date: actionDate,
history: [{
action: __PlayerAction.action,
date: actionDate
}]
}; else {
playersConnections[__PlayerAction.player].action = __PlayerAction.action;
playersConnections[__PlayerAction.player].date = actionDate;
playersConnections[__PlayerAction.player].history.push({
action: __PlayerAction.action,
date: actionDate
});
}
const __PlayerAction: playerAction2 = {player: player, xuid: xuid, action: "unknown", Date: actionDate};
if (action === "connected") __PlayerAction.action = "connect";
else if (action === "disconnected") __PlayerAction.action = "disconnect";
// Server player event
serverEvents.emit("player", __PlayerAction);
delete __PlayerAction.action;
if (action === "connect") serverEvents.emit("player_connect", __PlayerAction);
else if (action === "disconnect") serverEvents.emit("player_disconnect", __PlayerAction);
else serverEvents.emit("player_unknown", __PlayerAction);
}
});
@ -164,7 +142,7 @@ export async function startServer(): Promise<BdsSession> {
}
});
CrontimeBackup.start();
onExit().catch(() => null).then(() => CrontimeBackup.stop());
serverEvents.on("closed", () => CrontimeBackup.stop());
return CrontimeBackup;
}
@ -172,43 +150,50 @@ export async function startServer(): Promise<BdsSession> {
const logFile = path.resolve(process.env.LOG_PATH||path.resolve(ServerPath, "../log"), `bedrock_${SessionID}.log`);
if(!(fs.existsSync(path.parse(logFile).dir))) fs.mkdirSync(path.parse(logFile).dir, {recursive: true});
const logStream = fs.createWriteStream(logFile, {flags: "w+"});
logStream.write(`[${StartDate.toString()}] Server started\n\n`);
logStream.write(`[${(new Date()).toString()}] Server started\n\n`);
ServerProcess.Exec.stdout.pipe(logStream);
ServerProcess.Exec.stderr.pipe(logStream);
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
// Session Object
const Seesion: BdsSession = {
id: SessionID,
startDate: StartDate,
logFile: logFile,
creteBackup: backupCron,
onExit: onExit,
onPlayer: onPlayer,
ports: () => ports,
getPlayer: () => playersConnections,
server: {
on: serverOn,
once: serverOnce
},
seed: (await getConfig()).worldSeed,
started: false,
addonManeger: undefined,
log: onLog,
seed: serverConfig.worldSeed,
ports: [],
Player: {},
commands: serverCommands,
server: {
on: (act, fn) => serverEvents.on(act, fn),
once: (act, fn) => serverEvents.once(act, fn),
started: false,
startDate: new Date(),
}
};
onLog.on("all", lineData => {
// [2022-05-19 22:35:09:315 INFO] Server started.
if (/\[.*\]\s+Server\s+started\./.test(lineData)) {
Seesion.started = true;
serverEvents.emit("started", new Date());
serverEvents.on("port_listen", Seesion.ports.push);
serverEvents.on("started", date => {Seesion.server.started = true; Seesion.server.startDate = date;});
serverEvents.on("player", __PlayerAction => {
// Add to object
if (!Seesion.Player[__PlayerAction.player]) Seesion.Player[__PlayerAction.player] = {
action: __PlayerAction.action,
date: __PlayerAction.Date,
history: [{
action: __PlayerAction.action,
date: __PlayerAction.Date
}]
}; else {
Seesion.Player[__PlayerAction.player].action = __PlayerAction.action;
Seesion.Player[__PlayerAction.player].date = __PlayerAction.Date;
Seesion.Player[__PlayerAction.player].history.push({
action: __PlayerAction.action,
date: __PlayerAction.Date
});
}
});
// Return Session
bedrockSesions[SessionID] = Seesion;
onExit().catch(() => null).then(() => delete bedrockSesions[SessionID]);
serverEvents.on("closed", () => delete bedrockSesions[SessionID]);
return Seesion;
}

View File

@ -2,29 +2,29 @@ import * as bedrock from "./bedrock/index";
import * as pocketmine from "./pocketmine/index";
import * as java from "./java/index";
import * as spigot from "./spigot/index";
import { BdsSession } from "../globalType";
// import { BdsSession } from "../globalType";
//
// type globalPlatform = {
// [platform: string]: {
// /**
// * Download Server (and ¹auto install).
// *
// * 1: **In java server required java installation (if not installed, it will install it)**
// */
// DownloadServer: (version: string|boolean) => Promise<{version: string, publishDate: Date, url: string}>,
// server: {
// /** get server session */
// startServer: () => Promise<BdsSession>,
// /** Get all Server Sessions */
// getSessions: () => {[sessionId: string]: BdsSession}
// },
// backup: {
// /** Create Platform Backup, and return Buffer zip File */
// CreateBackup: () => Promise<Buffer>,
// /** Restore Zip Backup, this option replace local files */
// RestoreBackup: (buffer: Buffer) => Promise<void>,
// },
// }
// };
type globalPlatform = {
[platform: string]: {
/**
* Download Server (and ¹auto install).
*
* 1: **In java server required java installation (if not installed, it will install it)**
*/
DownloadServer: (version: string|boolean) => Promise<{version: string, publishDate: Date, url: string}>,
server: {
/** get server session */
startServer: () => Promise<BdsSession>,
/** Get all Server Sessions */
getSessions: () => {[sessionId: string]: BdsSession}
},
backup: {
/** Create Platform Backup, and return Buffer zip File */
CreateBackup: () => Promise<Buffer>,
/** Restore Zip Backup, this option replace local files */
RestoreBackup: (buffer: Buffer) => Promise<void>,
},
}
};
export default {bedrock, java, pocketmine, spigot} as globalPlatform;
export default {bedrock, java, pocketmine, spigot}

View File

@ -0,0 +1,23 @@
import * as java from "./index";
export default async function javaTest() {
// Download and install server
const download = await java.DownloadServer(true);
console.log("Version: %s, Date: %o, url: %s", download.version, download.publishDate, download.url);
// Start Server
const server = await java.server.startServer();
server.server.on("log", console.log);
server.server.once("started", () => console.log("Server started"));
server.server.on("port_listen", port => console.log("Server listening on port: %o with protocol: %s", port.port, port.protocol));
await new Promise((resolve, reject) => {
server.server.once("started", resolve);
server.server.once("closed", code => code === null ? resolve(code) : reject(code));
});
await server.commands.stop();
// Backup
const zipFile = await java.backup.CreateBackup();
console.log("Backup created: %o", zipFile.length);
return;
}

View File

@ -1,13 +1,13 @@
import path from "node:path";
import fs from "node:fs";
import events from "events";
import crypto from "crypto";
import node_cron from "cron";
import * as child_process from "../../childProcess";
import { backupRoot, serverRoot } from "../../pathControl";
import { BdsSession, bdsSessionCommands } from "../../globalType";
import { BdsSession, bdsSessionCommands, serverListen } from '../../globalType';
import { gitBackup, gitBackupOption } from "../../backup/git";
import { createZipBackup } from "../../backup/zip";
import events from "../../lib/customEvents";
const javaSesions: {[key: string]: BdsSession} = {};
export function getSessions() {return javaSesions;}
@ -19,27 +19,13 @@ export async function startServer(): Promise<BdsSession> {
const serverEvents = new events();
const StartDate = new Date();
const ServerProcess = await child_process.execServer({runOn: "host"}, "java", ["-jar", "Server.jar"], {cwd: ServerPath});
const { onExit } = ServerProcess;
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
const uid = crypto.randomUUID();
playerCallbacks[uid] = {callback: callback};
return uid;
};
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
const playersConnections: {[player: string]: {action: "connect"|"disconnect"|"unknown", date: Date, history: Array<{action: "connect"|"disconnect"|"unknown", date: Date}>}} = {};
// Parse ports
onLog.on("all", data => {
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
if (!!portParse) {
ports.push({
port: parseInt(portParse[2]),
version: "IPv4/IPv6"
});
}
// Log Server redirect to callbacks events and exit
ServerProcess.on("out", data => serverEvents.emit("log_stdout", data));
ServerProcess.on("err", data => serverEvents.emit("log_stderr", data));
ServerProcess.on("all", data => serverEvents.emit("log", data));
ServerProcess.Exec.on("exit", code => {
serverEvents.emit("closed", code);
if (code === null) serverEvents.emit("err", new Error("Server exited with code null"));
});
// Run Command
@ -104,7 +90,7 @@ export async function startServer(): Promise<BdsSession> {
}
});
CrontimeBackup.start();
onExit().catch(() => null).then(() => CrontimeBackup.stop());
serverEvents.on("closed", () => CrontimeBackup.stop());
return CrontimeBackup;
}
@ -116,39 +102,49 @@ export async function startServer(): Promise<BdsSession> {
ServerProcess.Exec.stdout.pipe(logStream);
ServerProcess.Exec.stderr.pipe(logStream);
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
// Session Object
const Seesion: BdsSession = {
id: SessionID,
startDate: StartDate,
creteBackup: backupCron,
onExit: onExit,
onPlayer: onPlayer,
ports: () => ports,
getPlayer: () => playersConnections,
server: {
on: serverOn,
once: serverOnce
},
ports: [],
Player: {},
seed: undefined,
started: false,
addonManeger: undefined,
log: onLog,
commands: serverCommands,
server: {
on: (act, fn) => serverEvents.on(act, fn),
once: (act, fn) => serverEvents.once(act, fn),
started: false,
startDate: StartDate
}
};
onLog.on("all", lineData => {
// Detect server start
serverEvents.on("log", lineData => {
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) {
Seesion.started = true;
serverEvents.emit("started", new Date());
const StartDate = new Date();
Seesion.server.startDate = StartDate;
serverEvents.emit("started", StartDate);
Seesion.server.started = true;
}
});
// Parse ports
serverEvents.on("log", data => {
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
if (!!portParse) {
const portObject: serverListen = {
port: parseInt(portParse[2]),
version: "IPv4/IPv6"
};
serverEvents.emit("port_listen", portObject);
Seesion.ports.push(portObject);
}
});
// Return Session
javaSesions[SessionID] = Seesion;
onExit().catch(() => null).then(() => delete javaSesions[SessionID]);
serverEvents.on("closed", () => delete javaSesions[SessionID]);
return Seesion;
}

View File

@ -1,13 +1,13 @@
import path from "node:path";
import fs from "node:fs";
import events from "events";
import crypto from "crypto";
import node_cron from "cron";
import * as child_process from "../../childProcess";
import { backupRoot, serverRoot } from "../../pathControl";
import { BdsSession, bdsSessionCommands } from "../../globalType";
import { BdsSession, bdsSessionCommands, serverListen, playerAction2 } from '../../globalType';
import { gitBackup, gitBackupOption } from "../../backup/git";
import { createZipBackup } from "../../backup/zip";
import events from "../../lib/customEvents";
const pocketmineSesions: {[key: string]: BdsSession} = {};
export function getSessions() {return pocketmineSesions;}
@ -31,79 +31,12 @@ export async function startServer(): Promise<BdsSession> {
const serverEvents = new events();
const StartDate = new Date();
const ServerProcess = await child_process.execServer({runOn: "host"}, Process.command, Process.args, {env: Process.env, cwd: ServerPath});
const { onExit } = ServerProcess;
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
const uid = crypto.randomUUID();
playerCallbacks[uid] = {callback: callback};
return uid;
};
const playersConnections: {
[player: string]: {
action: "connect"|"disconnect"|"unknown";
date: Date;
history: Array<{
action: "connect"|"disconnect"|"unknown";
date: Date
}>
}
} = {};
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
// Port listen
onLog.on("all", data => {
// [16:49:31.284] [Server thread/INFO]: Minecraft network interface running on [::]:19133
// [16:49:31.273] [Server thread/INFO]: Minecraft network interface running on 0.0.0.0:19132
if (/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+.*/gi.test(data)) {
const matchString = data.match(/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(.*)/);
if (!!matchString) {
const portParse = matchString[1];
const isIpv6 = /\[.*\]/.test(portParse);
if (isIpv6) {
ports.push({
port: parseInt(portParse.replace(/\[.*\]:/, "").trim()),
version: "IPv6"
});
} else {
ports.push({
port: parseInt(portParse.split(":")[1]),
version: "IPv4"
});
}
}
}
});
// Player Actions
onLog.on("all", data => {
if (/\[.*\]:\s+(.*)\s+(.*)\s+the\s+game/gi.test(data)) {
const actionDate = new Date();
const [action, player] = (data.match(/[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi)||[]).slice(1, 3);
const __PlayerAction: {player: string, action: "connect"|"disconnect"|"unknown"} = {
player: player,
action: "unknown"
};
if (action === "joined") __PlayerAction.action = "connect";
else if (action === "left") __PlayerAction.action = "disconnect";
if (!playersConnections[__PlayerAction.player]) playersConnections[__PlayerAction.player] = {
action: __PlayerAction.action,
date: actionDate,
history: [{
action: __PlayerAction.action,
date: actionDate
}]
}; else {
playersConnections[__PlayerAction.player].action = __PlayerAction.action;
playersConnections[__PlayerAction.player].date = actionDate;
playersConnections[__PlayerAction.player].history.push({
action: __PlayerAction.action,
date: actionDate
});
}
}
});
const { onExit, on: execOn } = ServerProcess;
// Log Server redirect to callbacks events and exit
execOn("out", data => serverEvents.emit("log_stdout", data));
execOn("err", data => serverEvents.emit("log_stderr", data));
execOn("all", data => serverEvents.emit("log", data));
onExit().catch(err => {serverEvents.emit("err", err);return null}).then(code => serverEvents.emit("closed", code));
// Run Command
const serverCommands: bdsSessionCommands = {
@ -167,7 +100,7 @@ export async function startServer(): Promise<BdsSession> {
}
});
CrontimeBackup.start();
onExit().catch(() => null).then(() => CrontimeBackup.stop());
serverEvents.on("closed", () => CrontimeBackup.stop());
return CrontimeBackup;
}
@ -179,39 +112,89 @@ export async function startServer(): Promise<BdsSession> {
ServerProcess.Exec.stdout.pipe(logStream);
ServerProcess.Exec.stderr.pipe(logStream);
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
// Session Object
const Seesion: BdsSession = {
id: SessionID,
startDate: StartDate,
creteBackup: backupCron,
onExit: onExit,
onPlayer: onPlayer,
ports: () => ports,
getPlayer: () => playersConnections,
server: {
on: serverOn,
once: serverOnce
},
ports: [],
Player: {},
seed: undefined,
started: false,
addonManeger: undefined,
log: onLog,
commands: serverCommands,
server: {
on: (act, fn) => serverEvents.on(act, fn),
once: (act, fn) => serverEvents.once(act, fn),
startDate: StartDate,
started: false
}
};
onLog.on("all", lineData => {
// On server started
serverEvents.on("log", lineData => {
// [22:52:05.580] [Server thread/INFO]: Done (0.583s)! For help, type "help" or "?"
if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) {
Seesion.started = true;
serverEvents.emit("started", new Date());
const StartDate = new Date();
Seesion.server.startDate = StartDate;
serverEvents.emit("started", StartDate);
Seesion.server.started = true;
}
});
// Port listen
serverEvents.on("log", data => {
// [16:49:31.284] [Server thread/INFO]: Minecraft network interface running on [::]:19133
// [16:49:31.273] [Server thread/INFO]: Minecraft network interface running on 0.0.0.0:19132
if (/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+.*/gi.test(data)) {
const matchString = data.match(/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(.*)/);
if (!!matchString) {
const portParse = matchString[1];
const isIpv6 = /\[.*\]:/.test(portParse);
const portObject: serverListen = {port: 0, version: "IPv4"};
if (!isIpv6) portObject.port = parseInt(portParse.split(":")[1]);
else {
portObject.port = parseInt(portParse.replace(/\[.*\]:/, "").trim())
portObject.version = "IPv6";
}
serverEvents.emit("port_listen", portObject);
Seesion.ports.push(portObject);
}
}
});
// Player Actions
serverEvents.on("log", data => {
if (/\[.*\]:\s+(.*)\s+(.*)\s+the\s+game/gi.test(data)) {
const actionDate = new Date();
const [action, player] = (data.match(/[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi)||[]).slice(1, 3);
const __PlayerAction: playerAction2 = {player: player, action: "unknown", Date: actionDate};
if (action === "joined") __PlayerAction.action = "connect";
else if (action === "left") __PlayerAction.action = "disconnect";
if (!Seesion.Player[__PlayerAction.player]) Seesion.Player[__PlayerAction.player] = {
action: __PlayerAction.action,
date: actionDate,
history: [{
action: __PlayerAction.action,
date: actionDate
}]
}; else {
Seesion.Player[__PlayerAction.player].action = __PlayerAction.action;
Seesion.Player[__PlayerAction.player].date = actionDate;
Seesion.Player[__PlayerAction.player].history.push({
action: __PlayerAction.action,
date: actionDate
});
}
// Server player event
serverEvents.emit("player", __PlayerAction);
delete __PlayerAction.action;
if (action === "connect") serverEvents.emit("player_connect", __PlayerAction);
else if (action === "disconnect") serverEvents.emit("player_disconnect", __PlayerAction);
else serverEvents.emit("player_unknown", __PlayerAction);
}
});
// Return Session
pocketmineSesions[SessionID] = Seesion;
onExit().catch(() => null).then(() => delete pocketmineSesions[SessionID]);
serverEvents.on("closed", () => delete pocketmineSesions[SessionID]);
return Seesion;
}

View File

@ -1,13 +1,13 @@
import path from "node:path";
import fs from "node:fs";
import events from "events";
import crypto from "crypto";
import crypto from "node:crypto";
import node_cron from "cron";
import * as child_process from "../../childProcess";
import { backupRoot, serverRoot } from "../../pathControl";
import { BdsSession, bdsSessionCommands } from "../../globalType";
import { BdsSession, bdsSessionCommands, serverListen } from '../../globalType';
import { gitBackup, gitBackupOption } from "../../backup/git";
import { createZipBackup } from "../../backup/zip";
import events from "../../lib/customEvents";
const javaSesions: {[key: string]: BdsSession} = {};
export function getSessions() {return javaSesions;}
@ -19,28 +19,12 @@ export async function startServer(): Promise<BdsSession> {
const serverEvents = new events();
const StartDate = new Date();
const ServerProcess = await child_process.execServer({runOn: "host"}, "java", ["-jar", "Server.jar"], {cwd: ServerPath});
const { onExit } = ServerProcess;
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
const uid = crypto.randomUUID();
playerCallbacks[uid] = {callback: callback};
return uid;
};
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
const playersConnections: {[player: string]: {action: "connect"|"disconnect"|"unknown", date: Date, history: Array<{action: "connect"|"disconnect"|"unknown", date: Date}>}} = {};
// Parse ports
onLog.on("all", data => {
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
if (!!portParse) {
ports.push({
port: parseInt(portParse[2]),
version: "IPv4/IPv6"
});
}
});
const { onExit, on: execOn } = ServerProcess;
// Log Server redirect to callbacks events and exit
execOn("out", data => serverEvents.emit("log_stdout", data));
execOn("err", data => serverEvents.emit("log_stderr", data));
execOn("all", data => serverEvents.emit("log", data));
onExit().catch(err => {serverEvents.emit("err", err);return null}).then(code => serverEvents.emit("closed", code));
// Run Command
const serverCommands: bdsSessionCommands = {
@ -104,7 +88,7 @@ export async function startServer(): Promise<BdsSession> {
}
});
CrontimeBackup.start();
onExit().catch(() => null).then(() => CrontimeBackup.stop());
serverEvents.on("closed", () => CrontimeBackup.stop());
return CrontimeBackup;
}
@ -116,39 +100,44 @@ export async function startServer(): Promise<BdsSession> {
ServerProcess.Exec.stdout.pipe(logStream);
ServerProcess.Exec.stderr.pipe(logStream);
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
// Session Object
const Seesion: BdsSession = {
id: SessionID,
startDate: StartDate,
creteBackup: backupCron,
onExit: onExit,
onPlayer: onPlayer,
ports: () => ports,
getPlayer: () => playersConnections,
server: {
on: serverOn,
once: serverOnce
},
ports: [],
Player: {},
seed: undefined,
started: false,
addonManeger: undefined,
log: onLog,
commands: serverCommands,
server: {
on: (act, fn) => serverEvents.on(act, fn),
once: (act, fn) => serverEvents.once(act, fn),
started: false,
startDate: StartDate
}
};
onLog.on("all", lineData => {
serverEvents.on("log", lineData => {
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) {
Seesion.started = true;
serverEvents.emit("started", new Date());
const StartDate = new Date();
Seesion.server.startDate = StartDate;
serverEvents.emit("started", StartDate);
Seesion.server.started = true;
}
});
// Parse ports
serverEvents.on("log", data => {
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
if (!!portParse) {
const portObject: serverListen = {port: parseInt(portParse[2]), version: "IPv4/IPv6"};
serverEvents.emit("port_listen", portObject);
Seesion.ports.push(portObject);
}
});
// Return Session
javaSesions[SessionID] = Seesion;
onExit().catch(() => null).then(() => delete javaSesions[SessionID]);
serverEvents.on("closed", () => delete javaSesions[SessionID]);
return Seesion;
}

View File

@ -1,38 +0,0 @@
import * as server from "./server";
export const name = "startServer";
export const depends = "installServer";
export async function java() {
console.log("Starting java server...");
const session = await server.Start("java");
session.log.on("all", console.log);
await new Promise(res => {
session.server.once("started", (date: Date) => {
console.log("java started at", date);
console.log("Stoping java");
res("");
});
});
return session.commands.stop();
}
export async function pocketmine() {
console.log("Starting pocketmine server...");
const session = await server.Start("pocketmine");
session.log.on("all", console.log);
session.log.on("all", data => {
// [*] PocketMine-MP set-up wizard
if (/set-up\s+wizard/.test(data)) {
console.log("Auto setup wizard");
session.commands.execCommand("eng").execCommand("y").execCommand("y");
}
});
await new Promise(res => {
session.server.once("started", (date: Date) => {
console.log("pocketmine started at", date);
console.log("Stoping pocketmine");
res("")
});
});
return session.commands.stop();
}

View File

@ -1,6 +0,0 @@
FROM ghcr.io/the-bds-maneger/container:latest
WORKDIR /testApp
COPY ./package*.json ./
RUN npm ci
COPY ./ ./
RUN npm run test:host -- --exit-on-error --show-return

View File

@ -1,65 +0,0 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const testDir = path.join(__dirname, ".testDir");
if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true });
fs.mkdirSync(testDir);
/**
* Read dir recursively
*
* @param {string} dir
* @param {Array<RegExp>} filterFile
* @returns {Promise<Array<string>>}
*/
async function readdirRecursive(dir, filterFile) {
if (!fs.existsSync(dir)) throw new Error(`Directory not found: ${dir}`);
if (!(fs.statSync(dir).isDirectory())) return [dir];
if (!filterFile) filterFile = [];
/** @type {Array<string>} */
const files = [];
/** @type {Array<string>} */
const dirs = fs.readdirSync(dir);
for (const d of dirs) {
const dirPath = path.resolve(dir, d);
if (fs.statSync(dirPath).isDirectory()) files.push(...(await readdirRecursive(dirPath, filterFile)));
else {
if (filterFile.length <= 0) files.push(dirPath);
else {
if (filterFile.some(f => f.test(d))) files.push(dirPath);
}
}
}
return files;
}
if (fs.existsSync(path.join(__dirname, "dist"))) fs.rmSync(path.join(__dirname, "dist"), {recursive: true, force: true});
console.log("Building project");
execSync("npm run build:cjs", { stdio: "inherit" });
(async function() {
/** @type {Array<{filePath: string, funcs: {name?: string, depends?: string}}>} */
const Files = await readdirRecursive(path.join(__dirname, "dist/cjs"), [/\.test\.js$/]).then(res => res.map(x => ({filePath: x, funcs: require(x)})));
const Sort = [];
while (Files.length > 0) {
const File = Files.shift();
if (!File.funcs.depends||!File.funcs.name) Sort.push(File);
else {
const depends = File.funcs.depends;
if (Sort.some(f => f.funcs.name === depends)) Sort.push(File);
else Files.push(File);
}
}
// return console.log(Sort);
for (const {filePath: file, funcs: main} of Sort) {
const logFile = path.join(testDir, file.replace(path.join(__dirname, "dist/cjs"), "").replace(/\/|\\/gi, "_").replace(/\.test\.[tj]s$/, "").replace(/^_/, ""), "");
const log = [];
for (const func of Object.keys(main).filter(key => typeof main[key] === "function")) {
const Data = await main[func]();
console.log(`${file}#${func}: %o`, Data);
}
fs.writeFileSync(logFile+".json", JSON.stringify(log, null, 2));
}
})();

49
testProject.ts Normal file
View File

@ -0,0 +1,49 @@
import * as fs from "node:fs/promises";
import * as fsOld from "node:fs";
import * as path from "node:path";
async function readDirAndFilter(dir: string, test: Array<RegExp> = [/.*/]) {
if (!(fsOld.existsSync(dir))) throw new Error(`${dir} does not exist`);
const files = await fs.readdir(dir);
const parseFiles: Array<string> = []
await Promise.all(files.map(async (fd) => {
const stat = await fs.stat(path.join(dir, fd));
if (stat.isDirectory()) return readDirAndFilter(path.join(dir, fd), test).then(res => parseFiles.push(...res)).catch(err => console.error(err));
else if (stat.isFile()) {
const match = test.some(reg => reg.test(fd));
if (match) parseFiles.push(path.join(dir, fd));
}
}));
return parseFiles;
}
async function runTest() {
const mainFind = path.join(process.cwd(), "src");
const testsFiles = await readDirAndFilter(mainFind, [/.*\.test\.ts$/]);
for (const file of testsFiles) {
console.log("************** Start Script: %s **************", file);
const testScript = await import(file) as {[key: string]: () => Promise<void>};
if (!!testScript.default) {
console.log("************** Start Test: %s **************", file);
await testScript.default();
}
for (const key in testScript) {
if (key === "default") continue;
console.log("************** Start Test: %s **************", key);
await testScript[key]();
}
console.log("************** End Script: %s **************", file);
}
}
runTest().then(() => {
console.log("Test passed");
process.exitCode = 0;
}).catch((err: Error) => {
console.error("Test failed");
console.error(err);
process.exitCode = 1;
}).then(() => {
console.log("Exit with code: %d", process.exitCode);
return process.exit();
});