import path from "node:path";
import { createServer } from "node:http";
import { existsSync, readdirSync } from "node:fs";
import { DevCompiler } from "./compiler.js";
import { HotSwappableHandler } from "./hot-swappable-handler.js";
import { SoftReloader } from "./soft-reloader.js";
import { reloadModels as reloadModelDefs } from "./model-reloader.js";
import { finalizeConfig, loadRawConfig } from "../config-loader.js";
import { createApp } from "../app.js";
import { applyServerConfig, createNodeServerOptions, } from "../server-config.js";
import { loadI18n } from "../i18n-loader.js";
import { schemaAdapter } from "../schema-adapter.js";
import { loadPlugins } from "../plugin-loader.js";
import { createMonSQLizePlugin, shouldLoadMonSQLize, } from "../plugins/monsqlize/index.js";
import { loadMiddlewares } from "../middleware-loader.js";
import { loadServices } from "../service-loader.js";
import { loadRoutes } from "../router-loader.js";
import { resolveAdapter } from "../adapter-resolver.js";
import { createRequestIdMiddleware } from "../middlewares/request-id.js";
import { createCorsMiddleware } from "../middlewares/cors.js";
import { createBodyParserMiddleware } from "../middlewares/body-parser.js";
import { createRateLimitMiddleware } from "../middlewares/rate-limit.js";
import { responseWrapper } from "../middlewares/response-wrapper.js";
import { createAccessLogMiddleware } from "../middlewares/access-log.js";
import { createErrorHandler } from "../middlewares/error-handler.js";
import { renderDevErrorPage } from "./error-overlay.js";
import { createVextFetch } from "../fetch.js";
import { RouteMetadataCollector } from "../openapi/collector.js";
import { OpenAPIGenerator } from "../openapi/generator.js";
import { registerDocEndpoints } from "../openapi/doc-endpoints.js";
import { printReadyLog } from "../utils/network.js";
import { normalizePortConflictStrategy, resolvePortConflict, } from "../port-conflict.js";
import { requestPortConflictDecisionFromParent, sendLifecycleLevelToParent, } from "../ipc-port-conflict.js";
function getLifecycleLevel(config) {
    const logger = config.logger;
    return logger?.lifecycleLevel === "verbose" ? "verbose" : "concise";
}
// ── 主函数 ──────────────────────────────────────────────────
/**
 * devBootstrap — Dev 模式完整启动编排
 *
 * 在 ColdRestarter fork 的子进程中执行。
 * 完成 esbuild 编译 + 框架初始化 + HTTP 启动 + IPC 就绪通知。
 *
 * 流程：
 *   不可重载阶段：
 *     0. DevCompiler.start() → 全量编译 src/ → .vext/dev/
 *     1. loadConfig(outDir/config) → 从编译产物加载配置
 *     2. createApp(config) → 创建 app + internals
 *     3. loadI18n(outDir/locales) → 加载 i18n 语言包
 *     4. loadPlugins(app, outDir/plugins) → 加载并执行插件 setup()
 *
 *   可重载阶段（首次执行）：
 *     5. loadMiddlewares(outDir/middlewares) → 加载中间件定义
 *     6. loadServices(app, outDir/services) → 加载并注入服务
 *     7. loadRoutes(app, outDir/routes) → 加载路由并注册到 adapter
 *     8. 注册内置中间件 + 错误处理 + 404
 *     9. lockUse() → 锁定 app.use()
 *
 *   Server 启动（Phase 2B — HotSwappableHandler）：
 *     10. adapter.buildHandler() → 获取请求处理函数
 *     10b. new HotSwappableHandler(handler) → 包装为可热替换
 *     11. http.createServer(hotHandler.handle) → 创建 server
 *     12. server.listen() → 开始监听
 *
 *   Soft Reload 初始化（Phase 2B）：
 *     12b. 创建 SoftReloader 实例（注入所有依赖）
 *     12c. 注册 IPC reload 消息监听
 *
 *   就绪通知：
 *     13. IPC 发送 { type: 'ready' }
 *     14. 注册信号处理
 *     15. 执行 onReady 钩子
 *
 * @param options 启动选项
 * @returns 启动后的资源引用
 * @throws 任何启动步骤失败时抛出错误（由 dev-entry.ts 的 catch 处理）
 */
