Merge branch 'dev' of gitea.51mars.com:Oak-Team/oak-cli into dev

This commit is contained in:
Xu Chang 2025-10-07 18:10:20 +08:00
commit 2bbc326934
21 changed files with 1194 additions and 658 deletions

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = build;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@ -184,4 +185,3 @@ async function build(cmd) {
(0, tip_style_1.Error)(`${(0, tip_style_1.error)(`target could only be web or mp(wechatMp) or rn(native)`)}`);
}
}
exports.default = build;

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = run;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@ -48,4 +49,3 @@ async function run(options) {
process.exit(-1);
}
}
exports.default = run;

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.update = exports.create = void 0;
exports.create = create;
exports.update = update;
const tslib_1 = require("tslib");
const ts = tslib_1.__importStar(require("typescript"));
const fs_1 = require("fs");
@ -242,7 +243,7 @@ async function create(dirName, cmd) {
}
// 获取package.json内容
const packageJson = (0, template_1.packageJsonContent)({
name: DEFAULT_PROJECT_NAME,
name: DEFAULT_PROJECT_NAME, // 后面再统一rename
version,
description,
cliName: config_1.CLI_NAME,
@ -265,7 +266,6 @@ async function create(dirName, cmd) {
(0, tip_style_1.Error)((0, tip_style_1.error)(err));
}
}
exports.create = create;
async function update(dirName, subDirName, cmd) {
const isDev = cmd.dev ? true : false;
try {
@ -292,4 +292,3 @@ async function update(dirName, subDirName, cmd) {
console.error((0, tip_style_1.error)(err.message));
}
}
exports.update = update;

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateCompilerConfig = void 0;
exports.CreateCompilerConfig = CreateCompilerConfig;
/**
* 创建一个oak编译器配置
* @param raw 原始配置
@ -11,7 +11,6 @@ function CreateCompilerConfig(raw) {
// 在这里可以做配置的预处理
return raw;
}
exports.CreateCompilerConfig = CreateCompilerConfig;
/**
* 将compiler.js中的模块导出使用以下形式
* module.exports = CreateComilerConfig({})

View File

@ -1,5 +1,3 @@
/// <reference types="node" />
/// <reference types="node" />
import { PathLike } from 'fs';
import { checkFileExistsAndCreateType } from './enum';
/**

View File

@ -1,6 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkFileExistsAndCreate = exports.checkFileExists = exports.copyFolder = exports.readFile = exports.writeFile = exports.deleteFolderRecursive = exports.parseJsonFile = exports.parseJsonFiles = exports.readDirGetFile = exports.readDirPath = void 0;
exports.readDirPath = readDirPath;
exports.readDirGetFile = readDirGetFile;
exports.parseJsonFiles = parseJsonFiles;
exports.parseJsonFile = parseJsonFile;
exports.deleteFolderRecursive = deleteFolderRecursive;
exports.writeFile = writeFile;
exports.readFile = readFile;
exports.copyFolder = copyFolder;
exports.checkFileExists = checkFileExists;
exports.checkFileExistsAndCreate = checkFileExistsAndCreate;
const fs_1 = require("fs");
const path_1 = require("path");
const enum_1 = require("./enum");
@ -25,7 +34,6 @@ function readDirPath(entry) {
}
return pathList;
}
exports.readDirPath = readDirPath;
/**
* @name 读取指定目录的文件不进行深度遍历只获取根目录
* @export
@ -36,7 +44,6 @@ function readDirGetFile(entry) {
const dirInfo = (0, fs_1.readdirSync)(entry);
return dirInfo;
}
exports.readDirGetFile = readDirGetFile;
/**
* @name 解析json文件(数组)
* @export
@ -51,7 +58,6 @@ function parseJsonFiles(arr) {
}
return result;
}
exports.parseJsonFiles = parseJsonFiles;
/**
* @name 解析单个文件json
* @export
@ -67,7 +73,6 @@ function parseJsonFile(file) {
return;
}
}
exports.parseJsonFile = parseJsonFile;
/**
* @name 删除文件夹
* @export
@ -97,7 +102,6 @@ function deleteFolderRecursive(entry) {
// console.log("文件夹不存在");
}
}
exports.deleteFolderRecursive = deleteFolderRecursive;
;
function writeFile(path, data) {
try {
@ -108,7 +112,6 @@ function writeFile(path, data) {
(0, tip_style_1.Error)((0, tip_style_1.error)('文件写入失败'));
}
}
exports.writeFile = writeFile;
function readFile(path, options) {
try {
const data = (0, fs_1.readFileSync)(path, options);
@ -119,7 +122,6 @@ function readFile(path, options) {
(0, tip_style_1.Error)((0, tip_style_1.error)('文件读取失败'));
}
}
exports.readFile = readFile;
/**
* @name 拷贝文件夹
* @export
@ -181,7 +183,6 @@ function copyFolder(currentDir, targetDir, overwrite = false) {
throw new global.Error(`需要copy的文件夹不存在: ${currentDir}`);
}
}
exports.copyFolder = copyFolder;
/**
* @name 检测文件/文件夹是否存在
* @export
@ -191,7 +192,6 @@ exports.copyFolder = copyFolder;
function checkFileExists(path) {
return (0, fs_1.existsSync)(path);
}
exports.checkFileExists = checkFileExists;
/**
* @name 检测文件/文件夹是否存在不存在则创建
* @export
@ -217,4 +217,3 @@ function checkFileExistsAndCreate(path, data, type = enum_1.checkFileExistsAndCr
throw new global.Error(`${path} already exists!`);
}
}
exports.checkFileExistsAndCreate = checkFileExistsAndCreate;

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = make;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@ -21,4 +22,3 @@ async function make(cmd) {
process.exit(-1);
}
}
exports.default = make;

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = make;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@ -20,4 +21,3 @@ async function make() {
process.exit(-1);
}
}
exports.default = make;

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = make;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const localeBuilder_1 = tslib_1.__importDefault(require("oak-domain/lib/compiler/localeBuilder"));
@ -18,4 +19,3 @@ async function make(cmd) {
process.exit(-1);
}
}
exports.default = make;

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = make;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@ -30,4 +31,3 @@ async function make(cmd, watch) {
process.exit(-1);
}
}
exports.default = make;

View File

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.rename = exports.renameProject = void 0;
exports.renameProject = renameProject;
exports.rename = rename;
const path_1 = require("path");
const fs_1 = require("fs");
const editTemplate_1 = require("@react-native-community/cli/build/commands/init/editTemplate");
@ -44,7 +45,6 @@ async function renameProject(dir, name, title, placeholderName, placeholderTitle
process.chdir(cwd);
(0, tip_style_1.Success)(`${(0, tip_style_1.success)(`Change project name to ${(0, tip_style_1.primary)(name)}, project title to ${(0, tip_style_1.primary)(title)}`)}`);
}
exports.renameProject = renameProject;
async function rename(cmd) {
const { oldName, newName, oldTitle, newTitle } = cmd;
if (!oldName) {
@ -65,4 +65,3 @@ async function rename(cmd) {
}
renameProject(process.cwd(), newName, newTitle, oldName, oldTitle);
}
exports.rename = rename;

View File

@ -1,5 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = run;
const tslib_1 = require("tslib");
const tip_style_1 = require("./tip-style");
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
@ -59,4 +60,3 @@ async function run(options) {
process.exit(-1);
}
}
exports.default = run;

View File

@ -1,4 +1,3 @@
/// <reference path="../../src/typings/polyfill.d.ts" />
import { EntityDict } from 'oak-domain/lib/types';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initialize = void 0;
exports.initialize = initialize;
/// <reference path="../typings/polyfill.d.ts" />
const oak_backend_base_1 = require("oak-backend-base");
async function initialize(path, ifExists) {
@ -10,4 +10,3 @@ async function initialize(path, ifExists) {
await appLoader.unmount();
console.log('data initialized');
}
exports.initialize = initialize;

View File

@ -1,4 +1,3 @@
/// <reference path="../../src/typings/polyfill.d.ts" />
import './polyfill';
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
import { Connector, EntityDict } from 'oak-domain/lib/types';

View File

@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startup = void 0;
exports.startup = startup;
const tslib_1 = require("tslib");
/// <reference path="../typings/polyfill.d.ts" />
require("./polyfill");
@ -64,7 +64,7 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
}
: serverConfiguration.cors
? {
origin: serverConfiguration.cors.origin,
origin: serverConfiguration.cors.origin, //socket.io配置cors origin是支持数组和字符串
allowedHeaders: [
...corsHeaders.concat(connector.getCorsHeader()),
...(serverConfiguration.cors.headers || []),
@ -146,7 +146,7 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
if (!ui?.disable) {
(0, admin_ui_1.instrument)(io, {
auth: {
type: "basic",
type: "basic", // 使用基本认证,生产建议关闭或换成自定义 auth
username: ui?.username || "admin",
password: bcryptjs_1.default.hashSync(passwordForAdminUI, 10), // 必须使用 bcrypt 加密之后的密码
},
@ -368,4 +368,3 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
};
return shutdown;
}
exports.startup = startup;

39
lib/server/watch.d.ts vendored
View File

@ -1,4 +1,3 @@
/// <reference types="node" />
export type LogFormatterProp = {
level: "info" | "warn" | "error";
caller: NodeJS.CallSite | null;
@ -6,6 +5,7 @@ export type LogFormatterProp = {
};
export type LogFormatter = (props: LogFormatterProp) => any[];
export type WatchConfig = {
autoUpdateI18n?: boolean;
/**
* polyfill
*/
@ -36,34 +36,59 @@ export type WatchConfig = {
*
* @returns void
*/
onInit?: () => void;
onInit?: (config: RealWatchConfig) => void;
/**
*
* @returns void
*/
onServerStart?: () => void;
onServerStart?: (config: RealWatchConfig) => void;
/**
*
* @returns void
*/
onBeforeCompile?: () => void;
onBeforeCompile?: (config: RealWatchConfig) => void;
/**
*
* @returns void
*/
onAfterCompile?: () => void;
onAfterCompile?: (config: RealWatchConfig) => void;
/**
*
* @returns void
*/
onServerShutdown?: () => void;
onServerShutdown?: (config: RealWatchConfig) => void;
/**
*
* @returns void
*/
onDispose?: () => void;
onDispose?: (config: RealWatchConfig) => void;
};
};
export type FileChangeType = "add" | "remove" | "change";
export type FileChangeEvent = {
path: string;
type: FileChangeType;
timestamp: number;
};
export type CompileTask = {
id: string;
filePath: string;
changeType: FileChangeType;
timestamp: number;
};
export type CompileResult = {
taskId: string;
success: boolean;
filePath: string;
error?: string;
};
export type EventType = "file-changed" | "compile-task-added" | "compile-task-completed" | "compile-batch-started" | "compile-batch-completed" | "server-restart-needed" | "server-restarted";
export type EventHandler<T = any> = (data: T) => void | Promise<void>;
export type EventEmitter = {
on: <T>(event: EventType, handler: EventHandler<T>) => void;
emit: <T>(event: EventType, data: T) => Promise<void>;
off: <T>(event: EventType, handler: EventHandler<T>) => void;
};
type DeepRequiredIfPresent<T> = {
[P in keyof T]-?: NonNullable<T[P]> extends (...args: any) => any ? T[P] : 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]>;
};

