This commit is contained in:
parent
712964f288
commit
3f4c86783e
|
|
@ -4,4 +4,4 @@ import { Connector, EntityDict } from 'oak-domain/lib/types';
|
|||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||
export declare function startup<ED extends EntityDict & BaseEntityDict, FrontCxt extends SyncContext<ED>, Cxt extends BackendRuntimeContext<ED>>(path: string, connector: Connector<ED, FrontCxt>, omitWatchers?: boolean, omitTimers?: boolean, routine?: (context: AsyncContext<ED>) => Promise<void>): Promise<(() => void) | undefined>;
|
||||
export declare function startup<ED extends EntityDict & BaseEntityDict, FrontCxt extends SyncContext<ED>, Cxt extends BackendRuntimeContext<ED>>(path: string, connector: Connector<ED, FrontCxt>, omitWatchers?: boolean, omitTimers?: boolean, routine?: (context: AsyncContext<ED>) => Promise<void>): Promise<(() => Promise<void>) | undefined>;
|
||||
|
|
|
|||
|
|
@ -235,8 +235,8 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
|||
process.exit(0);
|
||||
});
|
||||
const shutdown = async () => {
|
||||
httpServer.close();
|
||||
koa.removeAllListeners();
|
||||
await httpServer.close();
|
||||
await koa.removeAllListeners();
|
||||
await appLoader.unmount();
|
||||
};
|
||||
return shutdown;
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export declare const watch: (projectPath: string) => void;
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
"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 path_1 = require("path");
|
||||
const typescript_1 = tslib_1.__importDefault(require("typescript"));
|
||||
const path_2 = tslib_1.__importDefault(require("path"));
|
||||
const start_1 = require("./start");
|
||||
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
|
||||
const fs_1 = tslib_1.__importDefault(require("fs"));
|
||||
const lodash_1 = require("lodash");
|
||||
const watch = (projectPath) => {
|
||||
const enableTrace = !!process.env.ENABLE_TRACE;
|
||||
//polyfill console.log 添加时间
|
||||
const polyfill = (trace) => {
|
||||
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 = trace ? "]\x1B[0m\n" : "]\x1B[0m";
|
||||
// 获取调用堆栈信息
|
||||
const getCallerInfo = () => {
|
||||
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;
|
||||
};
|
||||
const getFileInfo = () => {
|
||||
if (!trace) {
|
||||
return "";
|
||||
}
|
||||
const callerInfo = getCallerInfo();
|
||||
const fileInfo = callerInfo
|
||||
? `${callerInfo.getFileName()}:${callerInfo.getLineNumber()}`
|
||||
: "";
|
||||
return fileInfo.trim();
|
||||
};
|
||||
// polyfill console.log 添加时间和文件位置
|
||||
const oldLog = console.log;
|
||||
console.log = function (...args) {
|
||||
oldLog(infoStart, getTime(), getFileInfo(), clearColor, ...args);
|
||||
};
|
||||
const oldWarn = console.warn;
|
||||
console.warn = function (...args) {
|
||||
oldWarn(warnStart, getTime(), getFileInfo(), clearColor, ...args);
|
||||
};
|
||||
const oldError = console.error;
|
||||
console.error = function (...args) {
|
||||
oldError(errorStart, getTime(), getFileInfo(), clearColor, ...args);
|
||||
};
|
||||
};
|
||||
polyfill(enableTrace);
|
||||
let shutdown;
|
||||
const restart = async () => {
|
||||
if (shutdown) {
|
||||
await shutdown();
|
||||
}
|
||||
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((0, path_1.join)(projectPath, 'lib/config/connector')).default;
|
||||
console.warn("----> Starting service......");
|
||||
shutdown = await (0, start_1.startup)(pwd, simpleConnector);
|
||||
};
|
||||
const watchSourcePath = (0, path_1.join)(projectPath, "src");
|
||||
console.log("Watching for changes in", watchSourcePath);
|
||||
// 查找配置文件
|
||||
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_2.default.dirname(configFileName));
|
||||
const watcher = chokidar_1.default.watch(watchSourcePath, {
|
||||
persistent: true,
|
||||
ignored: (file) => file.endsWith(".tsx") ||
|
||||
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 isProcessing = false;
|
||||
const fileChangeHandler = async (path, type) => {
|
||||
// 判断一下是不是以下扩展名:ts,tsx
|
||||
if (!path.endsWith(".ts")) {
|
||||
// 如果是json或者xml文件,复制或者删除
|
||||
if (path.endsWith(".json") || path.endsWith(".xml")) {
|
||||
const targetPath = path.replace("src", "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,xml] file, skiped.`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 控制台清空
|
||||
console.clear();
|
||||
console.warn(`File ${path} has been ${type}d`);
|
||||
// 先判断一下这个文件在不在require.cache里面
|
||||
const modulePath = (0, path_1.resolve)(path);
|
||||
// 将src替换为lib
|
||||
const libPath = modulePath
|
||||
.replace("\\src\\", "\\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;
|
||||
}
|
||||
else {
|
||||
// 如果是删除,则需要发出警告,文件正在被进程使用
|
||||
if (type === "remove") {
|
||||
console.error(`File ${libPath} is being used, skiped.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 只输出单个文件
|
||||
const emitResult = program.emit(sourceFile);
|
||||
// 是否成功
|
||||
const result = emitResult.emitSkipped;
|
||||
if (result) {
|
||||
console.error(`Emit failed for ${path}!`);
|
||||
}
|
||||
else {
|
||||
console.log(`Emit succeeded. ${compileOnly ? "" : "reload service......"}`);
|
||||
if (compileOnly) {
|
||||
return;
|
||||
}
|
||||
await restart();
|
||||
}
|
||||
};
|
||||
const onChangeDebounced = (0, lodash_1.debounce)(async (path, type) => {
|
||||
if (isProcessing) {
|
||||
console.log("Processing, please wait...");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isProcessing = true;
|
||||
await fileChangeHandler(path, type);
|
||||
}
|
||||
catch (e) {
|
||||
console.clear();
|
||||
console.error(e);
|
||||
}
|
||||
finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
}, 100);
|
||||
watcher
|
||||
.on("add", (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "add");
|
||||
}
|
||||
})
|
||||
.on("change", (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "change");
|
||||
}
|
||||
})
|
||||
.on("unlink", (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "remove");
|
||||
}
|
||||
});
|
||||
restart();
|
||||
};
|
||||
exports.watch = watch;
|
||||
|
|
@ -303,7 +303,10 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
|
|||
"readable-stream": "3.6.2"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"@project": "./lib"
|
||||
"@project": "./lib",
|
||||
"@oak-general-business": "./node_modules/oak-general-business/lib",
|
||||
"@oak-frontend-base": "./node_modules/oak-frontend-base/lib",
|
||||
"@oak-app-domain": "./lib/oak-app-domain"
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
Loading…
Reference in New Issue