import { join } from "node:path"; import type { RuntimeMode } from "../shared/api"; import type { ResolvedLoggingConfig } from "./checker/types"; import type { Logger } from "./logger"; import type { StartServerOptions } from "./server"; import type { StaticAssets } from "./static"; import { loadConfig, type ResolvedConfig } from "./checker/config-loader"; import { ProbeEngine } from "./checker/engine"; import { ProbeStore } from "./checker/store"; import { createConsoleFallback, createRuntimeLogger } from "./logger"; import { startServer } from "./server"; export interface BootstrapDependencies { createEngine?: ( store: ProbeStore, targets: ResolvedConfig["targets"], maxConcurrentChecks: number, retentionMs: number, logger: Logger, ) => BootstrapEngine; createLogger?: (config: ResolvedLoggingConfig, mode: string, version: string) => Promise; createStore?: (dbPath: string) => ProbeStore; exit?: (code: number) => never; loadConfig?: (configPath: string) => Promise; logError?: (...data: unknown[]) => void; onSignal?: (signal: ShutdownSignal, handler: () => void) => void; startServer?: (options: StartServerOptions) => unknown; } export interface BootstrapOptions { configPath: string; mode: RuntimeMode; staticAssets?: StaticAssets; version: string; } type BootstrapEngine = Pick; type ShutdownSignal = "SIGINT" | "SIGTERM"; export async function bootstrap(options: BootstrapOptions, dependencies: BootstrapDependencies = {}): Promise { const load = dependencies.loadConfig ?? loadConfig; const createStore = dependencies.createStore ?? ((dbPath: string) => new ProbeStore(dbPath)); const createEngine = dependencies.createEngine ?? (( store: ProbeStore, targets: ResolvedConfig["targets"], maxConcurrentChecks: number, retentionMs: number, logger: Logger, ) => new ProbeEngine(store, targets, maxConcurrentChecks, retentionMs, logger)); const buildLogger = dependencies.createLogger ?? createRuntimeLogger; const serve = dependencies.startServer ?? startServer; const onSignal = dependencies.onSignal ?? ((signal: ShutdownSignal, handler: () => void) => { process.on(signal, handler); }); const exit = dependencies.exit ?? ((code: number) => process.exit(code)); const logError = dependencies.logError ?? ((...data: unknown[]) => { createConsoleFallback().fatal( data.map((item) => (item instanceof Error ? item.message : String(item))).join(" "), ); }); let store: ProbeStore | undefined; let engine: BootstrapEngine | undefined; let logger: Logger | undefined; try { const config = await load(options.configPath); try { logger = await buildLogger(config.logging, options.mode, options.version); } catch (logInitError) { logError("日志初始化失败:", logInitError instanceof Error ? logInitError.message : logInitError); exit(1); } logger!.info({ configPath: options.configPath, mode: options.mode, version: options.version }, "配置加载成功"); store = createStore(join(config.dataDir, "probe.db")); store.syncTargets(config.targets); logger!.info({ dataDir: config.dataDir }, "数据库初始化成功"); engine = createEngine( store, config.targets, config.maxConcurrentChecks, config.retentionMs, logger!.child({ component: "engine" }), ); engine.start(); logger!.info( { maxConcurrentChecks: config.maxConcurrentChecks, targetCount: config.targets.length }, "调度引擎启动", ); const shutdown = () => { logger?.info("收到退出信号,开始优雅关机"); engine?.stop(); store?.close(); logger?.flush(); exit(0); }; onSignal("SIGINT", shutdown); onSignal("SIGTERM", shutdown); serve({ config: { host: config.host, port: config.port }, logger: logger!.child({ component: "server" }), mode: options.mode, staticAssets: options.staticAssets, store, version: options.version, }); } catch (error) { engine?.stop(); store?.close(); if (logger) { logger.fatal({ error: error instanceof Error ? error.message : String(error) }, "启动失败"); logger.flush(); } else { logError("启动失败:", error instanceof Error ? error.message : error); } exit(1); } }