Renamed command manager to console manager, improved log reading

This commit is contained in:
2024-12-28 21:24:59 +01:00
parent 0b99d4098e
commit 4f1b9cf024
8 changed files with 236 additions and 114 deletions

View File

@ -0,0 +1,59 @@
import * as fs from 'fs'
import * as path from 'path'
import Logger, { LoggerType } from '#application/logger'
import { getAppPath } from '#application/storage'
import { Command } from '#application/types'
export class CommandRegistry {
private readonly commands: Map<string, Command> = new Map()
private readonly logger = Logger.type(LoggerType.COMMAND)
public getCommand(name: string): Command | undefined {
return this.commands.get(name)
}
public async loadCommands(): Promise<void> {
const directory = getAppPath('commands')
this.logger.info(`Loading commands from: ${directory}`)
try {
const files = await fs.promises.readdir(directory, { withFileTypes: true })
await Promise.all(
files
.filter(file => this.isValidCommandFile(file))
.map(file => this.loadCommandFile(file))
)
} catch (error) {
this.logger.error(`Failed to read commands directory: ${error instanceof Error ? error.message : String(error)}`)
}
}
private isValidCommandFile(file: fs.Dirent): boolean {
return file.isFile() && (file.name.endsWith('.ts') || file.name.endsWith('.js'))
}
private async loadCommandFile(file: fs.Dirent): Promise<void> {
const fullPath = getAppPath('commands', file.name)
const commandName = path.basename(file.name, path.extname(file.name))
try {
const module = await import(fullPath)
if (typeof module.default !== 'function') {
this.logger.warn(`Unrecognized export in ${file.name}`)
return
}
this.registerCommand(commandName, module.default)
} catch (error) {
this.logger.error(`Error loading command ${file.name}: ${error instanceof Error ? error.message : String(error)}`)
}
}
private registerCommand(name: string, CommandClass: Command): void {
if (this.commands.has(name)) {
this.logger.warn(`Command '${name}' is already registered. Overwriting...`)
}
this.commands.set(name, CommandClass)
this.logger.info(`Registered command: ${name}`)
}
}

View File

@ -0,0 +1,33 @@
import * as readline from 'readline'
export class ConsolePrompt {
private readonly rl: readline.Interface
private isClosed: boolean = false
constructor(private readonly commandHandler: (command: string) => void) {
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
this.rl.on('close', () => {
this.isClosed = true
})
}
public start(): void {
if (this.isClosed) return
this.promptCommand()
}
public close(): void {
this.rl.close()
}
private promptCommand(): void {
this.rl.question('> ', (command: string) => {
this.commandHandler(command)
this.promptCommand()
})
}
}

View File

@ -0,0 +1,76 @@
import * as fs from 'fs'
import * as path from 'path'
import Logger, { LoggerType } from '#application/logger'
export class LogReader {
private logger = Logger.type(LoggerType.CONSOLE)
private watchers: fs.FSWatcher[] = []
private readonly logsDirectory: string
constructor(rootPath: string) {
this.logsDirectory = path.join(rootPath, 'logs')
}
public start(): void {
this.logger.info('Starting log reader...')
this.watchLogs()
}
public stop(): void {
this.watchers.forEach(watcher => watcher.close())
this.watchers = []
}
private watchLogs(): void {
// Watch directory for new files
const directoryWatcher = fs.watch(this.logsDirectory, (_, filename) => {
if (filename?.endsWith('.log')) {
this.watchLogFile(filename)
}
})
this.watchers.push(directoryWatcher)
// Watch existing files
try {
fs.readdirSync(this.logsDirectory)
.filter(file => file.endsWith('.log'))
.forEach(file => this.watchLogFile(file))
} catch (error) {
this.logger.error(`Error reading logs directory: ${error}`)
}
}
private watchLogFile(filename: string): void {
const filePath = path.join(this.logsDirectory, filename)
let currentPosition = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0
const watcher = fs.watch(filePath, () => {
try {
const stat = fs.statSync(filePath)
const newPosition = stat.size
if (newPosition < currentPosition) {
currentPosition = 0
}
if (newPosition > currentPosition) {
const stream = fs.createReadStream(filePath, {
start: currentPosition,
end: newPosition
})
stream.on('data', (data) => {
process.stdout.write('\r' + `[${filename}]\n${data}`)
process.stdout.write('\n> ')
})
currentPosition = newPosition
}
} catch {
watcher.close()
}
})
this.watchers.push(watcher)
}
}

View File

@ -8,7 +8,8 @@ export enum LoggerType {
QUEUE = 'queue',
COMMAND = 'command',
REPOSITORY = 'repository',
ENTITY = 'entity'
ENTITY = 'entity',
CONSOLE = 'console'
}
class Logger {

View File

@ -1,4 +1,4 @@
import { Socket } from 'socket.io'
import { Server, Socket } from 'socket.io'
import { Character } from '#entities/character'
import { ZoneEventTile } from '#entities/zoneEventTile'
@ -51,6 +51,12 @@ export type WorldSettings = {
fogDensity: number
}
export interface Command {
new (io: Server): {
execute(args: string[]): Promise<void>
}
}
// export type TCharacter = Socket & {
// user?: User
// character?: Character