export async function devBootstrap(options) {
    const { projectRoot, skipIpc = false } = options;
    const srcDir = path.join(projectRoot, "src");
    const outDir = options.outDir ?? path.join(projectRoot, ".vext", "dev");
    const tsconfig = options.tsconfig ?? path.join(projectRoot, "tsconfig.json");
    // 资源引用（用于错误边界清理）
    let internals = null;
    let server = null;
    let compiler = null;
    let hotHandler = null;
    let softReloader = null;
    try {
        // ════════════════════════════════════════════════════════
        // 不可重载阶段
        // ════════════════════════════════════════════════════════
        // ── 步骤 0: DevCompiler 首次全量编译 ──────────────────
        //
        // 编译 src/ 下所有 .ts/.js/.mjs/.cjs 文件到 .vext/dev/
        // 输出格式：CJS（require/module.exports），因为 dev 子进程
        // 不带 tsx loader，需要直接 require 编译产物。
        //
        compiler = new DevCompiler({ srcDir, outDir, tsconfig });
        const compileStats = await compiler.start();
        // ── 步骤 1: 加载配置 ─────────────────────────────────
        //
        // 从编译产物的 config/ 子目录加载（不是 src/config/）。
        // dev 子进程不带 tsx loader，无法直接 require TS 文件。
        // compiler.start() 已将 src/config/ 编译为 .vext/dev/config/ 下的 CJS .js
        //
        const rawConfig = await loadRawConfig(path.join(outDir, "config"), {
            rootDir: projectRoot,
            command: "dev",
            env: process.env,
        });
        const resolution = await resolvePortConflict({
            host: rawConfig.host,
            port: rawConfig.port,
            strategy: normalizePortConflictStrategy(process.env.VEXT_PORT_CONFLICT),
            interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
            requestDecision: (request) => requestPortConflictDecisionFromParent(request),
        });
        if (resolution.changed) {
            rawConfig.port = resolution.port;
            process.env.VEXT_PORT = String(resolution.port);
            console.log(`[vext dev] port conflict resolved: using next available port ${resolution.port}`);
        }
        else if (resolution.action === "kill" && resolution.details?.pid) {
            console.log(`[vext dev] port conflict resolved: stopped process ${resolution.details.pid} on ${resolution.port}`);
        }
        const config = finalizeConfig(rawConfig);
        sendLifecycleLevelToParent(getLifecycleLevel(rawConfig));
        // ── 步骤 2: createApp ────────────────────────────────
        const result = createApp(config);
        const app = result.app;
        internals = result.internals;
        // ── 步骤 2a: resolveAdapter（异步按需加载）────────────
        // 动态 import() 按需加载用户选择的 adapter 框架包。
        // 默认 native adapter（零外部依赖），其他 adapter 需用户额外安装对应框架。
        app.adapter = await resolveAdapter(config, app);
        app.logger.info("[vext dev] initializing (soft reload mode)...");
        app.logger.info(`[vext dev] compiled ${compileStats.fileCount} files in ${compileStats.elapsed}ms`);
        // ── 步骤 3: i18n 语言包加载 ──────────────────────────
        //
        // 从编译产物的 locales/ 子目录加载（如果存在）
        //
        // 两种模式自动检测（与生产 bootstrap 保持一致）：
        //   Mode A（平铺文件）：locales/zh-CN.js → loadI18n() 动态 import + 注册
        //   Mode B（子目录）  ：locales/user/zh-CN.js → schema-dsl 递归扫描
        //
        const localesDir = path.join(outDir, "locales");
        if (existsSync(localesDir)) {
            const loadedLocales = await loadI18n(localesDir, app.logger);
            if (loadedLocales.length > 0) {
                // Mode A: 平铺文件加载成功
                app.logger.info(`[vext dev] i18n locales loaded: ${loadedLocales.join(", ")}`);
            }
            else {
                // Mode B fallback: 检查是否存在子目录（如 user/, auth/）
                // 如果有子目录，交给 schema-dsl 的内置递归扫描处理
                try {
                    const entries = readdirSync(localesDir, { withFileTypes: true });
                    const hasSubDirs = entries.some((e) => e.isDirectory());
                    if (hasSubDirs) {
                        schemaAdapter.configure({ i18n: localesDir });
                        const subDirs = entries
                            .filter((e) => e.isDirectory())
                            .map((e) => e.name);
                        app.logger.info(`[vext dev] i18n locales loaded (subdirectory mode): ${subDirs.join(", ")}`);
                    }
                }
                catch (err) {
                    app.logger.warn({ error: err.message }, "[vext dev] Failed to scan locales subdirectories, i18n may not work");
                }
            }
        }
        // ── 步骤 3.5: 内置插件（MonSQLize）条件加载（P0 修复）──
        //
        // 生产 bootstrap.ts 有此步骤，dev-bootstrap 原先缺失，
        // 导致 dev 模式下 app.db / app.monsqlize 不存在。
        //
        // 仅当 config.database 存在时才启用 MonSQLize 内置插件。
        // 在用户插件之前执行，确保用户插件可安全依赖 app.db / app.monsqlize。
        // 无 database 配置则完全跳过，零开销。
        //
        // 注意：dev 模式使用 outDir（编译产物目录）而非 srcDir，
        // 因为 dev 子进程不带 tsx loader，需要从 .vext/dev/models/ 加载 CJS 编译产物。
        //
        const hasMonsqlize = shouldLoadMonSQLize(config);
        if (hasMonsqlize) {
            const monsqlizePlugin = createMonSQLizePlugin(outDir);
            app.logger.debug("[vext dev] built-in plugin: monsqlize (database config detected)");
            await monsqlizePlugin.setup(app);
            app.logger.info("[vext dev] built-in plugin: monsqlize loaded");
        }
        // ── 步骤 4: 加载插件 ─────────────────────────────────
        //
        // 从编译产物的 plugins/ 子目录加载（如果存在）
        // 插件在 setup() 阶段注册钩子和中间件，属于不可重载阶段
        //
        const pluginsDir = path.join(outDir, "plugins");
        if (existsSync(pluginsDir)) {
            await loadPlugins(app, pluginsDir);
        }
        // ════════════════════════════════════════════════════════
        // 可重载阶段（首次执行）
        // ════════════════════════════════════════════════════════
        // ── fetchConfig 提前提取（步骤 8 的 requestId 中间件需要用到）──
        // 必须在步骤 5 之前定义，因为步骤 8 注册 requestId 中间件时
        // 需要将 propagateHeaders 传入，而 fetchConfig 原本在步骤 8+ 才读取。
        const fetchConfig = config.fetch;
        // ── 步骤 4+: 挂载 app.fetch（必须在 loadRoutes 之前）────
        //
        // 路由工厂执行时会复制 app 当前属性到 collector 代理对象。
        // 若 app.fetch 在 loadRoutes 之后才赋值，路由闭包会捕获未初始化占位对象。
        const requestIdHeader = config.requestId?.header ?? "x-request-id";
        app.fetch = createVextFetch(app.logger, fetchConfig ?? {}, requestIdHeader);
        // ── 步骤 5: 加载中间件定义 ───────────────────────────
        const middlewareRegistry = await loadMiddlewares(path.join(outDir, "middlewares"), config.middlewares ?? [], app.logger, config.logger?.lifecycleLevel ?? "concise");
        // ── 步骤 6: 加载服务 ─────────────────────────────────
        await loadServices(app, path.join(outDir, "services"));
        // ── 步骤 7: 加载路由 ─────────────────────────────────
        //
        // 🆕 OpenAPI 集成：若 openapi.enabled，创建 collector 传入 loadRoutes，
        // 在每条路由注册到 adapter 时同步收集元信息（method / path / options / sourceFile）。
        const openapiConfig = config.openapi;
        const openapiEnabled = openapiConfig?.enabled ?? process.env.NODE_ENV !== "production";
        const collector = openapiEnabled ? new RouteMetadataCollector() : null;
        await loadRoutes(app, path.join(outDir, "routes"), {
            middlewareDefs: middlewareRegistry,
            globalMiddlewares: internals.getGlobalMiddlewares(),
        }, collector);
        // ── 步骤 7+: OpenAPI 文档生成 ────────────────────────
        //
        // 在所有路由注册完成后、步骤 8（内置中间件注册）之前生成 OpenAPI 文档。
        // 使用 collector 收集到的路由元信息 + config.openapi 配置，
        // 生成 OpenAPI 3.0 spec 并注册 /docs + /openapi.json 端点。
        //
        if (openapiEnabled && collector) {
            const generator = new OpenAPIGenerator({
                title: openapiConfig?.title,
                description: openapiConfig?.description,
                version: openapiConfig?.version,
                servers: openapiConfig?.servers,
                tags: openapiConfig?.tags,
                securitySchemes: openapiConfig
                    ?.securitySchemes,
                guardSecurityMap: openapiConfig
                    ?.guardSecurityMap,
                contact: openapiConfig?.contact,
                license: openapiConfig?.license,
            });
            const spec = generator.generate(collector.getRoutes());
            registerDocEndpoints(app, spec, {
                specPath: openapiConfig?.jsonPath ?? "/openapi.json",
                specPublicPath: openapiConfig
                    ?.jsonPublicPath,
                docsPath: openapiConfig?.docsPath ?? "/docs",
                title: openapiConfig?.title,
                scalar: openapiConfig?.scalar,
            });
            app.logger.info(`[openapi] ${collector.getCount()} route(s) documented`);
        }
        // ── 步骤 8: 注册内置中间件 ───────────────────────────
        //
        // 与生产 bootstrap 保持一致的中间件注册顺序和条件守卫：
        //   requestId → cors → body-parser → rate-limit → response-wrapper
        //   → access-log → 插件全局中间件 → 错误处理 → 404
        //
        // 🔧 D2/D3 修复：每个中间件仅在 enabled !== false 时注册（与生产 bootstrap.ts 对齐）。
        // 禁用的中间件完全不进入中间件链，实现真正的零开销。
        // D3 修复：createRequestIdMiddleware 补传第四参数 localeConfig，
        // 确保 dev 模式下 store.locale 正确写入（i18n 语言解析生效）。
        //
        // 1. requestId（config.requestId.enabled，默认 true）
        if (config.requestId?.enabled !== false) {
            const localeConfig = config.locale;
            const requestIdMiddleware = createRequestIdMiddleware(config.requestId, () => internals.getRequestIdGenerator(), (fetchConfig?.propagateHeaders ?? []), localeConfig);
            app.adapter.registerMiddleware(requestIdMiddleware);
        }
        // 2. cors（config.cors.enabled，默认 true）
        if (config.cors?.enabled !== false) {
            const corsMiddleware = createCorsMiddleware(config.cors);
            app.adapter.registerMiddleware(corsMiddleware);
        }
        // 3. body-parser（config.bodyParser.enabled，默认 true）
        if (config.bodyParser?.enabled !== false) {
            const bodyParserMiddleware = createBodyParserMiddleware(config.bodyParser, config.multipart);
            app.adapter.registerMiddleware(bodyParserMiddleware);
        }
        // 4. rate-limit（config.rateLimit.enabled，默认 true）
        if (config.rateLimit?.enabled !== false) {
            const rateLimitMiddleware = createRateLimitMiddleware(config.rateLimit, () => internals.getRateLimiter());
            app.adapter.registerMiddleware(rateLimitMiddleware);
        }
        // 5. response-wrapper（config.response.wrap，默认 true）
        if (config.response?.wrap !== false) {
            app.adapter.registerMiddleware(responseWrapper);
        }
        // 6. access-log（config.accessLog.enabled，默认 true）
        //    洋葱模型 after-middleware：before 记录开始时间，after 记录耗时+状态码
        if (config.accessLog?.enabled !== false) {
            const accessLogMiddleware = createAccessLogMiddleware(config.accessLog ?? {}, app.logger);
            app.adapter.registerMiddleware(accessLogMiddleware);
        }
        // 注册插件全局中间件
        for (const mw of internals.getGlobalMiddlewares()) {
            app.adapter.registerMiddleware(mw);
        }
        // 错误处理 + 404
        // 🆕 Dev 错误覆盖层：读取 config.dev.errorOverlay 配置，enabled !== false 时注入
        const devOverlayConfig = config.dev;
        const overlayEnabled = devOverlayConfig?.errorOverlay?.enabled !== false;
        const overlayOptions = devOverlayConfig?.errorOverlay
            ? {
                theme: devOverlayConfig.errorOverlay.theme,
                maxFrames: devOverlayConfig.errorOverlay.maxFrames,
            }
            : undefined;
        const overlayFn = overlayEnabled
            ? (err) => renderDevErrorPage(err, projectRoot, overlayOptions)
            : undefined;
        const errorHandler = createErrorHandler(config.response ?? {}, overlayFn, app.logger);
        app.adapter.registerErrorHandler(errorHandler);
        app.adapter.registerNotFound(createNotFoundHandler());
        // ── 步骤 9: 锁定 app.use() ──────────────────────────
        internals.lockUse();
        // ════════════════════════════════════════════════════════
        // Server 启动
        // ════════════════════════════════════════════════════════
        // ── 步骤 10: 构建请求处理函数 ────────────────────────
        //
        // Phase 2B（Soft Reload 模式）：
        //   使用 HotSwappableHandler 包装 handler，
        //   Soft Reload 时只需 hotHandler.swap(newHandler) 即可原子替换。
        //
        //   Server socket 绑定的是 hotHandler.handle（间接引用），
        //   soft reload 时只替换 currentHandler 引用，不影响 socket。
        //
        const handler = app.adapter.buildHandler();
        hotHandler = new HotSwappableHandler(handler);
        // ── 步骤 11: 创建 HTTP server ───────────────────────
        //
        // 关键决策：dev 模式下不通过 adapter.listen() 创建 server，
        // 而是框架直接用 http.createServer() 创建。
        //
        // 原因：
        //   1. Server socket 由框架控制，soft reload 时不受影响
        //   2. Adapter 可自由重建（每次 soft reload 创建新实例）
        //   3. 请求处理函数通过 HotSwappableHandler 可原子替换
        //
        server = createServer(createNodeServerOptions(config.server), hotHandler.handle);
        applyServerConfig(server, config.server);
        // ── 步骤 12: 开始监听 ────────────────────────────────
        const port = config.port ?? 3000;
        const host = config.host ?? "0.0.0.0";
        const serverHandle = await new Promise((resolve, reject) => {
            server.once("error", reject);
            server.listen(port, host, () => {
                server.removeListener("error", reject);
                resolve({
                    port,
                    host,
                    async close() {
                        return new Promise((res, rej) => {
                            server.close((err) => {
                                if (err)
                                    rej(err);
                                else
                                    res();
                            });
                        });
                    },
                });
            });
        });
        // ════════════════════════════════════════════════════════
        // 就绪通知 & 信号注册
        // ════════════════════════════════════════════════════════
        // ── 步骤 12b: 创建 SoftReloader ─────────────────────
        //
        // SoftReloader 封装完整的 Soft Reload 流程，包含：
        //   编译 → 清缓存 → i18n → 中间件 → 服务 → 路由 → swap → 内存监控
        //
        // 所有依赖通过构造选项注入，SoftReloader 不直接 import 任何框架模块。
        //
        // 内置中间件创建器 — 供 route-reloader 在每次 soft reload 时
        // 在新 adapter 上重新注册所有内置中间件。
        //
        // 使用 as any 是因为 VextMiddleware 和 RouteReloaderMiddleware
        // 的签名形式不同（VextMiddleware 使用 req/res/next 三参数，
        // RouteReloaderMiddleware 使用 ctx/next 两参数），
        // 但运行时实际传递的函数是同一个对象，adapter 内部做转换。
        //
        // ── 步骤 12b-pre: 构造 reloadModels 闭包 ────────────
        //
        // 仅当 monSQLize 插件已加载时，构造 model 重载闭包注入 SoftReloader。
        // 闭包捕获 app 和 outDir，SoftReloader 在 Step 4.5 调用。
        //
        const reloadModelsClosure = hasMonsqlize
            ? (invalidated) => reloadModelDefs(app, outDir, invalidated)
            : undefined;
        // 🔧 D2/D3 修复（soft reload 侧）：
        // - 每个 creator 仅在对应 enabled 条件满足时注入（undefined 时 route-reloader 自动跳过）
        // - createRequestIdMiddleware 补传 cfg.locale（D3 修复），确保热重载后 store.locale 仍写入
        const builtinMwCreators = {
            createRequestIdMiddleware: config.requestId?.enabled !== false
                ? ((cfg) => createRequestIdMiddleware(cfg.requestId, () => internals.getRequestIdGenerator(), (fetchConfig?.propagateHeaders ?? []), cfg.locale))
                : undefined,
            createCorsMiddleware: config.cors?.enabled !== false
                ? ((cfg) => createCorsMiddleware(cfg.cors))
                : undefined,
            createBodyParserMiddleware: config.bodyParser?.enabled !== false
                ? ((cfg) => createBodyParserMiddleware(cfg.bodyParser, cfg.multipart))
                : undefined,
            createRateLimitMiddleware: config.rateLimit?.enabled !== false
                ? ((cfg) => createRateLimitMiddleware(cfg.rateLimit, () => internals.getRateLimiter()))
                : undefined,
            responseWrapper: config.response?.wrap !== false ? responseWrapper : undefined,
            createAccessLogMiddleware: config.accessLog?.enabled !== false
                ? ((cfg) => createAccessLogMiddleware((cfg.accessLog ?? {}), app.logger))
                : undefined,
        };
        softReloader = new SoftReloader({
            compiler: compiler,
            hotHandler: hotHandler,
            app: app,
            config: config,
            logger: app.logger,
            resolveAdapter: resolveAdapter,
            loadRoutes: loadRoutes,
            loadMiddlewares: loadMiddlewares,
            createErrorHandler: ((cfg) => createErrorHandler(cfg.response ?? {}, 
            // 🆕 soft reload 后重建的错误处理器同样包含 overlay 注入
            overlayEnabled
                ? (err) => renderDevErrorPage(err, projectRoot, overlayOptions)
                : undefined, app.logger)),
            createNotFoundHandler: createNotFoundHandler,
            builtinMiddlewares: builtinMwCreators,
            getGlobalMiddlewares: () => internals.getGlobalMiddlewares(),
            // 🆕 monSQLize 热重载：传递 reloadModels 闭包（仅当 monsqlize 已加载）
            reloadModels: reloadModelsClosure,
            // 🆕 BUG-022 修复：传递 openapiConfig 使热重载后自动重新注册 /docs + /openapi.json
            openapiConfig: openapiEnabled
                ? openapiConfig
                : undefined,
        });
        // ── 步骤 12c-pre: 定义 handleShutdown（提前到 IPC 监听之前）──
        //
        // handleShutdown 需要在 IPC message 监听器中引用，
        // 因此必须在注册 process.on('message') 之前定义（避免 const TDZ 错误）。
        //
        const handleShutdown = async () => {
            app.logger.info("[vext dev] worker shutting down...");
            try {
                // 停止接受新请求
                await serverHandle.close();
            }
            catch {
                // 静默忽略 server 关闭错误
            }
            try {
                // 执行 onClose hooks（DB 断开、缓存清理等）
                await internals.shutdown();
            }
            catch {
                // 静默忽略 shutdown 错误
            }
            try {
                // 释放 esbuild 资源
                await compiler.dispose();
            }
            catch {
                // 静默忽略 compiler dispose 错误
            }
        };
        // ── 步骤 12c: 注册 IPC 消息监听（reload + shutdown）──
        //
        // 主进程（cli/dev.ts）在 soft 类型的文件变更时，
        // 通过 restarter.sendToChild({ type: 'reload', files: [...] })
        // 发送 IPC 消息到本子进程。
        //
        // 本监听器接收消息后调用 softReloader.reload()
        // 执行完整的 Soft Reload 流程。
        //
        // 🐛 修复 BUG-014：同时监听 { type: 'shutdown' } 消息，
        // Windows 上 ColdRestarter 通过此 IPC 消息触发优雅关闭。
        //
        process.on("message", (msg) => {
            if (typeof msg !== "object" || msg === null)
                return;
            const msgType = msg.type;
            if (msgType === "reload") {
                const files = msg
                    .files;
                if (Array.isArray(files) && files.length > 0) {
                    softReloader.reload(files).catch((err) => {
                        const error = err instanceof Error ? err : new Error(String(err));
                        app.logger.error("[hot-reload] unexpected error in reload:", error.message);
                        if (error.stack) {
                            app.logger.error(error.stack);
                        }
                    });
                }
            }
            else if (msgType === "shutdown") {
                handleShutdown().finally(() => {
                    process.exit(0);
                });
            }
        });
        // ── 步骤 13: IPC 就绪通知 ────────────────────────────
        //
        // 通知主进程（ColdRestarter）子进程已完成初始化，
        // 可以开始接受请求了。
        //
        if (!skipIpc && process.send) {
            process.send({ type: "ready" });
        }
        // ── 步骤 14: 信号处理 ────────────────────────────────
        //
        // Dev 子进程的信号处理：
        //   - SIGTERM：由 ColdRestarter.safeKill 发送 → 触发优雅关闭
        //   - SIGINT：一般不会直接收到（主进程拦截了），但作为保险注册
        //   - IPC { type: 'shutdown' }：Windows 上由 ColdRestarter 发送（BUG-014 修复）
        //     已在步骤 12c 的 process.on('message') 中处理
        //
        // 不使用 setupShutdown（因为 dev 子进程的退出由 ColdRestarter 控制，
        // 不需要 process.exit），直接注册信号处理。
        //
        // handleShutdown 已在步骤 12c-pre 中定义（供 IPC + 信号共用）。
        //
        process.once("SIGTERM", () => {
            handleShutdown().finally(() => {
                process.exit(0);
            });
        });
        process.once("SIGINT", () => {
            handleShutdown().finally(() => {
                process.exit(0);
            });
        });
        // ── 步骤 15: 执行 onReady 钩子 ──────────────────────
        await internals.runReady();
        printReadyLog(app.logger, serverHandle.host, serverHandle.port, {
            prefix: "[vext dev]",
            suffix: "(soft reload enabled)",
        });
        return {
            app,
            server,
            serverHandle,
            internals,
            compiler,
            compileStats,
            hotHandler: hotHandler,
            softReloader: softReloader,
        };
    }
    catch (err) {
        // ── 错误边界：清理已分配的资源 ─────────────────────────
        //
        // 启动过程中任何步骤失败时的清理逻辑。
        // 确保不会泄漏端口、文件句柄、esbuild 进程等资源。
        //
        if (server) {
            try {
                server.close();
            }
            catch {
                // 静默忽略
            }
        }
        if (internals) {
            try {
                await internals.shutdown(undefined, { skipExit: true });
            }
            catch {
                // 静默忽略
            }
        }
        if (compiler) {
            try {
                await compiler.dispose();
            }
            catch {
                // 静默忽略
            }
        }
        // 重新抛出，由 dev-entry.ts 的 catch 处理
        throw err;
    }
}
// ── 辅助函数 ────────────────────────────────────────────────
/**
 * 创建 404 兜底处理函数
 *
 * 与生产 bootstrap 保持一致的 404 响应格式。
 */
function createNotFoundHandler() {
    return async (req, res, _next) => {
        res.rawJson({
            code: 404,
            message: "Not Found",
            requestId: req.requestId,
        }, 404);
    };
}
