UNPKG

@tryloop/oats

Version:

🌾 OATS - OpenAPI TypeScript Sync. The missing link between your OpenAPI specs and TypeScript applications. Automatically watch, generate, and sync TypeScript clients from your API definitions.

192 lines • 6.56 kB
import { EventEmitter } from 'events'; import chalk from 'chalk'; import ora from 'ora'; import { Logger } from '../../utils/logger.js'; import { ServiceStartError } from '../../errors/index.js'; import { ShutdownManager } from '../../utils/shutdown-manager.js'; export var ServiceState; (function (ServiceState) { ServiceState["STOPPED"] = "stopped"; ServiceState["STARTING"] = "starting"; ServiceState["RUNNING"] = "running"; ServiceState["STOPPING"] = "stopping"; ServiceState["ERROR"] = "error"; })(ServiceState || (ServiceState = {})); export class BaseService extends EventEmitter { config; logger; processManager; process; state = ServiceState.STOPPED; startTime; error; runtimeConfig; constructor(config, processManager, runtimeConfig) { super(); this.config = config; this.logger = new Logger(config.name); this.processManager = processManager; this.runtimeConfig = runtimeConfig; } /** * Get the current service state */ getState() { return this.state; } /** * Get service info */ getInfo() { return { name: this.config.name, state: this.state, port: this.config.port, startTime: this.startTime, error: this.error, pid: this.process?.pid, }; } /** * Start the service */ async start() { if (this.state !== ServiceState.STOPPED) { this.logger.warn(`Service already ${this.state}, cannot start`); return; } this.setState(ServiceState.STARTING); this.error = undefined; try { if (!this.runtimeConfig?.log?.quiet) { console.log(chalk.blue(`🚀 Starting ${this.config.name} service...`)); } // Port conflict check (if applicable) if (this.config.port) { await this.checkPort(); } // Start the process const { command, args } = this.parseCommand(); this.process = this.processManager.startProcess(command, args, { cwd: this.config.path, env: this.config.env, }); // Set up process event handlers this.setupProcessHandlers(); // Wait for service to be ready await this.waitForReady(); this.startTime = new Date(); this.setState(ServiceState.RUNNING); if (!this.runtimeConfig?.log?.quiet) { ora().succeed(`${this.config.name} service started`); } } catch (error) { this.error = error; this.setState(ServiceState.ERROR); throw new ServiceStartError(this.config.name, error.message, this.process?.exitCode ?? -1); } } /** * Stop the service */ async stop() { if (this.state === ServiceState.STOPPED) { return; } this.setState(ServiceState.STOPPING); if (!this.runtimeConfig?.log?.quiet) { console.log(chalk.yellow(`🛑 Stopping ${this.config.name} service...`)); } if (this.process) { await this.processManager.killProcess(this.process); this.process = undefined; } this.setState(ServiceState.STOPPED); if (!this.runtimeConfig?.log?.quiet) { ora().succeed(`${this.config.name} service stopped`); } } /** * Parse command string into command and args */ parseCommand() { const parts = this.config.command.split(' '); return { command: parts[0] || '', args: parts.slice(1), }; } /** * Set up process event handlers */ setupProcessHandlers() { if (!this.process) return; this.process.stdout?.on('data', (data) => { const output = data.toString().trim(); if (!output) return; this.handleOutput(output); // Show service output based on log level and showServiceOutput setting const logLevel = this.runtimeConfig?.log?.level ?? 'info'; const showServiceOutput = this.runtimeConfig?.log?.showServiceOutput ?? true; // Only show service output in debug mode or if explicitly enabled in info mode // Don't show output during shutdown const shutdownManager = ShutdownManager.getInstance(); if (showServiceOutput && (logLevel === 'debug' || logLevel === 'info') && !shutdownManager.isShutdownInProgress()) { console.log(chalk.gray(`[${this.config.name}] ${output}`)); } }); this.process.stderr?.on('data', (data) => { const output = data.toString(); this.handleError(output); // Show errors based on log level const logLevel = this.runtimeConfig?.log?.level ?? 'info'; const shutdownManager = ShutdownManager.getInstance(); // Always show errors unless log level is set higher than error or during shutdown if (logLevel !== 'none' && !shutdownManager.isShutdownInProgress()) { console.error(chalk.red(`[${this.config.name}] ${output}`)); } }); this.process.on('exit', (code) => { // Don't show exit messages during shutdown if (this.state === ServiceState.STOPPING) { return; } if (this.state === ServiceState.RUNNING) { this.error = new Error(`Process exited unexpectedly with code ${code}`); this.setState(ServiceState.ERROR); } }); } /** * Set the service state and emit event */ setState(state) { const oldState = this.state; this.state = state; this.emit('stateChange', { oldState, newState: state }); } /** * Handle process output (override in subclasses) */ handleOutput(_output) { // Override in subclasses to detect when service is ready } /** * Handle process errors (override in subclasses) */ handleError(_output) { // Override in subclasses for custom error handling } /** * Check if port is available (override if needed) */ async checkPort() { // Override in subclasses } } //# sourceMappingURL=base-service.js.map