import { spawn } from 'child_process' ; import EventsEmitter from 'events' ; import { ChildProcess } from 'node:child_process' ; import get from 'lodash.get' ; import defaultsDeep from 'lodash.defaultsdeep' ; import { Config , DeepPartial } from './Config' ; export interface JavaServerConfig { jar : string ; args : string [ ] ; path : string ; pipeStdout : boolean ; pipeStdin : boolean ; flavorSpecific : { [ flavor ] : { startedRegExp : RegExp ; stoppedRegExp : RegExp ; } ; } ; } declare module './Config' { export interface Config { javaServer : JavaServerConfig ; } } export const DEFAULT_JAVA_SERVER_CONFIG = { jar : 'server.jar' , args : [ '-Xmx1024M' , '-Xms1024M' ] , path : '.' , pipeStdout : true , pipeStdin : true , flavorSpecific : { default : { startedRegExp : /^\[.+?\]: Done/ , stoppedRegExp : /^\[.+?\]: ThreadedAnvilChunkStorage: All dimensions are saved$/ , } , } , } ; export interface JavaServerEvents { console : ( message : string ) => void ; start : ( ) => void ; stop : ( ) => void ; } export declare interface JavaServer { on < U extends keyof JavaServerEvents > ( event , listener ) : this ; emit < U extends keyof JavaServerEvents > ( event , ... args ) : boolean ; } export class JavaServer extends EventsEmitter { public config : Config ; private process ? : ChildProcess ; constructor ( config = { } ) { super ( ) ; this . config = defaultsDeep ( config , { javaServer : DEFAULT_JAVA_SERVER_CONFIG } ) ; this . on ( 'console' , ( message ) => { if ( message . match ( get ( this . config , `javaServer.flavorSpecific.${ this . config . flavor }.startedRegExp` , this . config . javaServer . flavorSpecific . default . startedRegExp ) ) ) { this . emit ( 'start' ) ; } if ( message . match ( get ( this . config , `javaServer.flavorSpecific.${ this . config . flavor }.stoppedRegExp` , this . config . javaServer . flavorSpecific . default . stoppedRegExp ) ) ) { this . emit ( 'stop' ) ; } } ) ; } public start ( ) { if ( this . process ) { throw new Error ( 'JavaServer already running' ) ; } this . process = spawn ( 'java' , [ ... this . config . javaServer . args , '-jar' , this . config . javaServer . jar , 'nogui' ] , { cwd : this . config . javaServer . path , stdio : [ 'pipe' , 'pipe' , 'pipe' ] , } ) ; if ( this . config . javaServer . pipeStdout ) { this . process . stdout ?. pipe ( process . stdout ) ; this . process . stderr ?. pipe ( process . stderr ) ; } if ( this . config . javaServer . pipeStdin ) { process . stdin . pipe ( this . process . stdin ! ) ; } this . process . stdout ?. on ( 'data' , ( chunk ) => { chunk . toString ( ) . split ( '\n' ) . forEach ( message => this . emit ( 'console' , message ) ) ; } ) ; this . process . on ( 'exit' , ( ) => { this . stop ( ) ; } ) ; process . on ( 'exit' , ( ) => { this . stop ( ) ; } ) ; } public stop ( ) { this . process ?. kill ( ) ; this . process = undefined ; } public send ( message ) { if ( ! this . process ) { throw new Error ( 'JavaServer not running' ) ; } this . process . stdin ?. write ( message + '\n' ) ; } }