"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 lodash_1 = require("lodash"); const defaultConfig = { 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: () => { console.log("----> Watcher initialized."); }, onServerStart: () => { console.log("----> Server started."); }, onBeforeCompile: () => { console.log("----> Compiling......"); }, onAfterCompile: () => { console.log("----> Compile completed."); }, onServerShutdown: () => { console.log("----> Server shutdown."); }, onDispose: () => { 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; }; /** * 根据 alias 配置表将路径中的别名替换为真实路径 * @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module" * @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组 * @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径 */ function replaceAliasWithPath(path, aliasConfig) { 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 = (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 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 = (0, lodash_1.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) => v.replace("src", "lib")); }); // 输出真实的alias配置 console.debug("[DEBUG] Running Alias config:", aliasConfig); const createProgramAndSourceFile = (path, options, projectReferences) => { 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 }; }; // 这个函数用于解决require的时候,如果文件不存在,会尝试编译ts文件 // 测试:在src下新建一个ts文件,然后删除lib下的js文件,然后require这个文件,会发现会自动编译ts文件 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, sourceFile, diagnostics } = createProgramAndSourceFile(tsPath, options, projectReferences); 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_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 = path_1.default.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; } }; }; polyfillLoader(); //polyfill console.log 添加时间 const polyfillConsole = (trace) => { // 获取调用堆栈信息 const getCallerInfo = () => { if (!trace) { return null; } const originalFunc = Error.prepareStackTrace; let callerInfo = null; try { const err = new Error(); Error.prepareStackTrace = (err, stack) => stack; const stack = err.stack; // Type assertion here const currentFile = stack[0].getFileName(); for (let i = 1; i < stack.length; i++) { // Start from index 1 const callSite = stack[i]; if (currentFile !== callSite.getFileName()) { callerInfo = callSite; break; } } } catch (e) { console.error(e); } Error.prepareStackTrace = originalFunc; return callerInfo; }; // polyfill console.log 添加时间和文件位置 ["info", "warn", "error"].forEach((level) => { const levelStr = level; const oldFunc = console[levelStr]; console[levelStr] = function (...args) { oldFunc(...(defaultConfig.polyfill?.console?.formatter({ level: levelStr, caller: getCallerInfo(), args, }) || [])); }; }); }; realConfig.polyfill.console.enable && polyfillConsole(enableTrace); // 这里注意要在require之前,因为require会触发编译 const { startup } = require('./start'); // 如果lib目录是空的,则直接编译所有的ts文件 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, diagnostics } = createProgramAndSourceFile(tsFile, options, projectReferences); 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(sourceFile); // 编译所有的文件 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) => { realConfig.lifecycle.onInit(); let shutdown; const restart = async () => { if (shutdown) { console.log("----> Shutting down service......"); await shutdown().then(realConfig.lifecycle.onServerShutdown); // reset shutdown 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.`); const pwd = process.cwd(); // 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......"); shutdown = await startup(pwd, simpleConnector).then((shutdown) => { realConfig.lifecycle.onServerStart(); return shutdown; }); }; const watchSourcePath = path_1.default.join(projectPath, "src"); 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, }); let startWatching = false; watcher.on("ready", () => { console.warn("Initial scan complete. Ready for changes"); startWatching = true; }); watcher.on("error", (error) => console.log(`Watcher error: ${error}`)); let processingQueue = []; const fileChangeHandler = async (path, type) => { // 判断一下是不是以下扩展名:ts if (!path.endsWith(".ts")) { // 如果是json文件,复制或者删除 if (path.endsWith(".json")) { // const targetPath = path.replace("src", "lib"); // 这里直接替换不对,应该是拿到项目目录,把项目目录/src 换成项目目录/lib const targetPath = path.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib")); if (type === "remove") { fs_1.default.unlinkSync(targetPath); console.warn(`File ${targetPath} has been removed.`); } else if (type === "add") { fs_1.default.copyFileSync(path, targetPath, fs_1.default.constants.COPYFILE_FICLONE); console.warn(`File ${path} has been created at ${targetPath}.`); } else if (type === "change") { // 强制覆盖文件 if (fs_1.default.existsSync(targetPath)) { fs_1.default.unlinkSync(targetPath); } fs_1.default.copyFileSync(path, targetPath, fs_1.default.constants.COPYFILE_FICLONE); console.warn(`File ${path} has been copied to ${targetPath}.`); } } else { console.warn(`File ${path} is not [ts,json] file, skiped.`); } return; } // 控制台清空 console.clear(); console.warn(`File ${path} has been ${type}d`); // 先判断一下这个文件在不在require.cache里面 const modulePath = path_1.default.resolve(path); // 将src替换为lib const libPath = modulePath .replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib")) .replace(/\.ts$/, ".js"); let compileOnly = false; if (!require.cache[libPath]) { // 如果是删除的话,直接尝试删除lib下的文件 if (type === "remove") { try { fs_1.default.unlinkSync(libPath); } catch (e) { console.error(`Error in delete ${libPath}`, e); } console.warn(`File ${libPath} has been removed.`); return; } // console.warn( // `File ${libPath} is not in module cache, will compile only.` // ); // compileOnly = true; // 这里现在不需要仅编译了,因为在require的时候会自动编译ts文件 } else { // 如果是删除,则需要发出警告,文件正在被进程使用 if (type === "remove") { console.error(`File ${libPath} is being used, skiped.`); return; } } const { program, sourceFile, diagnostics } = createProgramAndSourceFile(path, options, projectReferences); if (diagnostics.length) { return; } // 只输出单个文件 realConfig.lifecycle.onBeforeCompile(); const emitResult = program.emit(sourceFile); // 是否成功 const result = emitResult.emitSkipped; if (result) { console.error(`Emit failed for ${path}!`); realConfig.lifecycle.onAfterCompile(); } else { console.log(`Emit succeeded. ${compileOnly ? "" : "reload service......"}`); realConfig.lifecycle.onAfterCompile(); if (compileOnly) { return; } // await restart(); // 只有在队列里面的最后一个文件编译完成后才会重启服务 if (processingQueue.length === 1) { await restart(); } else { console.log("Waiting for other operations to complete..."); } } }; const onChangeDebounced = async (path, type) => { if (processingQueue.includes(path)) { console.log("Processing, please wait..."); return; } try { processingQueue.push(path); await fileChangeHandler(path, type); } catch (e) { console.clear(); console.error(e); process.exit(1); } finally { processingQueue = processingQueue.filter((p) => p !== path); } }; watcher .on("add", async (path) => { if (startWatching) { await onChangeDebounced(path, "add"); } }) .on("change", async (path) => { if (startWatching) { await onChangeDebounced(path, "change"); } }) .on("unlink", async (path) => { if (startWatching) { await onChangeDebounced(path, "remove"); } }); const dispose = async () => { if (shutdown) { await shutdown(); } await watcher.close(); realConfig.lifecycle.onDispose(); }; restart() .then(() => { resolve(dispose); }) .catch(reject); }); }; exports.watch = watch;