Merge branch 'dev' of gitea.51mars.com:Oak-Team/oak-cli into dev
This commit is contained in:
commit
5dda951e6a
|
|
@ -0,0 +1,11 @@
|
|||
type AnalysisOptions = {
|
||||
outputDir: string;
|
||||
includeNodeModules: boolean;
|
||||
};
|
||||
/**
|
||||
* 分析项目中的模块
|
||||
* @param dir 项目目录
|
||||
* @param output 输出目录
|
||||
*/
|
||||
export declare const analysis: (dir: string, config: AnalysisOptions) => void;
|
||||
export {};
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.analysis = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const fs_1 = tslib_1.__importDefault(require("fs"));
|
||||
const path_1 = tslib_1.__importDefault(require("path"));
|
||||
/**
|
||||
* 分析项目中的模块
|
||||
* @param dir 项目目录
|
||||
* @param output 输出目录
|
||||
*/
|
||||
const analysis = (dir, config) => {
|
||||
const BuiltinModule = require("module");
|
||||
// 兼容一些模拟环境下的 module 构造函数
|
||||
const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule;
|
||||
// 保存原始的 _resolveFilename 方法
|
||||
const oldResolveFilename = Module._resolveFilename;
|
||||
const successImported = new Set();
|
||||
// 重写 _resolveFilename 方法
|
||||
Module._resolveFilename = function (request, // 模块请求路径
|
||||
parent, // 调用方模块
|
||||
isMain, // 是否是主模块
|
||||
rFoptions // 解析选项
|
||||
) {
|
||||
// 调用原始的 _resolveFilename 方法
|
||||
const filename = oldResolveFilename.call(this, request, parent, isMain, rFoptions);
|
||||
// 记录成功导入的模块
|
||||
successImported.add(filename);
|
||||
// 返回解析后的模块路径
|
||||
return filename;
|
||||
};
|
||||
const filterProjectModules = (modulePath) => {
|
||||
if (config.includeNodeModules) {
|
||||
return modulePath.includes(dir);
|
||||
}
|
||||
return modulePath.includes(dir) && !modulePath.includes('node_modules');
|
||||
};
|
||||
/**
|
||||
* 将所有的文件复制到指定目录,并保持目录结构
|
||||
* @param files 文件列表 string[]
|
||||
* @param dest 目标目录
|
||||
* @param root 原目录的根目录
|
||||
*/
|
||||
const copyFiles = (files, dest, root) => {
|
||||
files.forEach(file => {
|
||||
const relativePath = path_1.default.relative(root, file);
|
||||
const destPath = path_1.default.join(dest, relativePath);
|
||||
fs_1.default.mkdirSync(path_1.default.dirname(destPath), { recursive: true });
|
||||
fs_1.default.copyFileSync(file, destPath);
|
||||
});
|
||||
};
|
||||
const { watch } = require("./watch");
|
||||
watch(dir).then(s => {
|
||||
setTimeout(() => {
|
||||
console.log('shutting down...');
|
||||
s().then(() => {
|
||||
console.log('server stoped');
|
||||
// 把导入成功的输出
|
||||
const project = Array.from(successImported).filter(filterProjectModules);
|
||||
// 添加本地的scripts下的所有文件和package.json
|
||||
const scriptsDir = path_1.default.join(dir, 'scripts');
|
||||
const pkgPath = path_1.default.join(dir, 'package.json');
|
||||
const files = fs_1.default.readdirSync(scriptsDir);
|
||||
const scriptFiles = files.map(file => path_1.default.join(scriptsDir, file));
|
||||
project.push(pkgPath, ...scriptFiles);
|
||||
// 复制文件
|
||||
const dest = path_1.default.join(dir, config.outputDir);
|
||||
fs_1.default.mkdirSync(dest, { recursive: true });
|
||||
copyFiles(project, dest, dir);
|
||||
console.warn('分析结果以项目启动所需依赖为准,如果涉及动态导入,请设计routine,在项目启动时进行加载!');
|
||||
process.exit(0);
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
exports.analysis = analysis;
|
||||
|
|
@ -5,7 +5,6 @@ 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 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");
|
||||
|
|
@ -95,9 +94,160 @@ const getOverrideConfig = (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) => {
|
||||
// 获取调用堆栈信息
|
||||
|
|
@ -141,6 +291,51 @@ const watch = (projectPath, config) => {
|
|||
});
|
||||
};
|
||||
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;
|
||||
|
|
@ -170,22 +365,13 @@ const watch = (projectPath, config) => {
|
|||
// 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 (0, start_1.startup)(pwd, simpleConnector).then((shutdown) => {
|
||||
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 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 watcher = chokidar_1.default.watch(watchSourcePath, {
|
||||
persistent: true,
|
||||
ignored: (file) => file.endsWith(".tsx") ||
|
||||
|
|
@ -209,13 +395,14 @@ const watch = (projectPath, config) => {
|
|||
startWatching = true;
|
||||
});
|
||||
watcher.on("error", (error) => console.log(`Watcher error: ${error}`));
|
||||
let isProcessing = false;
|
||||
let processingQueue = [];
|
||||
const fileChangeHandler = async (path, type) => {
|
||||
// 判断一下是不是以下扩展名:ts
|
||||
if (!path.endsWith(".ts")) {
|
||||
// 如果是json文件,复制或者删除
|
||||
if (path.endsWith(".json")) {
|
||||
const targetPath = path.replace("src", "lib");
|
||||
// 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.`);
|
||||
|
|
@ -245,8 +432,8 @@ const watch = (projectPath, config) => {
|
|||
const modulePath = path_1.default.resolve(path);
|
||||
// 将src替换为lib
|
||||
const libPath = modulePath
|
||||
.replace("\\src\\", "\\lib\\")
|
||||
.replace(".ts", ".js");
|
||||
.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"))
|
||||
.replace(/\.ts$/, ".js");
|
||||
let compileOnly = false;
|
||||
if (!require.cache[libPath]) {
|
||||
// 如果是删除的话,直接尝试删除lib下的文件
|
||||
|
|
@ -260,8 +447,11 @@ const watch = (projectPath, config) => {
|
|||
console.warn(`File ${libPath} has been removed.`);
|
||||
return;
|
||||
}
|
||||
console.warn(`File ${libPath} is not in module cache, will compile only.`);
|
||||
compileOnly = true;
|
||||
// console.warn(
|
||||
// `File ${libPath} is not in module cache, will compile only.`
|
||||
// );
|
||||
// compileOnly = true;
|
||||
// 这里现在不需要仅编译了,因为在require的时候会自动编译ts文件
|
||||
}
|
||||
else {
|
||||
// 如果是删除,则需要发出警告,文件正在被进程使用
|
||||
|
|
@ -270,24 +460,9 @@ const watch = (projectPath, config) => {
|
|||
return;
|
||||
}
|
||||
}
|
||||
const program = typescript_1.default.createProgram({
|
||||
rootNames: [path],
|
||||
options,
|
||||
projectReferences,
|
||||
});
|
||||
const sourceFile = program.getSourceFile(path);
|
||||
// 是否有语法错误
|
||||
const diagnostics = typescript_1.default.getPreEmitDiagnostics(program, sourceFile);
|
||||
const { program, sourceFile, diagnostics } = createProgramAndSourceFile(path, options, projectReferences);
|
||||
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;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 只输出单个文件
|
||||
realConfig.lifecycle.onBeforeCompile();
|
||||
|
|
@ -304,40 +479,47 @@ const watch = (projectPath, config) => {
|
|||
if (compileOnly) {
|
||||
return;
|
||||
}
|
||||
await restart();
|
||||
// await restart(); // 只有在队列里面的最后一个文件编译完成后才会重启服务
|
||||
if (processingQueue.length === 1) {
|
||||
await restart();
|
||||
}
|
||||
else {
|
||||
console.log("Waiting for other operations to complete...");
|
||||
}
|
||||
}
|
||||
};
|
||||
const onChangeDebounced = (0, lodash_1.debounce)(async (path, type) => {
|
||||
if (isProcessing) {
|
||||
const onChangeDebounced = async (path, type) => {
|
||||
if (processingQueue.includes(path)) {
|
||||
console.log("Processing, please wait...");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isProcessing = true;
|
||||
processingQueue.push(path);
|
||||
await fileChangeHandler(path, type);
|
||||
}
|
||||
catch (e) {
|
||||
console.clear();
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
finally {
|
||||
isProcessing = false;
|
||||
processingQueue = processingQueue.filter((p) => p !== path);
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
watcher
|
||||
.on("add", (path) => {
|
||||
.on("add", async (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "add");
|
||||
await onChangeDebounced(path, "add");
|
||||
}
|
||||
})
|
||||
.on("change", (path) => {
|
||||
.on("change", async (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "change");
|
||||
await onChangeDebounced(path, "change");
|
||||
}
|
||||
})
|
||||
.on("unlink", (path) => {
|
||||
.on("unlink", async (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "remove");
|
||||
await onChangeDebounced(path, "remove");
|
||||
}
|
||||
});
|
||||
const dispose = async () => {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
|
|||
"run:android": "oak-cli run -p android",
|
||||
"server:init": "${serverInitScript}",
|
||||
"server:start": "${serverStartWatchScript}",
|
||||
"server:ana": "cross-env ENABLE_TRACE=true cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node --stack-size=65500 scripts/analysis.js",
|
||||
"postinstall": "npm run make:dep"
|
||||
},
|
||||
"keywords": [],
|
||||
|
|
@ -272,8 +273,7 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
|
|||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-manifest-plugin": "^4.0.2",
|
||||
"workbox-webpack-plugin": "^6.4.1",
|
||||
"chokidar": "^4.0.1",
|
||||
"module-alias": "^2.2.3"
|
||||
"chokidar": "^4.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
@ -305,16 +305,9 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
|
|||
},
|
||||
"resolutions": {
|
||||
"readable-stream": "3.6.2"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
`;
|
||||
// _moduleAliases用于lib内运行时的模块声明,重载之后require的路径还会保留@project,需要这样的方式来进行路径alias
|
||||
}
|
||||
function tsConfigJsonContent() {
|
||||
return `{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
type AnalysisOptions = {
|
||||
outputDir: string
|
||||
includeNodeModules: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析项目中的模块
|
||||
* @param dir 项目目录
|
||||
* @param output 输出目录
|
||||
*/
|
||||
export const analysis = (dir: string, config: AnalysisOptions) => {
|
||||
const BuiltinModule = require("module");
|
||||
// 兼容一些模拟环境下的 module 构造函数
|
||||
const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule;
|
||||
// 保存原始的 _resolveFilename 方法
|
||||
const oldResolveFilename = Module._resolveFilename;
|
||||
const successImported = new Set<string>();
|
||||
// 重写 _resolveFilename 方法
|
||||
Module._resolveFilename = function (
|
||||
request: string, // 模块请求路径
|
||||
parent: typeof Module, // 调用方模块
|
||||
isMain: boolean, // 是否是主模块
|
||||
rFoptions: object | undefined // 解析选项
|
||||
) {
|
||||
// 调用原始的 _resolveFilename 方法
|
||||
const filename = oldResolveFilename.call(this, request, parent, isMain, rFoptions);
|
||||
|
||||
// 记录成功导入的模块
|
||||
successImported.add(filename);
|
||||
|
||||
// 返回解析后的模块路径
|
||||
return filename;
|
||||
}
|
||||
|
||||
const filterProjectModules = (modulePath: string) => {
|
||||
if (config.includeNodeModules) {
|
||||
return modulePath.includes(dir)
|
||||
}
|
||||
return modulePath.includes(dir) && !modulePath.includes('node_modules');
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有的文件复制到指定目录,并保持目录结构
|
||||
* @param files 文件列表 string[]
|
||||
* @param dest 目标目录
|
||||
* @param root 原目录的根目录
|
||||
*/
|
||||
const copyFiles = (files: string[], dest: string, root: string) => {
|
||||
files.forEach(file => {
|
||||
const relativePath = path.relative(root, file)
|
||||
const destPath = path.join(dest, relativePath)
|
||||
fs.mkdirSync(path.dirname(destPath), { recursive: true })
|
||||
fs.copyFileSync(file, destPath)
|
||||
})
|
||||
}
|
||||
|
||||
const { watch } = require("./watch") as { watch: (pwd: string) => Promise<() => Promise<void>> }
|
||||
|
||||
watch(dir).then(s => {
|
||||
setTimeout(() => {
|
||||
console.log('shutting down...')
|
||||
s().then(() => {
|
||||
console.log('server stoped')
|
||||
// 把导入成功的输出
|
||||
const project = Array.from(successImported).filter(filterProjectModules)
|
||||
|
||||
// 添加本地的scripts下的所有文件和package.json
|
||||
const scriptsDir = path.join(dir, 'scripts')
|
||||
const pkgPath = path.join(dir, 'package.json')
|
||||
const files = fs.readdirSync(scriptsDir)
|
||||
const scriptFiles = files.map(file => path.join(scriptsDir, file))
|
||||
project.push(pkgPath, ...scriptFiles)
|
||||
|
||||
// 复制文件
|
||||
const dest = path.join(dir, config.outputDir)
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
copyFiles(project, dest, dir)
|
||||
console.warn('分析结果以项目启动所需依赖为准,如果涉及动态导入,请设计routine,在项目启动时进行加载!');
|
||||
process.exit(0)
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import chokidar from "chokidar";
|
||||
import ts from "typescript";
|
||||
import ts, { CompilerOptions, ProjectReference } from "typescript";
|
||||
import pathLib from "path";
|
||||
import { startup } from "./start";
|
||||
import dayjs from "dayjs";
|
||||
import fs from "fs";
|
||||
import { debounce } from "lodash";
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
declare const require: NodeRequire;
|
||||
declare const process: NodeJS.Process;
|
||||
|
|
@ -80,14 +79,14 @@ export type WatchConfig = {
|
|||
// 真实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]>;
|
||||
? 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]>;
|
||||
};
|
||||
|
||||
export type RealWatchConfig = DeepRequiredIfPresent<WatchConfig>;
|
||||
|
|
@ -116,8 +115,8 @@ const defaultConfig: RealWatchConfig = {
|
|||
level === "info"
|
||||
? infoStart
|
||||
: level === "warn"
|
||||
? warnStart
|
||||
: errorStart;
|
||||
? warnStart
|
||||
: errorStart;
|
||||
return [
|
||||
levelStart,
|
||||
getTime(),
|
||||
|
|
@ -178,6 +177,44 @@ const getOverrideConfig = (config?: WatchConfig): RealWatchConfig => {
|
|||
: defaultConfig;
|
||||
};
|
||||
|
||||
type AliasConfig = Record<string, string | string[]>;
|
||||
type ModuleType = import('module')
|
||||
|
||||
/**
|
||||
* 根据 alias 配置表将路径中的别名替换为真实路径
|
||||
* @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module"
|
||||
* @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组
|
||||
* @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径
|
||||
*/
|
||||
function 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
|
||||
|
|
@ -185,6 +222,182 @@ export const watch = (
|
|||
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) => v.replace("src", "lib"));
|
||||
});
|
||||
|
||||
// 输出真实的alias配置
|
||||
console.debug("[DEBUG] Running Alias config:", aliasConfig);
|
||||
|
||||
const createProgramAndSourceFile = (path: string, options: CompilerOptions, projectReferences: readonly ProjectReference[] | undefined) => {
|
||||
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 };
|
||||
}
|
||||
|
||||
// 这个函数用于解决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: 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, 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.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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
polyfillLoader();
|
||||
|
||||
//polyfill console.log 添加时间
|
||||
const polyfillConsole = (trace: boolean) => {
|
||||
// 获取调用堆栈信息
|
||||
|
|
@ -232,6 +445,62 @@ export const watch = (
|
|||
|
||||
realConfig.polyfill.console.enable && polyfillConsole(enableTrace);
|
||||
|
||||
// 这里注意要在require之前,因为require会触发编译
|
||||
const { startup } = require('./start') as {
|
||||
startup: (pwd: string, connector: any) => Promise<() => Promise<void>>;
|
||||
}
|
||||
|
||||
// 如果lib目录是空的,则直接编译所有的ts文件
|
||||
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, diagnostics } = createProgramAndSourceFile(tsFile, options, projectReferences);
|
||||
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(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: (() => Promise<void>) | undefined;
|
||||
|
|
@ -278,26 +547,7 @@ export const watch = (
|
|||
|
||||
console.log("Watching for changes in", watchSourcePath);
|
||||
|
||||
// 查找配置文件
|
||||
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 watcher = chokidar.watch(watchSourcePath, {
|
||||
persistent: true,
|
||||
|
|
@ -327,7 +577,7 @@ export const watch = (
|
|||
|
||||
watcher.on("error", (error) => console.log(`Watcher error: ${error}`));
|
||||
|
||||
let isProcessing = false;
|
||||
let processingQueue: string[] = [];
|
||||
const fileChangeHandler = async (
|
||||
path: string,
|
||||
type: "add" | "remove" | "change"
|
||||
|
|
@ -336,7 +586,8 @@ export const watch = (
|
|||
if (!path.endsWith(".ts")) {
|
||||
// 如果是json文件,复制或者删除
|
||||
if (path.endsWith(".json")) {
|
||||
const targetPath = path.replace("src", "lib");
|
||||
// const targetPath = path.replace("src", "lib"); // 这里直接替换不对,应该是拿到项目目录,把项目目录/src 换成项目目录/lib
|
||||
const targetPath = path.replace(pathLib.join(projectPath, "src"), pathLib.join(projectPath, "lib"));
|
||||
if (type === "remove") {
|
||||
fs.unlinkSync(targetPath);
|
||||
console.warn(`File ${targetPath} has been removed.`);
|
||||
|
|
@ -375,8 +626,8 @@ export const watch = (
|
|||
const modulePath = pathLib.resolve(path);
|
||||
// 将src替换为lib
|
||||
const libPath = modulePath
|
||||
.replace("\\src\\", "\\lib\\")
|
||||
.replace(".ts", ".js");
|
||||
.replace(pathLib.join(projectPath, "src"), pathLib.join(projectPath, "lib"))
|
||||
.replace(/\.ts$/, ".js");
|
||||
let compileOnly = false;
|
||||
if (!require.cache[libPath]) {
|
||||
// 如果是删除的话,直接尝试删除lib下的文件
|
||||
|
|
@ -389,10 +640,11 @@ export const watch = (
|
|||
console.warn(`File ${libPath} has been removed.`);
|
||||
return;
|
||||
}
|
||||
console.warn(
|
||||
`File ${libPath} is not in module cache, will compile only.`
|
||||
);
|
||||
compileOnly = true;
|
||||
// console.warn(
|
||||
// `File ${libPath} is not in module cache, will compile only.`
|
||||
// );
|
||||
// compileOnly = true;
|
||||
// 这里现在不需要仅编译了,因为在require的时候会自动编译ts文件
|
||||
} else {
|
||||
// 如果是删除,则需要发出警告,文件正在被进程使用
|
||||
if (type === "remove") {
|
||||
|
|
@ -400,34 +652,13 @@ export const watch = (
|
|||
return;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
const { program, sourceFile, diagnostics } = createProgramAndSourceFile(path, options, projectReferences);
|
||||
|
||||
if (diagnostics.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 只输出单个文件
|
||||
realConfig.lifecycle.onBeforeCompile();
|
||||
const emitResult = program.emit(sourceFile);
|
||||
|
|
@ -438,51 +669,54 @@ export const watch = (
|
|||
realConfig.lifecycle.onAfterCompile();
|
||||
} else {
|
||||
console.log(
|
||||
`Emit succeeded. ${
|
||||
compileOnly ? "" : "reload service......"
|
||||
`Emit succeeded. ${compileOnly ? "" : "reload service......"
|
||||
}`
|
||||
);
|
||||
realConfig.lifecycle.onAfterCompile();
|
||||
if (compileOnly) {
|
||||
return;
|
||||
}
|
||||
await restart();
|
||||
// await restart(); // 只有在队列里面的最后一个文件编译完成后才会重启服务
|
||||
if (processingQueue.length === 1) {
|
||||
await restart();
|
||||
} else {
|
||||
console.log("Waiting for other operations to complete...");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeDebounced = debounce(
|
||||
const onChangeDebounced =
|
||||
async (path: string, type: "add" | "remove" | "change") => {
|
||||
if (isProcessing) {
|
||||
if (processingQueue.includes(path)) {
|
||||
console.log("Processing, please wait...");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isProcessing = true;
|
||||
processingQueue.push(path);
|
||||
await fileChangeHandler(path, type);
|
||||
} catch (e) {
|
||||
console.clear();
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
processingQueue = processingQueue.filter((p) => p !== path);
|
||||
}
|
||||
},
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
watcher
|
||||
.on("add", (path) => {
|
||||
.on("add", async (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "add");
|
||||
await onChangeDebounced(path, "add");
|
||||
}
|
||||
})
|
||||
.on("change", (path) => {
|
||||
.on("change", async (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "change");
|
||||
await onChangeDebounced(path, "change");
|
||||
}
|
||||
})
|
||||
.on("unlink", (path) => {
|
||||
.on("unlink", async (path) => {
|
||||
if (startWatching) {
|
||||
onChangeDebounced(path, "remove");
|
||||
await onChangeDebounced(path, "remove");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export function packageJsonContent({
|
|||
"run:android": "oak-cli run -p android",
|
||||
"server:init": "${serverInitScript}",
|
||||
"server:start": "${serverStartWatchScript}",
|
||||
"server:ana": "cross-env ENABLE_TRACE=true cross-env NODE_ENV=development cross-env OAK_PLATFORM=server node --stack-size=65500 scripts/analysis.js",
|
||||
"postinstall": "npm run make:dep"
|
||||
},
|
||||
"keywords": [],
|
||||
|
|
@ -279,8 +280,7 @@ export function packageJsonContent({
|
|||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-manifest-plugin": "^4.0.2",
|
||||
"workbox-webpack-plugin": "^6.4.1",
|
||||
"chokidar": "^4.0.1",
|
||||
"module-alias": "^2.2.3"
|
||||
"chokidar": "^4.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
@ -312,16 +312,9 @@ export function packageJsonContent({
|
|||
},
|
||||
"resolutions": {
|
||||
"readable-stream": "3.6.2"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
`;
|
||||
// _moduleAliases用于lib内运行时的模块声明,重载之后require的路径还会保留@project,需要这样的方式来进行路径alias
|
||||
}
|
||||
|
||||
export function tsConfigJsonContent() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const { analysis } = require('@xuchangzju/oak-cli/lib/server/analysis');
|
||||
const pwd = process.cwd();
|
||||
|
||||
analysis(pwd, {
|
||||
outputDir: 'backend_gen',
|
||||
includeNodeModules: true,
|
||||
});
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
require('module-alias/register');
|
||||
const { startup } = require('@xuchangzju/oak-cli/lib/server/start');
|
||||
const simpleConnector = require('../lib/config/connector').default;
|
||||
const pwd = process.cwd();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
require('module-alias/register');
|
||||
const { watch } = require('@xuchangzju/oak-cli/lib/server/watch');
|
||||
const pwd = process.cwd();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue