oak-cli/src/server/watch.ts

1005 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import chokidar from "chokidar";
import ts, { CompilerOptions, ProjectReference } from "typescript";
import pathLib from "path";
import dayjs from "dayjs";
import fs from "fs";
import { cloneDeep } from "lodash";
import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore";
import { LogFormatter, LogFormatterProp, polyfillConsole } from "./polyfill";
/*
* 工作流程
文件变更检测 → 生成文件变更事件
事件处理 → 创建编译任务并加入队列
批量编译 → 处理队列中的所有任务
编译完成 → 触发服务器重启事件
服务器重启 → 清理缓存并重新启动服务
*/
declare const require: NodeJS.Require;
declare const process: NodeJS.Process;
export type WatchConfig = {
autoUpdateI18n?: boolean;
/**
* 是否启用polyfill
*/
polyfill?: {
/**
* 是否启用console polyfill
*/
console?: {
/**
* 是否启用
*/
enable?: boolean;
/**
* 是否打印调用堆栈信息
*/
trace?: boolean;
/**
* 格式化函数
*/
formatter: LogFormatter;
};
};
/**
* 生命周期钩子
*/
lifecycle?: {
/**
* 初始化时调用
* @returns void
*/
onInit?: (config: RealWatchConfig) => void;
/**
* 服务启动时调用
* @returns void
*/
onServerStart?: (config: RealWatchConfig) => void;
/**
* 编译前调用
* @returns void
*/
onBeforeCompile?: (config: RealWatchConfig) => void;
/**
* 编译后调用
* @returns void
*/
onAfterCompile?: (config: RealWatchConfig) => void;
/**
* 服务关闭时调用
* @returns void
*/
onServerShutdown?: (config: RealWatchConfig) => void;
/**
* 销毁监视器时调用
* @returns void
*/
onDispose?: (config: RealWatchConfig) => void;
};
};
// 文件变更类型
export type FileChangeType = "add" | "remove" | "change";
// 文件变更事件
export type FileChangeEvent = {
path: string;
type: FileChangeType;
timestamp: number;
};
// 编译任务
export type CompileTask = {
id: string;
filePath: string;
changeType: FileChangeType;
timestamp: number;
};
// 编译结果
export type CompileResult = {
taskId: string;
success: boolean;
filePath: string;
error?: string;
};
// 事件类型
export type EventType =
| "file-changed"
| "compile-task-added"
| "compile-task-completed"
| "compile-batch-started"
| "compile-batch-completed"
| "server-restart-needed"
| "server-restarted";
// 事件处理器
export type EventHandler<T = any> = (data: T) => void | Promise<void>;
// 简单的事件系统
export type EventEmitter = {
on: <T>(event: EventType, handler: EventHandler<T>) => void;
emit: <T>(event: EventType, data: T) => Promise<void>;
off: <T>(event: EventType, handler: EventHandler<T>) => void;
};
// 创建事件发射器
const createEventEmitter = (): EventEmitter => {
const listeners = new Map<EventType, Set<EventHandler>>();
return {
on: <T>(event: EventType, handler: EventHandler<T>) => {
if (!listeners.has(event)) {
listeners.set(event, new Set());
}
listeners.get(event)!.add(handler);
},
emit: async <T>(event: EventType, data: T) => {
const handlers = listeners.get(event);
if (handlers) {
const promises = Array.from(handlers).map(handler =>
Promise.resolve(handler(data))
);
await Promise.all(promises);
}
},
off: <T>(event: EventType, handler: EventHandler<T>) => {
const handlers = listeners.get(event);
if (handlers) {
handlers.delete(handler);
}
}
};
};
// 真实config不会出现,所以这里新增一个type表示真实的config
type DeepRequiredIfPresent<T> = {
[P in keyof T]-?: NonNullable<T[P]> extends (...args: any) => any // Check for function type
? T[P] // Preserve the original function type
: NonNullable<T[P]> extends (infer U)[]
? DeepRequiredIfPresent<U>[]
: NonNullable<T[P]> extends object
? DeepRequiredIfPresent<NonNullable<T[P]>>
: T[P] extends undefined | null
? T[P]
: NonNullable<T[P]>;
};
// 创建编译任务队列
const createCompileQueue = (eventEmitter: EventEmitter) => {
const queue: CompileTask[] = [];
let isProcessing = false;
let processingCount = 0;
let taskProcessor: (task: CompileTask) => Promise<CompileResult> = async (task) => ({
taskId: task.id,
success: true,
filePath: task.filePath
});
const addTask = (task: CompileTask) => {
// 检查是否有相同文件的任务已存在,如果有则更新时间戳
const existingIndex = queue.findIndex(t => t.filePath === task.filePath);
if (existingIndex !== -1) {
queue[existingIndex] = { ...queue[existingIndex], ...task };
} else {
queue.push(task);
}
eventEmitter.emit("compile-task-added", task);
processQueue();
};
const processQueue = async () => {
if (isProcessing || queue.length === 0) {
return;
}
isProcessing = true;
processingCount = queue.length;
await eventEmitter.emit("compile-batch-started", { count: processingCount });
const tasksToProcess = [...queue];
queue.length = 0; // 清空队列
const results: CompileResult[] = [];
for (const task of tasksToProcess) {
try {
const result = await taskProcessor(task);
results.push(result);
await eventEmitter.emit("compile-task-completed", result);
} catch (error) {
const result: CompileResult = {
taskId: task.id,
success: false,
filePath: task.filePath,
error: error instanceof Error ? error.message : String(error)
};
results.push(result);
await eventEmitter.emit("compile-task-completed", result);
}
}
isProcessing = false;
processingCount = 0;
await eventEmitter.emit("compile-batch-completed", { results });
// 检查是否需要重启服务器
const hasSuccessfulCompiles = results.some(r => r.success);
if (hasSuccessfulCompiles) {
await eventEmitter.emit("server-restart-needed", { results });
}
// 如果在处理过程中又有新任务加入,继续处理
if (queue.length > 0) {
processQueue();
}
};
return {
addTask,
getQueueLength: () => queue.length,
isProcessing: () => isProcessing,
getProcessingCount: () => processingCount,
setTaskProcessor: (processor: (task: CompileTask) => Promise<CompileResult>) => {
taskProcessor = processor;
}
};
};
export type RealWatchConfig = DeepRequiredIfPresent<WatchConfig>;
type AliasConfig = Record<string, string | string[]>;
type ModuleType = import('module');
const defaultConfig: RealWatchConfig = {
autoUpdateI18n: true,
polyfill: {
console: {
enable: true,
trace: true,
formatter: ({ level, caller, args }: LogFormatterProp) => {
const getFileInfo = () => {
if (!caller) {
return "";
}
const fileInfo = caller
? `${caller.getFileName()}:${caller.getLineNumber()}`
: "";
return fileInfo.trim();
};
const getTime = () => dayjs().format("YYYY-MM-DD HH:mm:ss.SSS");
const infoStart = "\x1B[36m[ Info";
const warnStart = "\x1B[33m[ Warn";
const errorStart = "\x1B[31m[ Error";
const clearColor = caller ? "]\x1B[0m\n" : "]\x1B[0m";
const levelStart =
level === "info"
? infoStart
: level === "warn"
? warnStart
: errorStart;
return [
levelStart,
getTime(),
getFileInfo(),
clearColor,
...args,
];
},
},
},
lifecycle: {
onInit: (config: RealWatchConfig) => {
console.log("----> Watcher initialized.");
},
onServerStart: (config: RealWatchConfig) => {
console.log("----> Server started.");
},
onBeforeCompile: (config: RealWatchConfig) => {
console.log("----> Compiling......");
},
onAfterCompile: (config: RealWatchConfig) => {
console.log("----> Compile completed.");
},
onServerShutdown: (config: RealWatchConfig) => {
console.log("----> Server shutdown.");
},
onDispose: (config: RealWatchConfig) => {
console.log("----> Watcher disposed.");
},
},
};
const getOverrideConfig = (config?: WatchConfig): RealWatchConfig => {
// 遍历default里面的每一个key如果config里面有就覆盖没有就用default的
const overrideInner = (obj: any, dobj: any): any => {
const result: any = {};
Object.keys(dobj).forEach((key) => {
const value = dobj[key];
if (typeof value === "object") {
const v = obj[key];
if (v === undefined) {
result[key] = value;
} else {
result[key] = overrideInner(v, value);
}
} else {
if (obj && obj[key] !== undefined) {
result[key] = obj[key];
} else {
result[key] = value;
}
}
});
return result;
};
return config
? (overrideInner(config, defaultConfig) as RealWatchConfig)
: defaultConfig;
};
// 当前运行目录
const pwd = process.cwd();
// 创建服务器管理器
const createServerManager = (
projectPath: string,
eventEmitter: EventEmitter,
config: RealWatchConfig
) => {
let shutdown: (() => Promise<void>) | undefined;
let isRestarting = false;
const restart = async () => {
if (isRestarting) {
return;
}
isRestarting = true;
if (shutdown) {
console.log("----> Shutting down service......");
await shutdown().then(() => config.lifecycle.onServerShutdown(config));
shutdown = undefined;
}
console.warn("----> Clearing require cache of project......");
let deleteCount = 0;
// 清空lib以下目录的缓存
Object.keys(require.cache).forEach((key) => {
// 如果不是项目目录下的文件,不删除
if (!key.startsWith(projectPath)) {
return;
} else if (
key.includes("lib") &&
!key.includes("node_modules")
) {
delete require.cache[key];
deleteCount++;
}
});
console.warn(
`----> ${deleteCount} modules has been removed from require.cache.`
);
// eslint-disable-next-line @typescript-eslint/no-var-requires
const simpleConnector = require(pathLib.join(
projectPath,
"lib/config/connector"
)).default;
console.warn("----> Starting service......");
// 这里注意要在require之前因为require会触发编译
const { startup } = require('./start') as {
startup: (pwd: string, connector: any) => Promise<() => Promise<void>>;
}
shutdown = await startup(pwd, simpleConnector).then((shutdown) => {
config.lifecycle.onServerStart(config);
return shutdown;
});
isRestarting = false;
await eventEmitter.emit("server-restarted", {});
};
const dispose = async () => {
if (shutdown) {
await shutdown();
}
};
return {
restart,
dispose,
isRestarting: () => isRestarting
};
};
// 创建编译器
const createCompiler = (
projectPath: string,
options: CompilerOptions,
projectReferences: readonly ProjectReference[] | undefined,
aliasConfig: Record<string, string | string[]>,
config: RealWatchConfig
) => {
const createProgramAndSourceFile = (path: string) => {
const program = ts.createProgram({
rootNames: [path],
options,
projectReferences,
});
const sourceFile = program.getSourceFile(path);
// 是否有语法错误
const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
if (diagnostics.length) {
const syntaxErrors = diagnostics.filter(
(diagnostic) =>
diagnostic.category === ts.DiagnosticCategory.Error
);
if (syntaxErrors.length) {
console.error(`Error in ${path}`);
syntaxErrors.forEach((diagnostic) => {
console.error(
`${ts.flattenDiagnosticMessageText(
diagnostic.messageText,
"\n"
)}`
);
});
console.error(`文件存在语法错误,请检查修复后重试!`);
return { program, sourceFile, diagnostics };
}
}
return { program, sourceFile, diagnostics };
};
const compileTask = async (task: CompileTask): Promise<CompileResult> => {
const { filePath, changeType } = task;
// 判断文件类型
if (!filePath.endsWith(".ts")) {
// 处理非TypeScript文件 (如JSON文件)
if (filePath.endsWith(".json")) {
const targetPath = filePath.replace(
pathLib.join(projectPath, "src"),
pathLib.join(projectPath, "lib")
);
try {
if (changeType === "remove") {
if (fs.existsSync(targetPath)) {
fs.unlinkSync(targetPath);
}
console.warn(`File ${targetPath} has been removed.`);
} else if (changeType === "add" || changeType === "change") {
// 确保目录存在
const dir = pathLib.dirname(targetPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (changeType === "change" && fs.existsSync(targetPath)) {
fs.unlinkSync(targetPath);
}
fs.copyFileSync(filePath, targetPath, fs.constants.COPYFILE_FICLONE);
console.warn(`File ${filePath} has been copied to ${targetPath}.`);
}
return {
taskId: task.id,
success: true,
filePath
};
} catch (error) {
return {
taskId: task.id,
success: false,
filePath,
error: error instanceof Error ? error.message : String(error)
};
}
} else {
console.warn(`File ${filePath} is not [ts,json] file, skipped.`);
return {
taskId: task.id,
success: true,
filePath
};
}
}
// 处理TypeScript文件
console.clear();
console.warn(`File ${filePath} has been ${changeType}d`);
const modulePath = pathLib.resolve(filePath);
const libPath = modulePath
.replace(pathLib.join(projectPath, "src"), pathLib.join(projectPath, "lib"))
.replace(/\.ts$/, ".js");
if (changeType === "remove") {
try {
if (fs.existsSync(libPath)) {
fs.unlinkSync(libPath);
}
console.warn(`File ${libPath} has been removed.`);
return {
taskId: task.id,
success: true,
filePath
};
} catch (error) {
return {
taskId: task.id,
success: false,
filePath,
error: `Error removing file: ${error instanceof Error ? error.message : String(error)}`
};
}
}
// 编译TypeScript文件
const { program, sourceFile, diagnostics } = createProgramAndSourceFile(filePath);
if (diagnostics.length) {
return {
taskId: task.id,
success: false,
filePath,
error: "TypeScript compilation error"
};
}
// 只输出单个文件
config.lifecycle.onBeforeCompile(config);
const emitResult = program.emit(sourceFile);
if (emitResult.emitSkipped) {
console.error(`Emit failed for ${filePath}!`);
config.lifecycle.onAfterCompile(config);
return {
taskId: task.id,
success: false,
filePath,
error: "TypeScript emit failed"
};
} else {
console.log(`Emit succeeded for ${filePath}.`);
config.lifecycle.onAfterCompile(config);
return {
taskId: task.id,
success: true,
filePath
};
}
};
return {
compileTask
};
};
// 创建文件监视器
const createFileWatcher = (
projectPath: string,
eventEmitter: EventEmitter
) => {
const watchSourcePath = pathLib.join(projectPath, "src");
let startWatching = false;
console.log("Watching for changes in", watchSourcePath);
const watcher = chokidar.watch(watchSourcePath, {
persistent: true,
ignored: (file: string) =>
file.endsWith(".tsx") ||
file.endsWith(".xml") ||
file.includes("components") ||
file.includes("pages") ||
file.includes("hooks"),
interval: 100,
binaryInterval: 100,
cwd: projectPath,
depth: 99,
followSymlinks: true,
ignoreInitial: false,
ignorePermissionErrors: false,
usePolling: false,
alwaysStat: false,
});
const handleFileChange = async (path: string, type: FileChangeType) => {
if (!startWatching) {
return;
}
const event: FileChangeEvent = {
path,
type,
timestamp: Date.now()
};
await eventEmitter.emit("file-changed", event);
};
watcher.on("ready", () => {
console.warn("Initial scan complete. Ready for changes");
startWatching = true;
});
watcher.on("error", (error) => console.log(`Watcher error: ${error}`));
watcher
.on("add", async (path) => {
await handleFileChange(path, "add");
})
.on("change", async (path) => {
await handleFileChange(path, "change");
})
.on("unlink", async (path) => {
await handleFileChange(path, "remove");
});
const dispose = async () => {
await watcher.close();
};
return {
dispose
};
};
// 生成唯一任务ID
const generateTaskId = (): string => {
return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
/**
* 根据 alias 配置表将路径中的别名替换为真实路径
* @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module"
* @param aliasConfig - alias 配置表key 为别名value 为对应的真实路径或路径数组
* @returns 替换后的路径,如果没有匹配到 alias则返回原始路径
*/
const replaceAliasWithPath = (path: string, aliasConfig: Record<string, string | string[]>): string => {
for (const [alias, targets] of Object.entries(aliasConfig)) {
// If alias ends with "*", handle it as a dynamic alias.
if (alias.endsWith('*')) {
// Create a regex pattern that matches paths starting with the alias, followed by any characters
const aliasPattern = new RegExp(`^${alias.replace(/\*$/, "")}(.*)`); // e.g., '@project/*' becomes '@project/(.*)'
const match = path.match(aliasPattern);
if (match) {
// Replace the alias with the target path, appending the matched part from the original path
const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array)
// Ensure that the target path ends with a slash if it's not already
const replacedPath = target.replace(/\/\*$/, "/") + match[1];
return replacedPath;
}
} else {
// Handle static alias without "*" by directly matching the path
if (path.startsWith(alias)) {
const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array)
// Replace the alias part with the target path
return path.replace(alias, target);
}
}
}
// If no alias matches, return the original path
return path;
};
export const watch = (
projectPath: string,
config?: WatchConfig
): Promise<() => Promise<void>> => {
const realConfig = getOverrideConfig(config);
const enableTrace = !!process.env.ENABLE_TRACE;
// 查找配置文件
const configFileName = ts.findConfigFile(
projectPath,
ts.sys.fileExists,
"tsconfig.build.json"
);
if (!configFileName) {
throw new Error("Could not find a valid 'tsconfig.build.json'.");
}
// 读取配置文件
const configFile = ts.readConfigFile(configFileName, ts.sys.readFile);
// 解析配置文件内容
const { options, projectReferences } = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
pathLib.dirname(configFileName)
);
const aliasConfig: AliasConfig = cloneDeep(options.paths) || {};
// 输出原始配置
// console.log("[DEBUG] Original alias config:", aliasConfig);
Object.keys(aliasConfig).forEach((key) => {
const value = aliasConfig[key];
// 替换src
aliasConfig[key] = typeof value === "string" ? value.replace("src", "lib") : value.map((v: string) => v.replace("src", "lib"));
});
// 输出真实的alias配置
console.debug("[DEBUG] Running Alias config:", aliasConfig);
// 初始化polyfill
const polyfillLoader = () => {
const BuiltinModule = require("module");
// 模拟环境下的 module 构造函数
const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule;
// 保存原始 _resolveFilename 方法
const oldResolveFilename = Module._resolveFilename;
// 通用的路径检查与编译函数
function resolveAndCompile(requestPath: string, parent: ModuleType, options: CompilerOptions, projectReferences: readonly ts.ProjectReference[] | undefined) {
const jsPath = pathLib.resolve(requestPath + ".js");
const tsPath = jsPath.replace(/\.js$/, ".ts").replace(
pathLib.join(projectPath, "lib"),
pathLib.join(projectPath, "src")
);
// 检查并编译 .ts 文件
if (fs.existsSync(tsPath)) {
console.log(`[resolve] Found TypeScript source file: ${tsPath}, attempting to compile...`);
const program = ts.createProgram({
rootNames: [tsPath],
options,
projectReferences,
});
const sourceFile = program.getSourceFile(tsPath);
const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
if (diagnostics.length) {
console.error(`[resolve] Compilation failed for: ${tsPath}`);
throw new Error("TypeScript compilation error");
}
const emitResult = program.emit(sourceFile);
if (emitResult.emitSkipped) {
console.error(`[resolve] Emit skipped for: ${tsPath}`);
throw new Error("TypeScript emit skipped");
}
console.log(`[resolve] Successfully compiled: ${tsPath}`);
return jsPath;
}
// 如果没有找到对应的 .ts 文件
if (fs.existsSync(jsPath)) {
return jsPath;
}
throw new Error(`[resolve] Unable to find module: ${requestPath}`);
}
// 处理文件夹导入的情况
function resolveDirectory(requestPath: string, parent: ModuleType, options: CompilerOptions, projectReferences: readonly ts.ProjectReference[] | undefined) {
const indexJs = pathLib.join(requestPath, "index.js");
const indexTs = pathLib.join(requestPath, "index.ts").replace(
pathLib.join(projectPath, "lib"),
pathLib.join(projectPath, "src")
);
if (fs.existsSync(indexTs)) {
console.log(`[resolve] Found TypeScript index file: ${indexTs}, attempting to compile...`);
return resolveAndCompile(indexTs, parent, options, projectReferences);
}
if (fs.existsSync(indexJs)) {
return indexJs;
}
throw new Error(`[resolve] No index file found in directory: ${requestPath}`);
}
// 重写 _resolveFilename 方法
Module._resolveFilename = function (
request: string, // 模块请求路径
parent: ModuleType, // 调用方模块
isMain: boolean, // 是否是主模块
rFoptions: object | undefined // 解析选项
) {
let resolvedRequest = request;
const replacedPath = replaceAliasWithPath(request, aliasConfig);
if (replacedPath !== request) {
console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`);
resolvedRequest = pathLib.join(projectPath, replacedPath);
}
try {
return oldResolveFilename.call(this, resolvedRequest, parent, isMain, rFoptions);
} catch (error: any) {
if (error.code === "MODULE_NOT_FOUND") {
const requestPath = pathLib.resolve(pathLib.dirname(parent.filename), resolvedRequest);
// 处理文件夹导入
if (fs.existsSync(requestPath) && fs.statSync(requestPath).isDirectory()) {
return resolveDirectory(requestPath, parent, options, projectReferences);
}
// 处理单文件导入
return resolveAndCompile(requestPath, parent, options, projectReferences);
}
throw error;
}
};
};
// 初始化polyfill
polyfillLoader();
realConfig.polyfill.console.enable && polyfillConsole("watch", enableTrace, realConfig.polyfill?.console?.formatter);
// 初始编译检查
const initialCompile = () => {
const serverConfigFile = pathLib.join(projectPath, "lib/configuration/server.js");
if (!fs.existsSync(serverConfigFile)) {
// 尝试编译src/configuration/server.ts
console.log(`[watch] Server configuration file not found, attempting to compile the project......`);
const tryCompile = (tsFile: string) => {
console.log(`[watch] Compiling: ${tsFile}`);
if (fs.existsSync(tsFile)) {
const program = ts.createProgram({
rootNames: [tsFile],
options,
projectReferences,
});
const diagnostics = ts.getPreEmitDiagnostics(program);
if (diagnostics.length) {
console.error(`Error in ${tsFile}`);
diagnostics.forEach((diagnostic) => {
console.error(
`${ts.flattenDiagnosticMessageText(
diagnostic.messageText,
"\n"
)}`
);
});
console.error(`文件存在语法错误,请检查修复后重试!`);
process.exit(1);
}
// 编译所有的文件
const emitResult = program.emit();
if (emitResult.emitSkipped) {
console.error(`Emit failed for ${tsFile}!`);
process.exit(1);
}
console.log(`Emit succeeded. ${tsFile} has been compiled.`);
}
}
// 所有要编译的目录
// 其他涉及到的目录会在运行的时候自动编译这里主要处理的是动态require的文件
const compileFiles = [
"src/configuration/index.ts",
"src/aspects/index.ts",
"src/checkers/index.ts",
"src/triggers/index.ts",
"src/timers/index.ts",
"src/routines/start.ts",
"src/watchers/index.ts",
"src/endpoints/index.ts",
"src/data/index.ts",
"src/ports/index.ts",
];
compileFiles.forEach(tryCompile);
}
};
return new Promise((resolve, reject) => {
// 创建事件系统
const eventEmitter = createEventEmitter();
// 创建各个组件
const compileQueue = createCompileQueue(eventEmitter);
const compiler = createCompiler(projectPath, options, projectReferences, aliasConfig, realConfig);
const serverManager = createServerManager(projectPath, eventEmitter, realConfig);
const fileWatcher = createFileWatcher(projectPath, eventEmitter);
// 设置编译器处理器
compileQueue.setTaskProcessor(compiler.compileTask);
// 设置事件监听器
eventEmitter.on("file-changed", (event: FileChangeEvent) => {
const task: CompileTask = {
id: generateTaskId(),
filePath: event.path,
changeType: event.type,
timestamp: event.timestamp
};
compileQueue.addTask(task);
});
eventEmitter.on("compile-batch-started", (data: { count: number }) => {
console.log(`----> Starting compilation batch (${data.count} files)...`);
});
eventEmitter.on("compile-batch-completed", (data: { results: CompileResult[] }) => {
const successCount = data.results.filter(r => r.success).length;
console.log(`----> Compilation batch completed: ${successCount}/${data.results.length} successful`);
});
// 编译成功之后若设置的同步i18n则触发i18n更新
if (realConfig.autoUpdateI18n) {
const projectI18nPath = pathLib.join("src", "data", "i18n.ts");
eventEmitter.on("compile-task-completed", async (result: CompileResult) => {
if (result.filePath == projectI18nPath && result.success) {
console.log("-------------start upgrade i18n.-------------")
// 这里是copyupgradeI18n的
const { checkAndUpdateI18n } = require('oak-backend-base/lib/routines/i18n.js');
const simpleConnector = require(pathLib.join(
projectPath,
"lib/config/connector"
)).default;
// 这里注意要在require之前因为require会触发编译
const { startup } = require('./start') as {
startup: (
pwd: string,
connector: any,
omitWatchers?: boolean,
omitTimers?: boolean,
routine?: (context: AsyncContext<any>) => Promise<void>
) => Promise<() => Promise<void>>;
}
startup(pwd, simpleConnector, true, true, checkAndUpdateI18n).then(() => {
console.log("------------upgrade i18n success.------------")
}).catch((err) => {
console.error("------------upgrade i18n failed!------------", err)
})
}
});
}
eventEmitter.on("server-restart-needed", async () => {
if (!serverManager.isRestarting()) {
console.log("----> Restarting server...");
await serverManager.restart();
}
});
// 初始化
realConfig.lifecycle.onInit(realConfig);
// 执行初始编译
initialCompile();
// 启动服务器
serverManager.restart()
.then(() => {
const dispose = async () => {
await fileWatcher.dispose();
await serverManager.dispose();
realConfig.lifecycle.onDispose(realConfig);
};
resolve(dispose);
})
.catch(reject);
});
};