752 lines
31 KiB
JavaScript
752 lines
31 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.watch = void 0;
|
||
const tslib_1 = require("tslib");
|
||
const chokidar_1 = tslib_1.__importDefault(require("chokidar"));
|
||
const typescript_1 = tslib_1.__importDefault(require("typescript"));
|
||
const path_1 = tslib_1.__importDefault(require("path"));
|
||
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
|
||
const fs_1 = tslib_1.__importDefault(require("fs"));
|
||
const polyfill_1 = require("./polyfill");
|
||
const tsc_alias_1 = require("tsc-alias");
|
||
const timer_manager_1 = require("./timer-manager");
|
||
// 创建事件发射器
|
||
const createEventEmitter = () => {
|
||
const listeners = new Map();
|
||
return {
|
||
on: (event, handler) => {
|
||
if (!listeners.has(event)) {
|
||
listeners.set(event, new Set());
|
||
}
|
||
listeners.get(event).add(handler);
|
||
},
|
||
emit: async (event, data) => {
|
||
const handlers = listeners.get(event);
|
||
if (handlers) {
|
||
const promises = Array.from(handlers).map(handler => Promise.resolve(handler(data)));
|
||
await Promise.all(promises);
|
||
}
|
||
},
|
||
off: (event, handler) => {
|
||
const handlers = listeners.get(event);
|
||
if (handlers) {
|
||
handlers.delete(handler);
|
||
}
|
||
}
|
||
};
|
||
};
|
||
// 创建编译任务队列
|
||
const createCompileQueue = (eventEmitter) => {
|
||
const queue = [];
|
||
let isProcessing = false;
|
||
let processingCount = 0;
|
||
let taskProcessor = async (task) => ({
|
||
taskId: task.id,
|
||
success: true,
|
||
filePath: task.filePath
|
||
});
|
||
const addTask = (task) => {
|
||
// 检查是否有相同文件的任务已存在,如果有则更新时间戳
|
||
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 = [];
|
||
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 = {
|
||
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) => {
|
||
taskProcessor = processor;
|
||
}
|
||
};
|
||
};
|
||
const defaultConfig = {
|
||
autoUpdateI18n: true,
|
||
polyfill: {
|
||
console: {
|
||
enable: true,
|
||
trace: true,
|
||
formatter: ({ level, caller, args }) => {
|
||
const getFileInfo = () => {
|
||
if (!caller) {
|
||
return "";
|
||
}
|
||
const fileInfo = caller
|
||
? `${caller.getFileName()}:${caller.getLineNumber()}`
|
||
: "";
|
||
return fileInfo.trim();
|
||
};
|
||
const getTime = () => (0, dayjs_1.default)().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) => {
|
||
console.log("----> Watcher initialized.");
|
||
},
|
||
onServerStart: (config) => {
|
||
console.log("----> Server started.");
|
||
},
|
||
onBeforeCompile: (config) => {
|
||
console.log("----> Compiling......");
|
||
},
|
||
onAfterCompile: (config) => {
|
||
console.log("----> Compile completed.");
|
||
},
|
||
onServerShutdown: (config) => {
|
||
console.log("----> Server shutdown.");
|
||
},
|
||
onDispose: (config) => {
|
||
console.log("----> Watcher disposed.");
|
||
},
|
||
},
|
||
};
|
||
const getOverrideConfig = (config) => {
|
||
// 遍历default里面的每一个key,如果config里面有,就覆盖,没有就用default的
|
||
const overrideInner = (obj, dobj) => {
|
||
const result = {};
|
||
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)
|
||
: defaultConfig;
|
||
};
|
||
// 当前运行目录
|
||
const pwd = process.cwd();
|
||
// 创建服务器管理器
|
||
const createServerManager = (projectPath, eventEmitter, config) => {
|
||
let shutdown;
|
||
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(path_1.default.join(projectPath, "lib/config/connector")).default;
|
||
console.warn("----> Starting service......");
|
||
try {
|
||
(0, timer_manager_1.clearAllTimers)();
|
||
// 这里注意要在require之前,因为require会触发编译
|
||
const { startup } = require('./start');
|
||
shutdown = await startup(pwd, simpleConnector).then((shutdown) => {
|
||
config.lifecycle.onServerStart(config);
|
||
return shutdown;
|
||
});
|
||
}
|
||
catch (error) {
|
||
console.error("----> Failed to start service:", error);
|
||
isRestarting = false;
|
||
return;
|
||
}
|
||
isRestarting = false;
|
||
await eventEmitter.emit("server-restarted", {});
|
||
};
|
||
const dispose = async () => {
|
||
if (shutdown) {
|
||
await shutdown();
|
||
}
|
||
};
|
||
return {
|
||
restart,
|
||
dispose,
|
||
isRestarting: () => isRestarting
|
||
};
|
||
};
|
||
// 创建编译器
|
||
const createCompiler = async (projectPath, options, projectReferences, treatFile, config) => {
|
||
const createProgramAndSourceFile = (path) => {
|
||
const program = typescript_1.default.createProgram({
|
||
rootNames: [path],
|
||
options,
|
||
projectReferences,
|
||
});
|
||
const sourceFile = program.getSourceFile(path);
|
||
// 是否有语法错误
|
||
const diagnostics = typescript_1.default.getPreEmitDiagnostics(program, sourceFile);
|
||
if (diagnostics.length) {
|
||
const syntaxErrors = diagnostics.filter((diagnostic) => diagnostic.category === typescript_1.default.DiagnosticCategory.Error);
|
||
if (syntaxErrors.length) {
|
||
console.error(`Error in ${path}`);
|
||
syntaxErrors.forEach((diagnostic) => {
|
||
console.error(`${typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`);
|
||
});
|
||
console.error(`文件存在语法错误,请检查修复后重试!`);
|
||
return { program, sourceFile, diagnostics };
|
||
}
|
||
}
|
||
return { program, sourceFile, diagnostics };
|
||
};
|
||
const compileTask = async (task) => {
|
||
const { filePath, changeType } = task;
|
||
const modulePath = path_1.default.resolve(filePath);
|
||
// 判断文件类型
|
||
if (!filePath.endsWith(".ts")) {
|
||
// 处理非TypeScript文件 (如JSON文件)
|
||
if (filePath.endsWith(".json")) {
|
||
const targetPath = modulePath.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"));
|
||
try {
|
||
if (changeType === "remove") {
|
||
if (fs_1.default.existsSync(targetPath)) {
|
||
fs_1.default.unlinkSync(targetPath);
|
||
}
|
||
console.warn(`File ${targetPath} has been removed.`);
|
||
}
|
||
else if (changeType === "add" || changeType === "change") {
|
||
// 确保目录存在
|
||
const dir = path_1.default.dirname(targetPath);
|
||
if (!fs_1.default.existsSync(dir)) {
|
||
fs_1.default.mkdirSync(dir, { recursive: true });
|
||
}
|
||
if (changeType === "change" && fs_1.default.existsSync(targetPath)) {
|
||
fs_1.default.unlinkSync(targetPath);
|
||
}
|
||
fs_1.default.copyFileSync(filePath, targetPath, fs_1.default.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 libPath = modulePath
|
||
.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"))
|
||
.replace(/\.ts$/, ".js");
|
||
if (changeType === "remove") {
|
||
try {
|
||
if (fs_1.default.existsSync(libPath)) {
|
||
fs_1.default.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);
|
||
const jsFilePath = libPath;
|
||
treatFile(jsFilePath);
|
||
return {
|
||
taskId: task.id,
|
||
success: true,
|
||
filePath
|
||
};
|
||
}
|
||
};
|
||
return {
|
||
compileTask
|
||
};
|
||
};
|
||
// 创建文件监视器
|
||
const createFileWatcher = (projectPath, eventEmitter) => {
|
||
const watchSourcePath = path_1.default.join(projectPath, "src");
|
||
let startWatching = false;
|
||
console.log("Watching for changes in", watchSourcePath);
|
||
const watcher = chokidar_1.default.watch(watchSourcePath, {
|
||
persistent: true,
|
||
ignored: (file) => 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, type) => {
|
||
if (!startWatching) {
|
||
return;
|
||
}
|
||
const event = {
|
||
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 = () => {
|
||
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;
|
||
// };
|
||
const watch = async (projectPath, config) => {
|
||
const realConfig = getOverrideConfig(config);
|
||
const enableTrace = !!process.env.ENABLE_TRACE;
|
||
// 查找配置文件
|
||
const configFileName = typescript_1.default.findConfigFile(projectPath, typescript_1.default.sys.fileExists, "tsconfig.build.json");
|
||
if (!configFileName) {
|
||
throw new Error("Could not find a valid 'tsconfig.build.json'.");
|
||
}
|
||
const runFile = await (0, tsc_alias_1.prepareSingleFileReplaceTscAliasPaths)({
|
||
configFile: path_1.default.join(projectPath, "tsconfig.build.json"),
|
||
resolveFullPaths: true,
|
||
});
|
||
function treatFile(filePath) {
|
||
console.log(`Processing file for alias replacement: ${filePath}`);
|
||
const fileContents = fs_1.default.readFileSync(filePath, 'utf8');
|
||
const newContents = runFile({ fileContents, filePath });
|
||
// do stuff with newContents
|
||
fs_1.default.writeFileSync(filePath, newContents, 'utf8');
|
||
}
|
||
// // 读取配置文件
|
||
const configFile = typescript_1.default.readConfigFile(configFileName, typescript_1.default.sys.readFile);
|
||
// // 解析配置文件内容
|
||
const { options, projectReferences } = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path_1.default.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, parent, options, projectReferences) {
|
||
const jsPath = path_1.default.resolve(requestPath + ".js");
|
||
const tsPath = jsPath.replace(/\.js$/, ".ts").replace(path_1.default.join(projectPath, "lib"), path_1.default.join(projectPath, "src"));
|
||
// 检查并编译 .ts 文件
|
||
if (fs_1.default.existsSync(tsPath)) {
|
||
console.log(`[resolve] Found TypeScript source file: ${tsPath}, attempting to compile...`);
|
||
const program = typescript_1.default.createProgram({
|
||
rootNames: [tsPath],
|
||
options,
|
||
projectReferences,
|
||
});
|
||
const sourceFile = program.getSourceFile(tsPath);
|
||
const diagnostics = typescript_1.default.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");
|
||
}
|
||
else {
|
||
treatFile(jsPath);
|
||
}
|
||
console.log(`[resolve] Successfully compiled: ${tsPath}`);
|
||
return jsPath;
|
||
}
|
||
// 如果没有找到对应的 .ts 文件
|
||
if (fs_1.default.existsSync(jsPath)) {
|
||
return jsPath;
|
||
}
|
||
throw new Error(`[resolve] Unable to find module: ${requestPath}`);
|
||
}
|
||
// 处理文件夹导入的情况
|
||
function resolveDirectory(requestPath, parent, options, projectReferences) {
|
||
const indexJs = path_1.default.join(requestPath, "index.js");
|
||
const indexTs = path_1.default.join(requestPath, "index.ts").replace(path_1.default.join(projectPath, "lib"), path_1.default.join(projectPath, "src"));
|
||
if (fs_1.default.existsSync(indexTs)) {
|
||
console.log(`[resolve] Found TypeScript index file: ${indexTs}, attempting to compile...`);
|
||
return resolveAndCompile(indexTs, parent, options, projectReferences);
|
||
}
|
||
if (fs_1.default.existsSync(indexJs)) {
|
||
return indexJs;
|
||
}
|
||
throw new Error(`[resolve] No index file found in directory: ${requestPath}`);
|
||
}
|
||
// 重写 _resolveFilename 方法
|
||
Module._resolveFilename = function (request, // 模块请求路径
|
||
parent, // 调用方模块
|
||
isMain, // 是否是主模块
|
||
rFoptions // 解析选项
|
||
) {
|
||
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) {
|
||
if (error.code === "MODULE_NOT_FOUND") {
|
||
const requestPath = path_1.default.resolve(path_1.default.dirname(parent.filename), resolvedRequest);
|
||
// 处理文件夹导入
|
||
if (fs_1.default.existsSync(requestPath) && fs_1.default.statSync(requestPath).isDirectory()) {
|
||
return resolveDirectory(requestPath, parent, options, projectReferences);
|
||
}
|
||
// 处理单文件导入
|
||
return resolveAndCompile(requestPath, parent, options, projectReferences);
|
||
}
|
||
throw error;
|
||
}
|
||
};
|
||
};
|
||
// 初始化polyfill
|
||
polyfillLoader();
|
||
realConfig.polyfill.console.enable && (0, polyfill_1.polyfillConsole)("watch", enableTrace, realConfig.polyfill?.console?.formatter);
|
||
// 监听定时器相关的API,确保定时器在重启后不会重复触发
|
||
(0, timer_manager_1.hookTimers)();
|
||
// 初始编译检查
|
||
const initialCompile = () => {
|
||
const serverConfigFile = path_1.default.join(projectPath, "lib/configuration/server.js");
|
||
if (!fs_1.default.existsSync(serverConfigFile)) {
|
||
// 尝试编译src/configuration/server.ts
|
||
console.log(`[watch] Server configuration file not found, attempting to compile the project......`);
|
||
const tryCompile = (tsFile) => {
|
||
console.log(`[watch] Compiling: ${tsFile}`);
|
||
if (fs_1.default.existsSync(tsFile)) {
|
||
const program = typescript_1.default.createProgram({
|
||
rootNames: [tsFile],
|
||
options,
|
||
projectReferences,
|
||
});
|
||
const diagnostics = typescript_1.default.getPreEmitDiagnostics(program);
|
||
if (diagnostics.length) {
|
||
console.error(`Error in ${tsFile}`);
|
||
diagnostics.forEach((diagnostic) => {
|
||
console.error(`${typescript_1.default.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);
|
||
// 最后替换lib目录下所有的路径别名
|
||
console.log(`[watch] Replacing path aliases in lib directory......`);
|
||
const libDir = path_1.default.join(projectPath, "lib");
|
||
const walkDir = (dir) => {
|
||
const files = fs_1.default.readdirSync(dir);
|
||
files.forEach((file) => {
|
||
const fullPath = path_1.default.join(dir, file);
|
||
const stat = fs_1.default.statSync(fullPath);
|
||
if (stat.isDirectory()) {
|
||
walkDir(fullPath);
|
||
}
|
||
else if (stat.isFile() && fullPath.endsWith(".js")) {
|
||
const fileContents = fs_1.default.readFileSync(fullPath, 'utf8');
|
||
const newContents = runFile({ fileContents, filePath: fullPath });
|
||
fs_1.default.writeFileSync(fullPath, newContents, 'utf8');
|
||
}
|
||
});
|
||
};
|
||
walkDir(libDir);
|
||
console.log(`[watch] Path alias replacement completed.`);
|
||
}
|
||
};
|
||
return new Promise(async (resolve, reject) => {
|
||
// 创建事件系统
|
||
const eventEmitter = createEventEmitter();
|
||
// 创建各个组件
|
||
const compileQueue = createCompileQueue(eventEmitter);
|
||
const compiler = await createCompiler(projectPath, options, projectReferences, treatFile, realConfig);
|
||
const serverManager = createServerManager(projectPath, eventEmitter, realConfig);
|
||
const fileWatcher = createFileWatcher(projectPath, eventEmitter);
|
||
// 设置编译器处理器
|
||
compileQueue.setTaskProcessor(compiler.compileTask);
|
||
// 设置事件监听器
|
||
eventEmitter.on("file-changed", (event) => {
|
||
const task = {
|
||
id: generateTaskId(),
|
||
filePath: event.path,
|
||
changeType: event.type,
|
||
timestamp: event.timestamp
|
||
};
|
||
compileQueue.addTask(task);
|
||
});
|
||
eventEmitter.on("compile-batch-started", (data) => {
|
||
console.log(`----> Starting compilation batch (${data.count} files)...`);
|
||
});
|
||
eventEmitter.on("compile-batch-completed", (data) => {
|
||
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 = path_1.default.join("src", "data", "i18n.ts");
|
||
eventEmitter.on("compile-task-completed", async (result) => {
|
||
if (result.filePath == projectI18nPath && result.success) {
|
||
console.log("-------------start upgrade i18n.-------------");
|
||
// 这里是copy:upgradeI18n的
|
||
const { checkAndUpdateI18n } = require('oak-backend-base/lib/routines/i18n.js');
|
||
const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default;
|
||
// 这里注意要在require之前,因为require会触发编译
|
||
const { startup } = require('./start');
|
||
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);
|
||
});
|
||
};
|
||
exports.watch = watch;
|