View File

@ -8,7 +8,105 @@ 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 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,
@ -44,22 +142,22 @@ const defaultConfig = {
},
},
lifecycle: {
onInit: () => {
onInit: (config) => {
console.log("----> Watcher initialized.");
},
onServerStart: () => {
onServerStart: (config) => {
console.log("----> Server started.");
},
onBeforeCompile: () => {
onBeforeCompile: (config) => {
console.log("----> Compiling......");
},
onAfterCompile: () => {
onAfterCompile: (config) => {
console.log("----> Compile completed.");
},
onServerShutdown: () => {
onServerShutdown: (config) => {
console.log("----> Server shutdown.");
},
onDispose: () => {
onDispose: (config) => {
console.log("----> Watcher disposed.");
},
},
@ -94,13 +192,265 @@ const getOverrideConfig = (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......");
// 这里注意要在require之前因为require会触发编译
const { startup } = require('./start');
shutdown = await startup(pwd, simpleConnector).then((shutdown) => {
config.lifecycle.onServerStart(config);
return shutdown;
});
isRestarting = false;
await eventEmitter.emit("server-restarted", {});
};
const dispose = async () => {
if (shutdown) {
await shutdown();
}
};
return {
restart,
dispose,
isRestarting: () => isRestarting
};
};
// 创建编译器
const createCompiler = (projectPath, options, projectReferences, aliasConfig, 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;
// 判断文件类型
if (!filePath.endsWith(".ts")) {
// 处理非TypeScript文件 (如JSON文件)
if (filePath.endsWith(".json")) {
const targetPath = filePath.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 modulePath = path_1.default.resolve(filePath);
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);
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则返回原始路径
*/
function replaceAliasWithPath(path, aliasConfig) {
const replaceAliasWithPath = (path, aliasConfig) => {
for (const [alias, targets] of Object.entries(aliasConfig)) {
// If alias ends with "*", handle it as a dynamic alias.
if (alias.endsWith('*')) {
@ -126,7 +476,7 @@ function replaceAliasWithPath(path, aliasConfig) {
}
// If no alias matches, return the original path
return path;
}
};
const watch = (projectPath, config) => {
const realConfig = getOverrideConfig(config);
const enableTrace = !!process.env.ENABLE_TRACE;
@ -149,30 +499,7 @@ const watch = (projectPath, config) => {
});
// 输出真实的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文件
// 初始化polyfill
const polyfillLoader = () => {
const BuiltinModule = require("module");
// 模拟环境下的 module 构造函数
@ -186,7 +513,13 @@ const watch = (projectPath, config) => {
// 检查并编译 .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);
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");
@ -247,8 +580,6 @@ const watch = (projectPath, config) => {
}
};
};
polyfillLoader();
//polyfill console.log 添加时间
const polyfillConsole = (trace) => {
// 获取调用堆栈信息
const getCallerInfo = () => {
@ -282,7 +613,7 @@ const watch = (projectPath, config) => {
const levelStr = level;
const oldFunc = console[levelStr];
console[levelStr] = function (...args) {
oldFunc(...(defaultConfig.polyfill?.console?.formatter({
oldFunc(...(realConfig.polyfill?.console?.formatter({
level: levelStr,
caller: getCallerInfo(),
args,
@ -290,247 +621,122 @@ const watch = (projectPath, config) => {
};
});
};
// 初始化polyfill
polyfillLoader();
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")}`);
// 初始编译检查
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,
});
console.error(`文件存在语法错误,请检查修复后重试!`);
process.exit(1);
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.`);
}
// 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);
}
};
// 所有要编译的目录
// 其他涉及到的目录会在运行的时候自动编译这里主要处理的是动态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++;
// 创建事件系统
const eventEmitter = createEventEmitter();
// 创建各个组件
const compileQueue = createCompileQueue(eventEmitter);
const compiler = createCompiler(projectPath, options, projectReferences, aliasConfig, realConfig);
const serverManager = createServerManager(projectPath, eventEmitter, realConfig);
const fileWatcher = createFileWatcher(projectPath, eventEmitter);
// 设置编译器处理器
compileQueue.setTaskProcessor(compiler.compileTask);
// 设置事件监听器
eventEmitter.on("file-changed", (event) => {
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.-------------");
// 这里是copyupgradeI18n的
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);
});
}
});
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");
}
eventEmitter.on("server-restart-needed", async () => {
if (!serverManager.isRestarting()) {
console.log("----> Restarting server...");
await serverManager.restart();
}
});
const dispose = async () => {
if (shutdown) {
await shutdown();
}
await watcher.close();
realConfig.lifecycle.onDispose();
};
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);

View File

@ -1,6 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateCompilerJsContent = exports.oakConfigContentWithWeb = exports.oakConfigContentWithWeChatMp = exports.appJsonContentWithWeChatMp = exports.projectConfigContentWithWeChatMp = exports.tsConfigWebJsonContent = exports.tsConfigMpJsonContent = exports.tsConfigPathsJsonContent = exports.tsConfigBuildJsonContent = exports.tsConfigJsonContent = exports.packageJsonContent = void 0;
exports.packageJsonContent = packageJsonContent;
exports.tsConfigJsonContent = tsConfigJsonContent;
exports.tsConfigBuildJsonContent = tsConfigBuildJsonContent;
exports.tsConfigPathsJsonContent = tsConfigPathsJsonContent;
exports.tsConfigMpJsonContent = tsConfigMpJsonContent;
exports.tsConfigWebJsonContent = tsConfigWebJsonContent;
exports.projectConfigContentWithWeChatMp = projectConfigContentWithWeChatMp;
exports.appJsonContentWithWeChatMp = appJsonContentWithWeChatMp;
exports.oakConfigContentWithWeChatMp = oakConfigContentWithWeChatMp;
exports.oakConfigContentWithWeb = oakConfigContentWithWeb;
exports.updateCompilerJsContent = updateCompilerJsContent;
const tslib_1 = require("tslib");
const child_process_1 = require("child_process");
const fs_1 = require("fs");
@ -300,7 +310,6 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
}
`;
}
exports.packageJsonContent = packageJsonContent;
function tsConfigJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
@ -352,7 +361,6 @@ function tsConfigJsonContent() {
]
}`;
}
exports.tsConfigJsonContent = tsConfigJsonContent;
function tsConfigBuildJsonContent() {
return `{
"extends": "./tsconfig.build.paths.json",
@ -392,7 +400,6 @@ function tsConfigBuildJsonContent() {
]
}`;
}
exports.tsConfigBuildJsonContent = tsConfigBuildJsonContent;
function tsConfigPathsJsonContent(deps) {
const paths = {
"@project/*": [
@ -421,7 +428,6 @@ function tsConfigPathsJsonContent(deps) {
}
}, null, '\t');
}
exports.tsConfigPathsJsonContent = tsConfigPathsJsonContent;
function tsConfigMpJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
@ -469,7 +475,6 @@ function tsConfigMpJsonContent() {
]
}`;
}
exports.tsConfigMpJsonContent = tsConfigMpJsonContent;
function tsConfigWebJsonContent() {
return `{
"extends": "./tsconfig.paths.json",
@ -519,7 +524,6 @@ function tsConfigWebJsonContent() {
]
}`;
}
exports.tsConfigWebJsonContent = tsConfigWebJsonContent;
function projectConfigContentWithWeChatMp(oakConfigName, projectname, miniVersion) {
return `{
"description": "项目配置文件",
@ -595,7 +599,6 @@ function projectConfigContentWithWeChatMp(oakConfigName, projectname, miniVersio
}
}`;
}
exports.projectConfigContentWithWeChatMp = projectConfigContentWithWeChatMp;
function appJsonContentWithWeChatMp(isDev) {
const pages = [
'@project/pages/store/list/index',
@ -619,21 +622,18 @@ function appJsonContentWithWeChatMp(isDev) {
"sitemapLocation": "sitemap.json"
}`;
}
exports.appJsonContentWithWeChatMp = appJsonContentWithWeChatMp;
function oakConfigContentWithWeChatMp() {
return `{
"theme": {
}
}`;
}
exports.oakConfigContentWithWeChatMp = oakConfigContentWithWeChatMp;
function oakConfigContentWithWeb() {
return `{
"theme": {
}
}`;
}
exports.oakConfigContentWithWeb = oakConfigContentWithWeb;
function updateCompilerJsContent(directory, deps) {
const compilerJsPath = (0, path_1.join)(directory, 'configuration', 'compiler.js');
(0, assert_1.default)((0, fs_1.existsSync)(compilerJsPath));
@ -692,4 +692,3 @@ function updateCompilerJsContent(directory, deps) {
(0, fs_1.writeFileSync)(compilerJsPath, code);
}
}
exports.updateCompilerJsContent = updateCompilerJsContent;

View File

@ -1,6 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkNodeVersion = exports.randomString = exports.deWeight = exports.formatJsonByFile = exports.union = exports.intersect = exports.difference = exports.getStr = exports.findJson = void 0;
exports.findJson = findJson;
exports.getStr = getStr;
exports.difference = difference;
exports.intersect = intersect;
exports.union = union;
exports.formatJsonByFile = formatJsonByFile;
exports.deWeight = deWeight;
exports.randomString = randomString;
exports.checkNodeVersion = checkNodeVersion;
const tslib_1 = require("tslib");
const chalk_1 = tslib_1.__importDefault(require("chalk"));
/**
@ -20,7 +28,6 @@ function findJson(pathArr) {
}
return result;
}
exports.findJson = findJson;
/**
* @name 已知前后文取中间文本
* @export
@ -34,7 +41,6 @@ function getStr(str, start, end) {
let res = str.match(reg);
return res ? res[1] : null;
}
exports.getStr = getStr;
/**
* @name 差集
* @export
@ -46,7 +52,6 @@ exports.getStr = getStr;
function difference(current, target) {
return new Set([...target].filter(x => !current.has(x)));
}
exports.difference = difference;
/**
* @name 获取交集
* @export
@ -58,7 +63,6 @@ exports.difference = difference;
function intersect(current, target) {
return new Set([...target].filter(x => current.has(x)));
}
exports.intersect = intersect;
/**
* @name 获取并集
* @export
@ -70,7 +74,6 @@ exports.intersect = intersect;
function union(current, target) {
return new Set([...current, ...target]);
}
exports.union = union;
/**
* @name 格式化json
* @export
@ -81,7 +84,6 @@ exports.union = union;
function formatJsonByFile(data) {
return JSON.stringify(data, null, 2);
}
exports.formatJsonByFile = formatJsonByFile;
/**
* @name 数组对象去重
* @export
@ -98,7 +100,6 @@ function deWeight(arr, type) {
}
return new Set([...map.values()]);
}
exports.deWeight = deWeight;
/**
* @name 随机字符串
* @export
@ -114,7 +115,6 @@ function randomString(length) {
}
return result;
}
exports.randomString = randomString;
/**
* @name 检查当前nodejs运行时版本
* @export
@ -135,4 +135,3 @@ function checkNodeVersion() {
process.exit(-1);
}
}
exports.checkNodeVersion = checkNodeVersion;

File diff suppressed because it is too large Load Diff