Compare commits
51 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
3efd2fe9dd | |
|
|
eb39114fe9 | |
|
|
152d20efe4 | |
|
|
96d4337b59 | |
|
|
fc5f551cab | |
|
|
89fe961434 | |
|
|
3c32d41347 | |
|
|
f3e6ed4917 | |
|
|
13384ab772 | |
|
|
fb727c6b3c | |
|
|
35095a0219 | |
|
|
09d14be5e7 | |
|
|
22289f04d4 | |
|
|
52b3ed8d97 | |
|
|
3e0dba7172 | |
|
|
3781ed4629 | |
|
|
0e023f9a88 | |
|
|
2b0ffbffaf | |
|
|
da0bca5394 | |
|
|
4140595e0e | |
|
|
ef0d846f0a | |
|
|
1440147f3e | |
|
|
896d53561e | |
|
|
f0671efef6 | |
|
|
2d8690abb6 | |
|
|
23865e639d | |
|
|
1939358367 | |
|
|
00dab2c454 | |
|
|
e46c41812d | |
|
|
9a50ce03d3 | |
|
|
49b46c44a9 | |
|
|
af34753f48 | |
|
|
6bd83ff6e7 | |
|
|
97bc9b8042 | |
|
|
df8426d102 | |
|
|
04c53f956a | |
|
|
0b833b8fd6 | |
|
|
a3e2d584a8 | |
|
|
8c642d79c2 | |
|
|
b75a783aae | |
|
|
80e09e4f6f | |
|
|
523aac80c2 | |
|
|
be24825206 | |
|
|
893b1b04cb | |
|
|
cd7afc29a3 | |
|
|
a2cca9dd46 | |
|
|
2bbc326934 | |
|
|
747c40eabe | |
|
|
8571458e97 | |
|
|
61705bf9e1 | |
|
|
b3f56fb7b8 |
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.default = build;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
const tip_style_1 = require("./tip-style");
|
const tip_style_1 = require("./tip-style");
|
||||||
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
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)`)}`);
|
(0, tip_style_1.Error)(`${(0, tip_style_1.error)(`target could only be web or mp(wechatMp) or rn(native)`)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.default = build;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.default = run;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
const tip_style_1 = require("./tip-style");
|
const tip_style_1 = require("./tip-style");
|
||||||
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
||||||
|
|
@ -48,4 +49,3 @@ async function run(options) {
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.default = run;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.update = exports.create = void 0;
|
exports.create = create;
|
||||||
|
exports.update = update;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
const ts = tslib_1.__importStar(require("typescript"));
|
const ts = tslib_1.__importStar(require("typescript"));
|
||||||
const fs_1 = require("fs");
|
const fs_1 = require("fs");
|
||||||
|
|
@ -190,8 +191,22 @@ async function create(dirName, cmd) {
|
||||||
(0, file_handle_1.checkFileExistsAndCreate)(rootPath);
|
(0, file_handle_1.checkFileExistsAndCreate)(rootPath);
|
||||||
// 复制项目文件
|
// 复制项目文件
|
||||||
if (isModule) {
|
if (isModule) {
|
||||||
// 模块化的项目,只拷贝src和typings目录
|
// 模块化的项目,只拷贝 src 下的内容,但跳过 pages 目录;同时拷贝 typings
|
||||||
(0, file_handle_1.copyFolder)((0, path_1.join)(emptyTemplatePath, 'src'), (0, path_1.join)(rootPath, 'src'));
|
const templateSrc = (0, path_1.join)(emptyTemplatePath, 'src');
|
||||||
|
const destSrc = (0, path_1.join)(rootPath, 'src');
|
||||||
|
// 确保目标 src 目录存在
|
||||||
|
if (!(0, fs_1.existsSync)(destSrc)) {
|
||||||
|
(0, fs_1.mkdirSync)(destSrc, { recursive: true });
|
||||||
|
}
|
||||||
|
const entries = (0, fs_1.readdirSync)(templateSrc, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.name === 'pages') {
|
||||||
|
continue; // 模块模式下跳过 pages
|
||||||
|
}
|
||||||
|
const from = (0, path_1.join)(templateSrc, entry.name);
|
||||||
|
const to = (0, path_1.join)(destSrc, entry.name);
|
||||||
|
(0, file_handle_1.copyFolder)(from, to);
|
||||||
|
}
|
||||||
(0, file_handle_1.copyFolder)((0, path_1.join)(emptyTemplatePath, 'typings'), (0, path_1.join)(rootPath, 'typings'));
|
(0, file_handle_1.copyFolder)((0, path_1.join)(emptyTemplatePath, 'typings'), (0, path_1.join)(rootPath, 'typings'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -233,8 +248,16 @@ async function create(dirName, cmd) {
|
||||||
(0, file_handle_1.checkFileExistsAndCreate)(tsConfigMpJsonPath, tsConfigMpJson, enum_1.checkFileExistsAndCreateType.FILE);
|
(0, file_handle_1.checkFileExistsAndCreate)(tsConfigMpJsonPath, tsConfigMpJson, enum_1.checkFileExistsAndCreateType.FILE);
|
||||||
// 创建tsconfig.web.json
|
// 创建tsconfig.web.json
|
||||||
(0, file_handle_1.checkFileExistsAndCreate)(tsConfigWebJsonPath, tsConfigWebJson, enum_1.checkFileExistsAndCreateType.FILE);
|
(0, file_handle_1.checkFileExistsAndCreate)(tsConfigWebJsonPath, tsConfigWebJson, enum_1.checkFileExistsAndCreateType.FILE);
|
||||||
// 更新configuration/compiler.js
|
// 复制.gitignore
|
||||||
(0, template_1.updateCompilerJsContent)(rootPath, deps);
|
const gitignoreContent = (0, file_handle_1.readFile)((0, path_1.join)(__dirname, '..', 'template', '.gitignore'));
|
||||||
|
(0, file_handle_1.checkFileExistsAndCreate)((0, path_1.join)(rootPath, '.gitignore'), gitignoreContent, enum_1.checkFileExistsAndCreateType.FILE);
|
||||||
|
// 复制oak.config.json
|
||||||
|
const oakConfigContent = (0, file_handle_1.readFile)((0, path_1.join)(__dirname, '..', 'template', config_1.USER_CONFIG_FILE_NAME));
|
||||||
|
(0, file_handle_1.checkFileExistsAndCreate)((0, path_1.join)(rootPath, config_1.USER_CONFIG_FILE_NAME), oakConfigContent, enum_1.checkFileExistsAndCreateType.FILE);
|
||||||
|
// 更新configuration/compiler.js (仅在非模块化模式下)
|
||||||
|
if (!isModule) {
|
||||||
|
(0, template_1.updateCompilerJsContent)(rootPath, deps);
|
||||||
|
}
|
||||||
(0, tip_style_1.Success)(`${(0, tip_style_1.success)(`Successfully created project ${(0, tip_style_1.primary)(name)}, directory name is ${(0, tip_style_1.primary)(dirName)}`)}`);
|
(0, tip_style_1.Success)(`${(0, tip_style_1.success)(`Successfully created project ${(0, tip_style_1.primary)(name)}, directory name is ${(0, tip_style_1.primary)(dirName)}`)}`);
|
||||||
shelljs_1.default.cd(dirName);
|
shelljs_1.default.cd(dirName);
|
||||||
if (deps.length > 0) {
|
if (deps.length > 0) {
|
||||||
|
|
@ -242,7 +265,7 @@ async function create(dirName, cmd) {
|
||||||
}
|
}
|
||||||
// 获取package.json内容
|
// 获取package.json内容
|
||||||
const packageJson = (0, template_1.packageJsonContent)({
|
const packageJson = (0, template_1.packageJsonContent)({
|
||||||
name: DEFAULT_PROJECT_NAME,
|
name: DEFAULT_PROJECT_NAME, // 后面再统一rename
|
||||||
version,
|
version,
|
||||||
description,
|
description,
|
||||||
cliName: config_1.CLI_NAME,
|
cliName: config_1.CLI_NAME,
|
||||||
|
|
@ -253,8 +276,21 @@ async function create(dirName, cmd) {
|
||||||
});
|
});
|
||||||
// 创建package.json
|
// 创建package.json
|
||||||
(0, file_handle_1.checkFileExistsAndCreate)(packageJsonPath, packageJson, enum_1.checkFileExistsAndCreateType.FILE);
|
(0, file_handle_1.checkFileExistsAndCreate)(packageJsonPath, packageJson, enum_1.checkFileExistsAndCreateType.FILE);
|
||||||
(0, rename_1.renameProject)(rootPath, name, title, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_TITLE);
|
// 只在非模块化模式下重命名整个项目(包括web、wechatMp等)
|
||||||
if (example) {
|
if (!isModule) {
|
||||||
|
(0, rename_1.renameProject)(rootPath, name, title, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_TITLE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 模块化模式下只更新 package.json 的 name
|
||||||
|
const packageJsonFilePath = (0, path_1.join)(rootPath, 'package.json');
|
||||||
|
const packageJsonContent = (0, fs_1.readFileSync)(packageJsonFilePath, 'utf-8');
|
||||||
|
const packageJsonJson = JSON.parse(packageJsonContent);
|
||||||
|
packageJsonJson.name = name;
|
||||||
|
const newPackageJsonContent = JSON.stringify(packageJsonJson, undefined, 4);
|
||||||
|
(0, fs_1.writeFileSync)(packageJsonFilePath, newPackageJsonContent);
|
||||||
|
(0, tip_style_1.Success)(`${(0, tip_style_1.success)(`Change project name to ${(0, tip_style_1.primary)(name)}`)}`);
|
||||||
|
}
|
||||||
|
if (example && !isModule) {
|
||||||
// todo: copy template example files
|
// todo: copy template example files
|
||||||
(0, file_handle_1.copyFolder)(examplePath, rootPath, true);
|
(0, file_handle_1.copyFolder)(examplePath, rootPath, true);
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +301,6 @@ async function create(dirName, cmd) {
|
||||||
(0, tip_style_1.Error)((0, tip_style_1.error)(err));
|
(0, tip_style_1.Error)((0, tip_style_1.error)(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.create = create;
|
|
||||||
async function update(dirName, subDirName, cmd) {
|
async function update(dirName, subDirName, cmd) {
|
||||||
const isDev = cmd.dev ? true : false;
|
const isDev = cmd.dev ? true : false;
|
||||||
try {
|
try {
|
||||||
|
|
@ -292,4 +327,3 @@ async function update(dirName, subDirName, cmd) {
|
||||||
console.error((0, tip_style_1.error)(err.message));
|
console.error((0, tip_style_1.error)(err.message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.update = update;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.CreateCompilerConfig = void 0;
|
exports.CreateCompilerConfig = CreateCompilerConfig;
|
||||||
/**
|
/**
|
||||||
* 创建一个oak编译器配置
|
* 创建一个oak编译器配置
|
||||||
* @param raw 原始配置
|
* @param raw 原始配置
|
||||||
|
|
@ -11,7 +11,6 @@ function CreateCompilerConfig(raw) {
|
||||||
// 在这里可以做配置的预处理
|
// 在这里可以做配置的预处理
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
exports.CreateCompilerConfig = CreateCompilerConfig;
|
|
||||||
/**
|
/**
|
||||||
* 将compiler.js中的模块导出使用以下形式:
|
* 将compiler.js中的模块导出使用以下形式:
|
||||||
* module.exports = CreateComilerConfig({})
|
* module.exports = CreateComilerConfig({})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
/// <reference types="node" />
|
|
||||||
/// <reference types="node" />
|
|
||||||
import { PathLike } from 'fs';
|
import { PathLike } from 'fs';
|
||||||
import { checkFileExistsAndCreateType } from './enum';
|
import { checkFileExistsAndCreateType } from './enum';
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,7 +37,7 @@ export declare function writeFile(path: string | PathLike, data: any): void;
|
||||||
export declare function readFile(path: string | PathLike, options?: {
|
export declare function readFile(path: string | PathLike, options?: {
|
||||||
encoding?: null | undefined;
|
encoding?: null | undefined;
|
||||||
flag?: string | undefined;
|
flag?: string | undefined;
|
||||||
} | null): Buffer | undefined;
|
} | null): NonSharedBuffer | undefined;
|
||||||
/**
|
/**
|
||||||
* @name 拷贝文件夹
|
* @name 拷贝文件夹
|
||||||
* @export
|
* @export
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
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 fs_1 = require("fs");
|
||||||
const path_1 = require("path");
|
const path_1 = require("path");
|
||||||
const enum_1 = require("./enum");
|
const enum_1 = require("./enum");
|
||||||
|
|
@ -25,7 +34,6 @@ function readDirPath(entry) {
|
||||||
}
|
}
|
||||||
return pathList;
|
return pathList;
|
||||||
}
|
}
|
||||||
exports.readDirPath = readDirPath;
|
|
||||||
/**
|
/**
|
||||||
* @name 读取指定目录的文件(不进行深度遍历,只获取根目录)
|
* @name 读取指定目录的文件(不进行深度遍历,只获取根目录)
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -36,7 +44,6 @@ function readDirGetFile(entry) {
|
||||||
const dirInfo = (0, fs_1.readdirSync)(entry);
|
const dirInfo = (0, fs_1.readdirSync)(entry);
|
||||||
return dirInfo;
|
return dirInfo;
|
||||||
}
|
}
|
||||||
exports.readDirGetFile = readDirGetFile;
|
|
||||||
/**
|
/**
|
||||||
* @name 解析json文件(数组)
|
* @name 解析json文件(数组)
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -51,7 +58,6 @@ function parseJsonFiles(arr) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
exports.parseJsonFiles = parseJsonFiles;
|
|
||||||
/**
|
/**
|
||||||
* @name 解析单个文件json
|
* @name 解析单个文件json
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -67,7 +73,6 @@ function parseJsonFile(file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.parseJsonFile = parseJsonFile;
|
|
||||||
/**
|
/**
|
||||||
* @name 删除文件夹
|
* @name 删除文件夹
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -97,7 +102,6 @@ function deleteFolderRecursive(entry) {
|
||||||
// console.log("文件夹不存在");
|
// console.log("文件夹不存在");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.deleteFolderRecursive = deleteFolderRecursive;
|
|
||||||
;
|
;
|
||||||
function writeFile(path, data) {
|
function writeFile(path, data) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -108,7 +112,6 @@ function writeFile(path, data) {
|
||||||
(0, tip_style_1.Error)((0, tip_style_1.error)('文件写入失败'));
|
(0, tip_style_1.Error)((0, tip_style_1.error)('文件写入失败'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.writeFile = writeFile;
|
|
||||||
function readFile(path, options) {
|
function readFile(path, options) {
|
||||||
try {
|
try {
|
||||||
const data = (0, fs_1.readFileSync)(path, options);
|
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)('文件读取失败'));
|
(0, tip_style_1.Error)((0, tip_style_1.error)('文件读取失败'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.readFile = readFile;
|
|
||||||
/**
|
/**
|
||||||
* @name 拷贝文件夹
|
* @name 拷贝文件夹
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -147,9 +149,8 @@ function copyFolder(currentDir, targetDir, overwrite = false) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (file.isFile()) {
|
if (file.isFile()) {
|
||||||
const readStream = (0, fs_1.createReadStream)(copyCurrentFileInfo);
|
// 使用同步复制确保文件完全写入
|
||||||
const writeStream = (0, fs_1.createWriteStream)(copyTargetFileInfo);
|
(0, fs_1.copyFileSync)(copyCurrentFileInfo, copyTargetFileInfo);
|
||||||
readStream.pipe(writeStream);
|
|
||||||
// console.log(`复制文件: ${copyCurrentFileInfo} -> ${copyTargetFileInfo}`);
|
// console.log(`复制文件: ${copyCurrentFileInfo} -> ${copyTargetFileInfo}`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -181,7 +182,6 @@ function copyFolder(currentDir, targetDir, overwrite = false) {
|
||||||
throw new global.Error(`需要copy的文件夹不存在: ${currentDir}`);
|
throw new global.Error(`需要copy的文件夹不存在: ${currentDir}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.copyFolder = copyFolder;
|
|
||||||
/**
|
/**
|
||||||
* @name 检测文件/文件夹是否存在
|
* @name 检测文件/文件夹是否存在
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -191,7 +191,6 @@ exports.copyFolder = copyFolder;
|
||||||
function checkFileExists(path) {
|
function checkFileExists(path) {
|
||||||
return (0, fs_1.existsSync)(path);
|
return (0, fs_1.existsSync)(path);
|
||||||
}
|
}
|
||||||
exports.checkFileExists = checkFileExists;
|
|
||||||
/**
|
/**
|
||||||
* @name 检测文件/文件夹是否存在,不存在则创建
|
* @name 检测文件/文件夹是否存在,不存在则创建
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -217,4 +216,3 @@ function checkFileExistsAndCreate(path, data, type = enum_1.checkFileExistsAndCr
|
||||||
throw new global.Error(`${path} already exists!`);
|
throw new global.Error(`${path} already exists!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.checkFileExistsAndCreate = checkFileExistsAndCreate;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.default = make;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
const tip_style_1 = require("./tip-style");
|
const tip_style_1 = require("./tip-style");
|
||||||
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
||||||
|
|
@ -21,4 +22,3 @@ async function make(cmd) {
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.default = make;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.default = make;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
const tip_style_1 = require("./tip-style");
|
const tip_style_1 = require("./tip-style");
|
||||||
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
||||||
|
|
@ -20,4 +21,3 @@ async function make() {
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.default = make;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.default = make;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
const tip_style_1 = require("./tip-style");
|
const tip_style_1 = require("./tip-style");
|
||||||
const localeBuilder_1 = tslib_1.__importDefault(require("oak-domain/lib/compiler/localeBuilder"));
|
const localeBuilder_1 = tslib_1.__importDefault(require("oak-domain/lib/compiler/localeBuilder"));
|
||||||
|
|
@ -18,4 +19,3 @@ async function make(cmd) {
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.default = make;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.default = make;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
const tip_style_1 = require("./tip-style");
|
const tip_style_1 = require("./tip-style");
|
||||||
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
||||||
|
|
@ -30,4 +31,3 @@ async function make(cmd, watch) {
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.default = make;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.rename = exports.renameProject = void 0;
|
exports.renameProject = renameProject;
|
||||||
|
exports.rename = rename;
|
||||||
const path_1 = require("path");
|
const path_1 = require("path");
|
||||||
const fs_1 = require("fs");
|
const fs_1 = require("fs");
|
||||||
const editTemplate_1 = require("@react-native-community/cli/build/commands/init/editTemplate");
|
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);
|
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)}`)}`);
|
(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) {
|
async function rename(cmd) {
|
||||||
const { oldName, newName, oldTitle, newTitle } = cmd;
|
const { oldName, newName, oldTitle, newTitle } = cmd;
|
||||||
if (!oldName) {
|
if (!oldName) {
|
||||||
|
|
@ -65,4 +65,3 @@ async function rename(cmd) {
|
||||||
}
|
}
|
||||||
renameProject(process.cwd(), newName, newTitle, oldName, oldTitle);
|
renameProject(process.cwd(), newName, newTitle, oldName, oldTitle);
|
||||||
}
|
}
|
||||||
exports.rename = rename;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.default = run;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
const tip_style_1 = require("./tip-style");
|
const tip_style_1 = require("./tip-style");
|
||||||
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
const cross_spawn_1 = tslib_1.__importDefault(require("cross-spawn"));
|
||||||
|
|
@ -59,4 +60,3 @@ async function run(options) {
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.default = run;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
/// <reference path="../../src/typings/polyfill.d.ts" />
|
|
||||||
import { EntityDict } from 'oak-domain/lib/types';
|
import { EntityDict } from 'oak-domain/lib/types';
|
||||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
||||||
export declare function initialize<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>>(path: string): Promise<void>;
|
export declare function initialize<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>>(path: string, ifExists?: 'drop' | 'omit' | 'dropIfNotStatic'): Promise<void>;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.initialize = void 0;
|
exports.initialize = initialize;
|
||||||
/// <reference path="../typings/polyfill.d.ts" />
|
/// <reference path="../typings/polyfill.d.ts" />
|
||||||
const oak_backend_base_1 = require("oak-backend-base");
|
const oak_backend_base_1 = require("oak-backend-base");
|
||||||
async function initialize(path) {
|
async function initialize(path, ifExists) {
|
||||||
const appLoader = new oak_backend_base_1.AppLoader(path);
|
const appLoader = new oak_backend_base_1.AppLoader(path);
|
||||||
await appLoader.mount(true);
|
await appLoader.mount(true);
|
||||||
await appLoader.initialize();
|
await appLoader.initialize(ifExists);
|
||||||
await appLoader.unmount();
|
await appLoader.unmount();
|
||||||
console.log('data initialized');
|
console.log('data initialized');
|
||||||
}
|
}
|
||||||
exports.initialize = initialize;
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
export type GenerateIdOption = {
|
export type GenerateIdOption = {
|
||||||
shuffle?: boolean;
|
shuffle?: boolean;
|
||||||
};
|
};
|
||||||
|
export type LogFormatterProp = {
|
||||||
|
level: "info" | "warn" | "error";
|
||||||
|
caller: NodeJS.CallSite | null;
|
||||||
|
args: any[];
|
||||||
|
};
|
||||||
|
export type LogFormatter = (props: LogFormatterProp) => any[];
|
||||||
|
export declare const polyfillConsole: (id: string, trace: boolean, formatter?: LogFormatter) => void;
|
||||||
|
export declare const removePolyfill: (id: string) => void;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.removePolyfill = exports.polyfillConsole = void 0;
|
||||||
const uuid_1 = require("uuid");
|
const uuid_1 = require("uuid");
|
||||||
async function generateNewId(option) {
|
async function generateNewId(option) {
|
||||||
if (option?.shuffle && process.env.NODE_ENV === 'development') {
|
if (option?.shuffle && process.env.NODE_ENV === 'development') {
|
||||||
|
|
@ -10,3 +11,97 @@ async function generateNewId(option) {
|
||||||
Object.assign(global, {
|
Object.assign(global, {
|
||||||
generateNewId,
|
generateNewId,
|
||||||
});
|
});
|
||||||
|
// 存储所有的 polyfill 配置栈
|
||||||
|
const polyfillStack = [];
|
||||||
|
const originalConsoleMethods = {};
|
||||||
|
// 获取调用堆栈信息的工厂函数
|
||||||
|
const createGetCallerInfo = (trace) => () => {
|
||||||
|
if (!trace) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const originalFunc = Error.prepareStackTrace;
|
||||||
|
let callerInfo = null;
|
||||||
|
try {
|
||||||
|
const err = new Error();
|
||||||
|
Error.prepareStackTrace = (err, stack) => stack;
|
||||||
|
const stack = err.stack;
|
||||||
|
const currentFile = stack[0].getFileName();
|
||||||
|
for (let i = 1; i < stack.length; i++) {
|
||||||
|
const callSite = stack[i];
|
||||||
|
if (currentFile !== callSite.getFileName()) {
|
||||||
|
callerInfo = callSite;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
Error.prepareStackTrace = originalFunc;
|
||||||
|
return callerInfo;
|
||||||
|
};
|
||||||
|
const polyfillConsole = (id, trace, formatter) => {
|
||||||
|
// 检查是否已经添加过相同 id 的 polyfill
|
||||||
|
if (polyfillStack.some(item => item.id === id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 第一次调用时保存原始方法
|
||||||
|
if (polyfillStack.length === 0) {
|
||||||
|
originalConsoleMethods.info = console.info;
|
||||||
|
originalConsoleMethods.warn = console.warn;
|
||||||
|
originalConsoleMethods.error = console.error;
|
||||||
|
}
|
||||||
|
// 添加新的 polyfill 到栈顶
|
||||||
|
polyfillStack.push({ id, trace, formatter });
|
||||||
|
// 重新设置 console 方法
|
||||||
|
["info", "warn", "error"].forEach((level) => {
|
||||||
|
const levelStr = level;
|
||||||
|
const originalFunc = originalConsoleMethods[levelStr];
|
||||||
|
console[levelStr] = function (...args) {
|
||||||
|
let processedArgs = args;
|
||||||
|
// 从后往前执行所有 formatter
|
||||||
|
for (let i = polyfillStack.length - 1; i >= 0; i--) {
|
||||||
|
const item = polyfillStack[i];
|
||||||
|
if (item.formatter) {
|
||||||
|
const getCallerInfo = createGetCallerInfo(item.trace);
|
||||||
|
processedArgs = item.formatter({
|
||||||
|
level: levelStr,
|
||||||
|
caller: getCallerInfo(),
|
||||||
|
args: processedArgs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
originalFunc(...processedArgs);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.info(`new console polyfill added: ${id}`);
|
||||||
|
};
|
||||||
|
exports.polyfillConsole = polyfillConsole;
|
||||||
|
// 可选:提供移除 polyfill 的功能
|
||||||
|
const removePolyfill = (id) => {
|
||||||
|
const index = polyfillStack.findIndex(item => item.id === id);
|
||||||
|
if (index === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
polyfillStack.splice(index, 1);
|
||||||
|
// 如果栈为空,恢复原始方法
|
||||||
|
if (polyfillStack.length === 0) {
|
||||||
|
console.info = originalConsoleMethods.info;
|
||||||
|
console.warn = originalConsoleMethods.warn;
|
||||||
|
console.error = originalConsoleMethods.error;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 否则重新应用所有 polyfill
|
||||||
|
const tempStack = [...polyfillStack];
|
||||||
|
polyfillStack.length = 0;
|
||||||
|
Object.keys(originalConsoleMethods).forEach(level => {
|
||||||
|
const levelStr = level;
|
||||||
|
console[levelStr] = originalConsoleMethods[levelStr];
|
||||||
|
});
|
||||||
|
tempStack.forEach(item => {
|
||||||
|
polyfillStack.length = 0; // 清空以便重新添加
|
||||||
|
(0, exports.polyfillConsole)(item.id, item.trace, item.formatter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.removePolyfill = removePolyfill;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
/// <reference path="../../src/typings/polyfill.d.ts" />
|
|
||||||
import './polyfill';
|
import './polyfill';
|
||||||
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
||||||
import { Connector, EntityDict } from 'oak-domain/lib/types';
|
import { Connector, EntityDict } from 'oak-domain/lib/types';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.startup = void 0;
|
exports.startup = startup;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
/// <reference path="../typings/polyfill.d.ts" />
|
/// <reference path="../typings/polyfill.d.ts" />
|
||||||
require("./polyfill");
|
require("./polyfill");
|
||||||
|
|
@ -9,7 +9,7 @@ const path_1 = require("path");
|
||||||
const koa_1 = tslib_1.__importDefault(require("koa"));
|
const koa_1 = tslib_1.__importDefault(require("koa"));
|
||||||
const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
|
const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
|
||||||
const koa_body_1 = tslib_1.__importDefault(require("koa-body"));
|
const koa_body_1 = tslib_1.__importDefault(require("koa-body"));
|
||||||
const koa_logger_1 = tslib_1.__importDefault(require("koa-logger"));
|
// import logger from 'koa-logger';
|
||||||
const oak_backend_base_1 = require("oak-backend-base");
|
const oak_backend_base_1 = require("oak-backend-base");
|
||||||
const types_1 = require("oak-domain/lib/types");
|
const types_1 = require("oak-domain/lib/types");
|
||||||
const cluster_adapter_1 = require("@socket.io/cluster-adapter");
|
const cluster_adapter_1 = require("@socket.io/cluster-adapter");
|
||||||
|
|
@ -38,6 +38,17 @@ function concat(...paths) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
|
// let errorHandler: InternalErrorHandler<ED, Cxt> | undefined = undefined;
|
||||||
|
// try {
|
||||||
|
// errorHandler = require(join(
|
||||||
|
// path,
|
||||||
|
// 'lib',
|
||||||
|
// 'configuration',
|
||||||
|
// 'exception'
|
||||||
|
// )).default;
|
||||||
|
// } catch (err) {
|
||||||
|
// // 不存在exception配置
|
||||||
|
// }
|
||||||
const serverConfiguration = require((0, path_1.join)(path, 'lib', 'configuration', 'server')).default;
|
const serverConfiguration = require((0, path_1.join)(path, 'lib', 'configuration', 'server')).default;
|
||||||
// 拿到package.json,用作项目的唯一标识,否则无法区分不同项目的Redis+socketIO连接
|
// 拿到package.json,用作项目的唯一标识,否则无法区分不同项目的Redis+socketIO连接
|
||||||
const packageJson = require((0, path_1.join)(path, 'package.json'));
|
const packageJson = require((0, path_1.join)(path, 'package.json'));
|
||||||
|
|
@ -51,7 +62,7 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
const corsMethods = ['PUT', 'POST', 'GET', 'DELETE', 'OPTIONS'];
|
const corsMethods = ['PUT', 'POST', 'GET', 'DELETE', 'OPTIONS'];
|
||||||
const koa = new koa_1.default();
|
const koa = new koa_1.default();
|
||||||
// 使用 koa-logger 中间件打印日志
|
// 使用 koa-logger 中间件打印日志
|
||||||
koa.use((0, koa_logger_1.default)());
|
// koa.use(logger());
|
||||||
// socket
|
// socket
|
||||||
const httpServer = (0, http_1.createServer)(koa.callback());
|
const httpServer = (0, http_1.createServer)(koa.callback());
|
||||||
const socketPath = connector.getSocketPath();
|
const socketPath = connector.getSocketPath();
|
||||||
|
|
@ -64,7 +75,7 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
}
|
}
|
||||||
: serverConfiguration.cors
|
: serverConfiguration.cors
|
||||||
? {
|
? {
|
||||||
origin: serverConfiguration.cors.origin,
|
origin: serverConfiguration.cors.origin, //socket.io配置cors origin是支持数组和字符串
|
||||||
allowedHeaders: [
|
allowedHeaders: [
|
||||||
...corsHeaders.concat(connector.getCorsHeader()),
|
...corsHeaders.concat(connector.getCorsHeader()),
|
||||||
...(serverConfiguration.cors.headers || []),
|
...(serverConfiguration.cors.headers || []),
|
||||||
|
|
@ -146,7 +157,7 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
if (!ui?.disable) {
|
if (!ui?.disable) {
|
||||||
(0, admin_ui_1.instrument)(io, {
|
(0, admin_ui_1.instrument)(io, {
|
||||||
auth: {
|
auth: {
|
||||||
type: "basic",
|
type: "basic", // 使用基本认证,生产建议关闭或换成自定义 auth
|
||||||
username: ui?.username || "admin",
|
username: ui?.username || "admin",
|
||||||
password: bcryptjs_1.default.hashSync(passwordForAdminUI, 10), // 必须使用 bcrypt 加密之后的密码
|
password: bcryptjs_1.default.hashSync(passwordForAdminUI, 10), // 必须使用 bcrypt 加密之后的密码
|
||||||
},
|
},
|
||||||
|
|
@ -161,9 +172,25 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
if (routine) {
|
if (routine) {
|
||||||
// 如果传入了routine,执行完成后就结束
|
// 如果传入了routine,执行完成后就结束
|
||||||
const result = await appLoader.execRoutine(routine);
|
const result = await appLoader.execRoutine(routine);
|
||||||
await appLoader.unmount();
|
// await appLoader.unmount(); // 不卸载,在进程退出时会自动卸载
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
// if (errorHandler && typeof errorHandler === 'function') {
|
||||||
|
// // polyfillConsole("startup", true, (props) => {
|
||||||
|
// // if (props.level === "error") {
|
||||||
|
// // appLoader.execRoutine(async (ctx) => {
|
||||||
|
// // await errorHandler(props.caller, props.args, ctx);
|
||||||
|
// // }).catch((err) => {
|
||||||
|
// // console.warn('执行全局错误处理失败:', err);
|
||||||
|
// // });
|
||||||
|
// // }
|
||||||
|
// // return props.args;
|
||||||
|
// // });
|
||||||
|
// // appLoader.registerInternalErrorHandler(async (ctx, type, msg, err) => {
|
||||||
|
// // await errorHandler(ctx, type, msg, err);
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
appLoader.regAllExceptionHandler();
|
||||||
// 否则启动服务器模式
|
// 否则启动服务器模式
|
||||||
koa.use(async (ctx, next) => {
|
koa.use(async (ctx, next) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -172,18 +199,39 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
const { request } = ctx;
|
const { request } = ctx;
|
||||||
const exception = err instanceof types_1.OakException
|
const exception = (0, types_1.isOakException)(err)
|
||||||
? err
|
? err
|
||||||
: new types_1.OakException(serverConfiguration?.internalExceptionMask ||
|
: new types_1.OakException(serverConfiguration?.internalExceptionMask ||
|
||||||
ExceptionMask);
|
ExceptionMask);
|
||||||
const { body } = connector.serializeException(exception, request.headers, request.body);
|
const { body, headers } = connector.serializeException(exception, request.headers, request.body);
|
||||||
ctx.response.body = body;
|
ctx.response.body = body;
|
||||||
|
// headers 要拼上
|
||||||
|
Object.keys(headers || {}).forEach(key => {
|
||||||
|
ctx.set(key, headers?.[key]);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
koa.use((0, koa_body_1.default)(Object.assign({
|
koa.use((0, koa_body_1.default)(Object.assign({
|
||||||
multipart: true,
|
multipart: true,
|
||||||
}, serverConfiguration.koaBody)));
|
}, serverConfiguration.koaBody)));
|
||||||
|
// 注册自定义中间件
|
||||||
|
if (serverConfiguration.middleware) {
|
||||||
|
if (Array.isArray(serverConfiguration.middleware)) {
|
||||||
|
serverConfiguration.middleware.forEach((mw) => {
|
||||||
|
koa.use(mw);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (typeof serverConfiguration.middleware === 'function') {
|
||||||
|
const mws = serverConfiguration.middleware(koa);
|
||||||
|
if (!Array.isArray(mws)) {
|
||||||
|
throw new Error('middleware 配置函数必须返回 Koa.Middleware 数组');
|
||||||
|
}
|
||||||
|
mws.forEach((mw) => {
|
||||||
|
koa.use(mw);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
const router = new koa_router_1.default();
|
const router = new koa_router_1.default();
|
||||||
// 如果是开发环境,允许options
|
// 如果是开发环境,允许options
|
||||||
if (['development', 'staging'].includes(process.env.NODE_ENV)) {
|
if (['development', 'staging'].includes(process.env.NODE_ENV)) {
|
||||||
|
|
@ -191,6 +239,10 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
ctx.set('Access-Control-Allow-Origin', '*');
|
ctx.set('Access-Control-Allow-Origin', '*');
|
||||||
ctx.set('Access-Control-Allow-Headers', corsHeaders.concat(connector.getCorsHeader()));
|
ctx.set('Access-Control-Allow-Headers', corsHeaders.concat(connector.getCorsHeader()));
|
||||||
ctx.set('Access-Control-Allow-Methods', corsMethods);
|
ctx.set('Access-Control-Allow-Methods', corsMethods);
|
||||||
|
if (connector.getCorsExposeHeaders) {
|
||||||
|
const exposeHeaders = connector.getCorsExposeHeaders();
|
||||||
|
ctx.set('Access-Control-Expose-Headers', exposeHeaders);
|
||||||
|
}
|
||||||
if (ctx.method == 'OPTIONS') {
|
if (ctx.method == 'OPTIONS') {
|
||||||
ctx.body = 200;
|
ctx.body = 200;
|
||||||
}
|
}
|
||||||
|
|
@ -224,12 +276,39 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const connectorCustomAspects = connector.registerCustomAspect ? connector.registerCustomAspect() : null;
|
||||||
router.post(connector.getRouter(), async (ctx) => {
|
router.post(connector.getRouter(), async (ctx) => {
|
||||||
const { request } = ctx;
|
const { request } = ctx;
|
||||||
const { contextString, aspectName, data } = connector.parseRequest(request.headers, request.body, request.files);
|
const { contextString, aspectName, data } = await connector.parseRequest(request.headers, request.body, request.files);
|
||||||
const { result, opRecords, message } = await appLoader.execAspect(aspectName, request.headers, contextString, data);
|
let result;
|
||||||
|
let opRecords = [];
|
||||||
|
let message = undefined;
|
||||||
|
if (connectorCustomAspects &&
|
||||||
|
connectorCustomAspects.findIndex(a => a.name === aspectName) >= 0) {
|
||||||
|
// 自定义aspect处理
|
||||||
|
console.log(`调用Connector自定义Aspect: ${aspectName}`);
|
||||||
|
const aspect = connectorCustomAspects.find(a => a.name === aspectName);
|
||||||
|
const res = await aspect.handler({
|
||||||
|
headers: request.headers,
|
||||||
|
contextString,
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
result = res.result;
|
||||||
|
opRecords = res.opRecords || [];
|
||||||
|
message = res.message;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const res = await appLoader.execAspect(aspectName, request.headers, contextString, data);
|
||||||
|
result = res.result;
|
||||||
|
opRecords = res.opRecords || [];
|
||||||
|
message = res.message;
|
||||||
|
}
|
||||||
const { body, headers } = await connector.serializeResult(result, opRecords, request.headers, request.body, message);
|
const { body, headers } = await connector.serializeResult(result, opRecords, request.headers, request.body, message);
|
||||||
ctx.response.body = body;
|
ctx.response.body = body;
|
||||||
|
// headers 要拼上
|
||||||
|
Object.keys(headers || {}).forEach(key => {
|
||||||
|
ctx.set(key, headers?.[key]);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
// 桥接访问外部资源的入口
|
// 桥接访问外部资源的入口
|
||||||
|
|
@ -357,15 +436,63 @@ async function startup(path, connector, omitWatchers, omitTimers, routine) {
|
||||||
if (!omitTimers) {
|
if (!omitTimers) {
|
||||||
appLoader.startTimers();
|
appLoader.startTimers();
|
||||||
}
|
}
|
||||||
process.on('SIGINT', async () => {
|
let isShutingdown = false;
|
||||||
await appLoader.unmount();
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
const shutdown = async () => {
|
const shutdown = async () => {
|
||||||
await httpServer.close();
|
if (isShutingdown) {
|
||||||
await koa.removeAllListeners();
|
return;
|
||||||
await appLoader.unmount();
|
}
|
||||||
|
isShutingdown = true;
|
||||||
|
console.log('服务器正在关闭中...');
|
||||||
|
try {
|
||||||
|
await httpServer.close();
|
||||||
|
await koa.removeAllListeners();
|
||||||
|
await appLoader.execStopRoutines();
|
||||||
|
await appLoader.unmount();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error('关闭服务器时出错:', err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
// 处理优雅关闭的统一入口
|
||||||
|
let shutdownStarted = false;
|
||||||
|
const handleShutdown = async (signal) => {
|
||||||
|
// 防止重复处理
|
||||||
|
if (shutdownStarted) {
|
||||||
|
console.warn('关闭流程已启动,忽略此信号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shutdownStarted = true;
|
||||||
|
console.log(`\n收到 ${signal} 信号,准备关闭服务器...`);
|
||||||
|
// 移除所有信号处理器,防止重复触发
|
||||||
|
process.removeAllListeners('SIGINT');
|
||||||
|
process.removeAllListeners('SIGTERM');
|
||||||
|
process.removeAllListeners('SIGQUIT');
|
||||||
|
process.removeAllListeners('SIGHUP');
|
||||||
|
try {
|
||||||
|
await shutdown();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error('关闭过程出错:', err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 监听终止信号进行优雅关闭
|
||||||
|
// SIGINT - Ctrl+C 发送的中断信号
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
handleShutdown('SIGINT');
|
||||||
|
});
|
||||||
|
// SIGTERM - 系统/容器管理器发送的终止信号(Docker、K8s、PM2等)
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
handleShutdown('SIGTERM');
|
||||||
|
});
|
||||||
|
// SIGQUIT - Ctrl+\ 发送的退出信号
|
||||||
|
process.on('SIGQUIT', () => {
|
||||||
|
handleShutdown('SIGQUIT');
|
||||||
|
});
|
||||||
|
// SIGHUP - 终端关闭时发送(可选)
|
||||||
|
process.on('SIGHUP', () => {
|
||||||
|
handleShutdown('SIGHUP');
|
||||||
|
});
|
||||||
return shutdown;
|
return shutdown;
|
||||||
}
|
}
|
||||||
exports.startup = startup;
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
type TimerType = 'timeout' | 'interval' | 'immediate';
|
||||||
|
interface TimerRecord {
|
||||||
|
id: NodeJS.Timeout | NodeJS.Immediate;
|
||||||
|
type: TimerType;
|
||||||
|
createdAt: number;
|
||||||
|
}
|
||||||
|
declare class GlobalTimerManager {
|
||||||
|
private timers;
|
||||||
|
private isHooked;
|
||||||
|
private readonly original;
|
||||||
|
/**
|
||||||
|
* 开始拦截全局定时器
|
||||||
|
*/
|
||||||
|
hook(): void;
|
||||||
|
/**
|
||||||
|
* 恢复原始的全局定时器函数
|
||||||
|
*/
|
||||||
|
unhook(): void;
|
||||||
|
/**
|
||||||
|
* 清除所有被追踪的定时器
|
||||||
|
*/
|
||||||
|
clearAll(): number;
|
||||||
|
/**
|
||||||
|
* 按类型清除定时器
|
||||||
|
*/
|
||||||
|
clearByType(type: TimerType): number;
|
||||||
|
/**
|
||||||
|
* 获取当前活跃的定时器数量
|
||||||
|
*/
|
||||||
|
getActiveCount(): number;
|
||||||
|
/**
|
||||||
|
* 获取定时器统计信息
|
||||||
|
*/
|
||||||
|
getStats(): Record<TimerType, number>;
|
||||||
|
/**
|
||||||
|
* 获取所有定时器的详细信息(用于调试)
|
||||||
|
*/
|
||||||
|
getTimers(): TimerRecord[];
|
||||||
|
}
|
||||||
|
export declare const timerManager: GlobalTimerManager;
|
||||||
|
export declare const hookTimers: () => void;
|
||||||
|
export declare const unhookTimers: () => void;
|
||||||
|
export declare const clearAllTimers: () => number;
|
||||||
|
export declare const getTimerStats: () => Record<TimerType, number>;
|
||||||
|
export {};
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
"use strict";
|
||||||
|
// timer-manager.ts
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.getTimerStats = exports.clearAllTimers = exports.unhookTimers = exports.hookTimers = exports.timerManager = void 0;
|
||||||
|
class GlobalTimerManager {
|
||||||
|
timers = new Map();
|
||||||
|
isHooked = false;
|
||||||
|
// 保存原始函数
|
||||||
|
original = {
|
||||||
|
setTimeout: global.setTimeout,
|
||||||
|
setInterval: global.setInterval,
|
||||||
|
setImmediate: global.setImmediate, clearTimeout: global.clearTimeout,
|
||||||
|
clearInterval: global.clearInterval,
|
||||||
|
clearImmediate: global.clearImmediate,
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 开始拦截全局定时器
|
||||||
|
*/
|
||||||
|
hook() {
|
||||||
|
if (this.isHooked) {
|
||||||
|
console.warn('[TimerManager] Already hooked');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const self = this;
|
||||||
|
// Hook setTimeout
|
||||||
|
global.setTimeout = function (callback, ms, ...args) {
|
||||||
|
const id = self.original.setTimeout((...callbackArgs) => {
|
||||||
|
self.timers.delete(id); // 执行完后移除
|
||||||
|
callback(...callbackArgs);
|
||||||
|
}, ms, ...args);
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'timeout',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
// Hook setInterval
|
||||||
|
global.setInterval = function (callback, ms, ...args) {
|
||||||
|
const id = self.original.setInterval(callback, ms, ...args);
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'interval',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
// Hook setImmediate
|
||||||
|
global.setImmediate = function (callback, ...args) {
|
||||||
|
const id = self.original.setImmediate((...callbackArgs) => {
|
||||||
|
self.timers.delete(id); // 执行完后移除
|
||||||
|
callback(...callbackArgs);
|
||||||
|
}, ...args);
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'immediate',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
// Hook clear方法(确保从追踪中移除)
|
||||||
|
global.clearTimeout = ((id) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearTimeout(id);
|
||||||
|
});
|
||||||
|
global.clearInterval = ((id) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearInterval(id);
|
||||||
|
});
|
||||||
|
global.clearImmediate = ((id) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearImmediate(id);
|
||||||
|
});
|
||||||
|
this.isHooked = true;
|
||||||
|
console.log('[TimerManager] Hooked global timer functions');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 恢复原始的全局定时器函数
|
||||||
|
*/
|
||||||
|
unhook() {
|
||||||
|
if (!this.isHooked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global.setTimeout = this.original.setTimeout;
|
||||||
|
global.setInterval = this.original.setInterval;
|
||||||
|
global.setImmediate = this.original.setImmediate;
|
||||||
|
global.clearTimeout = this.original.clearTimeout;
|
||||||
|
global.clearInterval = this.original.clearInterval;
|
||||||
|
global.clearImmediate = this.original.clearImmediate;
|
||||||
|
this.isHooked = false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 清除所有被追踪的定时器
|
||||||
|
*/
|
||||||
|
clearAll() {
|
||||||
|
const count = this.timers.size;
|
||||||
|
this.timers.forEach((record) => {
|
||||||
|
if (record.type === 'immediate') {
|
||||||
|
this.original.clearImmediate.call(null, record.id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.original.clearTimeout.call(null, record.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.timers.clear();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 按类型清除定时器
|
||||||
|
*/
|
||||||
|
clearByType(type) {
|
||||||
|
let count = 0;
|
||||||
|
this.timers.forEach((record, id) => {
|
||||||
|
if (record.type === type) {
|
||||||
|
if (type === 'immediate') {
|
||||||
|
this.original.clearImmediate.call(null, id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.original.clearTimeout.call(null, id);
|
||||||
|
}
|
||||||
|
this.timers.delete(id);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取当前活跃的定时器数量
|
||||||
|
*/
|
||||||
|
getActiveCount() {
|
||||||
|
return this.timers.size;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取定时器统计信息
|
||||||
|
*/
|
||||||
|
getStats() {
|
||||||
|
const stats = {
|
||||||
|
timeout: 0,
|
||||||
|
interval: 0,
|
||||||
|
immediate: 0,
|
||||||
|
};
|
||||||
|
this.timers.forEach((record) => {
|
||||||
|
stats[record.type]++;
|
||||||
|
});
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取所有定时器的详细信息(用于调试)
|
||||||
|
*/
|
||||||
|
getTimers() {
|
||||||
|
return Array.from(this.timers.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.timerManager = new GlobalTimerManager();
|
||||||
|
const hookTimers = () => exports.timerManager.hook();
|
||||||
|
exports.hookTimers = hookTimers;
|
||||||
|
const unhookTimers = () => exports.timerManager.unhook();
|
||||||
|
exports.unhookTimers = unhookTimers;
|
||||||
|
const clearAllTimers = () => exports.timerManager.clearAll();
|
||||||
|
exports.clearAllTimers = clearAllTimers;
|
||||||
|
const getTimerStats = () => exports.timerManager.getStats();
|
||||||
|
exports.getTimerStats = getTimerStats;
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
/// <reference types="node" />
|
import { LogFormatter } from "./polyfill";
|
||||||
export type LogFormatterProp = {
|
|
||||||
level: "info" | "warn" | "error";
|
|
||||||
caller: NodeJS.CallSite | null;
|
|
||||||
args: any[];
|
|
||||||
};
|
|
||||||
export type LogFormatter = (props: LogFormatterProp) => any[];
|
|
||||||
export type WatchConfig = {
|
export type WatchConfig = {
|
||||||
|
autoUpdateI18n?: boolean;
|
||||||
/**
|
/**
|
||||||
* 是否启用polyfill
|
* 是否启用polyfill
|
||||||
*/
|
*/
|
||||||
|
|
@ -36,34 +31,59 @@ export type WatchConfig = {
|
||||||
* 初始化时调用
|
* 初始化时调用
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
onInit?: () => void;
|
onInit?: (config: RealWatchConfig) => void;
|
||||||
/**
|
/**
|
||||||
* 服务启动时调用
|
* 服务启动时调用
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
onServerStart?: () => void;
|
onServerStart?: (config: RealWatchConfig) => void;
|
||||||
/**
|
/**
|
||||||
* 编译前调用
|
* 编译前调用
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
onBeforeCompile?: () => void;
|
onBeforeCompile?: (config: RealWatchConfig) => void;
|
||||||
/**
|
/**
|
||||||
* 编译后调用
|
* 编译后调用
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
onAfterCompile?: () => void;
|
onAfterCompile?: (config: RealWatchConfig) => void;
|
||||||
/**
|
/**
|
||||||
* 服务关闭时调用
|
* 服务关闭时调用
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
onServerShutdown?: () => void;
|
onServerShutdown?: (config: RealWatchConfig) => void;
|
||||||
/**
|
/**
|
||||||
* 销毁监视器时调用
|
* 销毁监视器时调用
|
||||||
* @returns 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> = {
|
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]>;
|
[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]>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,108 @@ const typescript_1 = tslib_1.__importDefault(require("typescript"));
|
||||||
const path_1 = tslib_1.__importDefault(require("path"));
|
const path_1 = tslib_1.__importDefault(require("path"));
|
||||||
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
|
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
|
||||||
const fs_1 = tslib_1.__importDefault(require("fs"));
|
const fs_1 = tslib_1.__importDefault(require("fs"));
|
||||||
const lodash_1 = require("lodash");
|
const polyfill_1 = require("./polyfill");
|
||||||
|
const tsc_alias_1 = require("tsc-alias");
|
||||||
|
const timer_manager_1 = require("./timer-manager");
|
||||||
|
// 创建事件发射器
|
||||||
|
const createEventEmitter = () => {
|
||||||
|
const listeners = new Map();
|
||||||
|
return {
|
||||||
|
on: (event, handler) => {
|
||||||
|
if (!listeners.has(event)) {
|
||||||
|
listeners.set(event, new Set());
|
||||||
|
}
|
||||||
|
listeners.get(event).add(handler);
|
||||||
|
},
|
||||||
|
emit: async (event, data) => {
|
||||||
|
const handlers = listeners.get(event);
|
||||||
|
if (handlers) {
|
||||||
|
const promises = Array.from(handlers).map(handler => Promise.resolve(handler(data)));
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
off: (event, handler) => {
|
||||||
|
const handlers = listeners.get(event);
|
||||||
|
if (handlers) {
|
||||||
|
handlers.delete(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// 创建编译任务队列
|
||||||
|
const createCompileQueue = (eventEmitter) => {
|
||||||
|
const queue = [];
|
||||||
|
let isProcessing = false;
|
||||||
|
let processingCount = 0;
|
||||||
|
let taskProcessor = async (task) => ({
|
||||||
|
taskId: task.id,
|
||||||
|
success: true,
|
||||||
|
filePath: task.filePath
|
||||||
|
});
|
||||||
|
const addTask = (task) => {
|
||||||
|
// 检查是否有相同文件的任务已存在,如果有则更新时间戳
|
||||||
|
const existingIndex = queue.findIndex(t => t.filePath === task.filePath);
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
queue[existingIndex] = { ...queue[existingIndex], ...task };
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
queue.push(task);
|
||||||
|
}
|
||||||
|
eventEmitter.emit("compile-task-added", task);
|
||||||
|
processQueue();
|
||||||
|
};
|
||||||
|
const processQueue = async () => {
|
||||||
|
if (isProcessing || queue.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isProcessing = true;
|
||||||
|
processingCount = queue.length;
|
||||||
|
await eventEmitter.emit("compile-batch-started", { count: processingCount });
|
||||||
|
const tasksToProcess = [...queue];
|
||||||
|
queue.length = 0; // 清空队列
|
||||||
|
const results = [];
|
||||||
|
for (const task of tasksToProcess) {
|
||||||
|
try {
|
||||||
|
const result = await taskProcessor(task);
|
||||||
|
results.push(result);
|
||||||
|
await eventEmitter.emit("compile-task-completed", result);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const result = {
|
||||||
|
taskId: task.id,
|
||||||
|
success: false,
|
||||||
|
filePath: task.filePath,
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
};
|
||||||
|
results.push(result);
|
||||||
|
await eventEmitter.emit("compile-task-completed", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isProcessing = false;
|
||||||
|
processingCount = 0;
|
||||||
|
await eventEmitter.emit("compile-batch-completed", { results });
|
||||||
|
// 检查是否需要重启服务器
|
||||||
|
const hasSuccessfulCompiles = results.some(r => r.success);
|
||||||
|
if (hasSuccessfulCompiles) {
|
||||||
|
await eventEmitter.emit("server-restart-needed", { results });
|
||||||
|
}
|
||||||
|
// 如果在处理过程中又有新任务加入,继续处理
|
||||||
|
if (queue.length > 0) {
|
||||||
|
processQueue();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
addTask,
|
||||||
|
getQueueLength: () => queue.length,
|
||||||
|
isProcessing: () => isProcessing,
|
||||||
|
getProcessingCount: () => processingCount,
|
||||||
|
setTaskProcessor: (processor) => {
|
||||||
|
taskProcessor = processor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
|
autoUpdateI18n: true,
|
||||||
polyfill: {
|
polyfill: {
|
||||||
console: {
|
console: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
|
@ -44,22 +144,22 @@ const defaultConfig = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lifecycle: {
|
lifecycle: {
|
||||||
onInit: () => {
|
onInit: (config) => {
|
||||||
console.log("----> Watcher initialized.");
|
console.log("----> Watcher initialized.");
|
||||||
},
|
},
|
||||||
onServerStart: () => {
|
onServerStart: (config) => {
|
||||||
console.log("----> Server started.");
|
console.log("----> Server started.");
|
||||||
},
|
},
|
||||||
onBeforeCompile: () => {
|
onBeforeCompile: (config) => {
|
||||||
console.log("----> Compiling......");
|
console.log("----> Compiling......");
|
||||||
},
|
},
|
||||||
onAfterCompile: () => {
|
onAfterCompile: (config) => {
|
||||||
console.log("----> Compile completed.");
|
console.log("----> Compile completed.");
|
||||||
},
|
},
|
||||||
onServerShutdown: () => {
|
onServerShutdown: (config) => {
|
||||||
console.log("----> Server shutdown.");
|
console.log("----> Server shutdown.");
|
||||||
},
|
},
|
||||||
onDispose: () => {
|
onDispose: (config) => {
|
||||||
console.log("----> Watcher disposed.");
|
console.log("----> Watcher disposed.");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -94,62 +194,71 @@ const getOverrideConfig = (config) => {
|
||||||
? overrideInner(config, defaultConfig)
|
? overrideInner(config, defaultConfig)
|
||||||
: defaultConfig;
|
: defaultConfig;
|
||||||
};
|
};
|
||||||
/**
|
// 当前运行目录
|
||||||
* 根据 alias 配置表将路径中的别名替换为真实路径
|
const pwd = process.cwd();
|
||||||
* @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module"
|
// 创建服务器管理器
|
||||||
* @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组
|
const createServerManager = (projectPath, eventEmitter, config) => {
|
||||||
* @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径
|
let shutdown;
|
||||||
*/
|
let isRestarting = false;
|
||||||
function replaceAliasWithPath(path, aliasConfig) {
|
const restart = async () => {
|
||||||
for (const [alias, targets] of Object.entries(aliasConfig)) {
|
if (isRestarting) {
|
||||||
// If alias ends with "*", handle it as a dynamic alias.
|
return;
|
||||||
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 {
|
isRestarting = true;
|
||||||
// Handle static alias without "*" by directly matching the path
|
if (shutdown) {
|
||||||
if (path.startsWith(alias)) {
|
console.log("----> Shutting down service......");
|
||||||
const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array)
|
await shutdown().then(() => config.lifecycle.onServerShutdown(config));
|
||||||
// Replace the alias part with the target path
|
shutdown = undefined;
|
||||||
return path.replace(alias, target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
console.warn("----> Clearing require cache of project......");
|
||||||
// If no alias matches, return the original path
|
let deleteCount = 0;
|
||||||
return path;
|
// 清空lib以下目录的缓存
|
||||||
}
|
Object.keys(require.cache).forEach((key) => {
|
||||||
const watch = (projectPath, config) => {
|
// 如果不是项目目录下的文件,不删除
|
||||||
const realConfig = getOverrideConfig(config);
|
if (!key.startsWith(projectPath)) {
|
||||||
const enableTrace = !!process.env.ENABLE_TRACE;
|
return;
|
||||||
// 查找配置文件
|
}
|
||||||
const configFileName = typescript_1.default.findConfigFile(projectPath, typescript_1.default.sys.fileExists, "tsconfig.build.json");
|
else if (key.includes("lib") &&
|
||||||
if (!configFileName) {
|
!key.includes("node_modules")) {
|
||||||
throw new Error("Could not find a valid 'tsconfig.build.json'.");
|
delete require.cache[key];
|
||||||
}
|
deleteCount++;
|
||||||
// 读取配置文件
|
}
|
||||||
const configFile = typescript_1.default.readConfigFile(configFileName, typescript_1.default.sys.readFile);
|
});
|
||||||
// 解析配置文件内容
|
console.warn(`----> ${deleteCount} modules has been removed from require.cache.`);
|
||||||
const { options, projectReferences } = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path_1.default.dirname(configFileName));
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const aliasConfig = (0, lodash_1.cloneDeep)(options.paths) || {};
|
const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default;
|
||||||
// 输出原始配置
|
console.warn("----> Starting service......");
|
||||||
// console.log("[DEBUG] Original alias config:", aliasConfig);
|
try {
|
||||||
Object.keys(aliasConfig).forEach((key) => {
|
(0, timer_manager_1.clearAllTimers)();
|
||||||
const value = aliasConfig[key];
|
// 这里注意要在require之前,因为require会触发编译
|
||||||
// 替换src
|
const { startup } = require('./start');
|
||||||
aliasConfig[key] = typeof value === "string" ? value.replace("src", "lib") : value.map((v) => v.replace("src", "lib"));
|
shutdown = await startup(pwd, simpleConnector).then((shutdown) => {
|
||||||
});
|
config.lifecycle.onServerStart(config);
|
||||||
// 输出真实的alias配置
|
return shutdown;
|
||||||
console.debug("[DEBUG] Running Alias config:", aliasConfig);
|
});
|
||||||
const createProgramAndSourceFile = (path, options, projectReferences) => {
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error("----> Failed to start service:", error);
|
||||||
|
isRestarting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isRestarting = false;
|
||||||
|
await eventEmitter.emit("server-restarted", {});
|
||||||
|
};
|
||||||
|
const dispose = async () => {
|
||||||
|
if (shutdown) {
|
||||||
|
await shutdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
restart,
|
||||||
|
dispose,
|
||||||
|
isRestarting: () => isRestarting
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// 创建编译器
|
||||||
|
const createCompiler = async (projectPath, options, projectReferences, treatFile, config) => {
|
||||||
|
const createProgramAndSourceFile = (path) => {
|
||||||
const program = typescript_1.default.createProgram({
|
const program = typescript_1.default.createProgram({
|
||||||
rootNames: [path],
|
rootNames: [path],
|
||||||
options,
|
options,
|
||||||
|
|
@ -171,8 +280,248 @@ const watch = (projectPath, config) => {
|
||||||
}
|
}
|
||||||
return { program, sourceFile, diagnostics };
|
return { program, sourceFile, diagnostics };
|
||||||
};
|
};
|
||||||
// 这个函数用于解决require的时候,如果文件不存在,会尝试编译ts文件
|
const compileTask = async (task) => {
|
||||||
// 测试:在src下新建一个ts文件,然后删除lib下的js文件,然后require这个文件,会发现会自动编译ts文件
|
const { filePath, changeType } = task;
|
||||||
|
const modulePath = path_1.default.resolve(filePath);
|
||||||
|
// 判断文件类型
|
||||||
|
if (!filePath.endsWith(".ts")) {
|
||||||
|
// 处理非TypeScript文件 (如JSON文件)
|
||||||
|
if (filePath.endsWith(".json")) {
|
||||||
|
const targetPath = modulePath.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"));
|
||||||
|
try {
|
||||||
|
if (changeType === "remove") {
|
||||||
|
if (fs_1.default.existsSync(targetPath)) {
|
||||||
|
fs_1.default.unlinkSync(targetPath);
|
||||||
|
}
|
||||||
|
console.warn(`File ${targetPath} has been removed.`);
|
||||||
|
}
|
||||||
|
else if (changeType === "add" || changeType === "change") {
|
||||||
|
// 确保目录存在
|
||||||
|
const dir = path_1.default.dirname(targetPath);
|
||||||
|
if (!fs_1.default.existsSync(dir)) {
|
||||||
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
if (changeType === "change" && fs_1.default.existsSync(targetPath)) {
|
||||||
|
fs_1.default.unlinkSync(targetPath);
|
||||||
|
}
|
||||||
|
fs_1.default.copyFileSync(filePath, targetPath, fs_1.default.constants.COPYFILE_FICLONE);
|
||||||
|
console.warn(`File ${filePath} has been copied to ${targetPath}.`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
success: true,
|
||||||
|
filePath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
success: false,
|
||||||
|
filePath,
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn(`File ${filePath} is not [ts,json] file, skipped.`);
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
success: true,
|
||||||
|
filePath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 处理TypeScript文件
|
||||||
|
console.clear();
|
||||||
|
console.warn(`File ${filePath} has been ${changeType}d`);
|
||||||
|
const libPath = modulePath
|
||||||
|
.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"))
|
||||||
|
.replace(/\.ts$/, ".js");
|
||||||
|
if (changeType === "remove") {
|
||||||
|
try {
|
||||||
|
if (fs_1.default.existsSync(libPath)) {
|
||||||
|
fs_1.default.unlinkSync(libPath);
|
||||||
|
}
|
||||||
|
console.warn(`File ${libPath} has been removed.`);
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
success: true,
|
||||||
|
filePath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
success: false,
|
||||||
|
filePath,
|
||||||
|
error: `Error removing file: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 编译TypeScript文件
|
||||||
|
const { program, sourceFile, diagnostics } = createProgramAndSourceFile(filePath);
|
||||||
|
if (diagnostics.length) {
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
success: false,
|
||||||
|
filePath,
|
||||||
|
error: "TypeScript compilation error"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 只输出单个文件
|
||||||
|
config.lifecycle.onBeforeCompile(config);
|
||||||
|
const emitResult = program.emit(sourceFile);
|
||||||
|
if (emitResult.emitSkipped) {
|
||||||
|
console.error(`Emit failed for ${filePath}!`);
|
||||||
|
config.lifecycle.onAfterCompile(config);
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
success: false,
|
||||||
|
filePath,
|
||||||
|
error: "TypeScript emit failed"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Emit succeeded for ${filePath}.`);
|
||||||
|
config.lifecycle.onAfterCompile(config);
|
||||||
|
const jsFilePath = libPath;
|
||||||
|
treatFile(jsFilePath);
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
success: true,
|
||||||
|
filePath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
compileTask
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// 创建文件监视器
|
||||||
|
const createFileWatcher = (projectPath, eventEmitter) => {
|
||||||
|
const watchSourcePath = path_1.default.join(projectPath, "src");
|
||||||
|
let startWatching = false;
|
||||||
|
console.log("Watching for changes in", watchSourcePath);
|
||||||
|
const watcher = chokidar_1.default.watch(watchSourcePath, {
|
||||||
|
persistent: true,
|
||||||
|
ignored: (file) => file.endsWith(".tsx") ||
|
||||||
|
file.endsWith(".xml") ||
|
||||||
|
file.includes("components") ||
|
||||||
|
file.includes("pages") ||
|
||||||
|
file.includes("hooks"),
|
||||||
|
interval: 100,
|
||||||
|
binaryInterval: 100,
|
||||||
|
cwd: projectPath,
|
||||||
|
depth: 99,
|
||||||
|
followSymlinks: true,
|
||||||
|
ignoreInitial: false,
|
||||||
|
ignorePermissionErrors: false,
|
||||||
|
usePolling: false,
|
||||||
|
alwaysStat: false,
|
||||||
|
});
|
||||||
|
const handleFileChange = async (path, type) => {
|
||||||
|
if (!startWatching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const event = {
|
||||||
|
path,
|
||||||
|
type,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
await eventEmitter.emit("file-changed", event);
|
||||||
|
};
|
||||||
|
watcher.on("ready", () => {
|
||||||
|
console.warn("Initial scan complete. Ready for changes");
|
||||||
|
startWatching = true;
|
||||||
|
});
|
||||||
|
watcher.on("error", (error) => console.log(`Watcher error: ${error}`));
|
||||||
|
watcher
|
||||||
|
.on("add", async (path) => {
|
||||||
|
await handleFileChange(path, "add");
|
||||||
|
})
|
||||||
|
.on("change", async (path) => {
|
||||||
|
await handleFileChange(path, "change");
|
||||||
|
})
|
||||||
|
.on("unlink", async (path) => {
|
||||||
|
await handleFileChange(path, "remove");
|
||||||
|
});
|
||||||
|
const dispose = async () => {
|
||||||
|
await watcher.close();
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
dispose
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// 生成唯一任务ID
|
||||||
|
const generateTaskId = () => {
|
||||||
|
return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
};
|
||||||
|
// /**
|
||||||
|
// * 根据 alias 配置表将路径中的别名替换为真实路径
|
||||||
|
// * @param path - 输入的路径字符串,例如 "@project/file" 或 "@oak-app-domain/some-module"
|
||||||
|
// * @param aliasConfig - alias 配置表,key 为别名,value 为对应的真实路径或路径数组
|
||||||
|
// * @returns 替换后的路径,如果没有匹配到 alias,则返回原始路径
|
||||||
|
// */
|
||||||
|
// const replaceAliasWithPath = (path: string, aliasConfig: Record<string, string | string[]>): string => {
|
||||||
|
// for (const [alias, targets] of Object.entries(aliasConfig)) {
|
||||||
|
// // If alias ends with "*", handle it as a dynamic alias.
|
||||||
|
// if (alias.endsWith('*')) {
|
||||||
|
// // Create a regex pattern that matches paths starting with the alias, followed by any characters
|
||||||
|
// const aliasPattern = new RegExp(`^${alias.replace(/\*$/, "")}(.*)`); // e.g., '@project/*' becomes '@project/(.*)'
|
||||||
|
// const match = path.match(aliasPattern);
|
||||||
|
// if (match) {
|
||||||
|
// // Replace the alias with the target path, appending the matched part from the original path
|
||||||
|
// const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array)
|
||||||
|
// // Ensure that the target path ends with a slash if it's not already
|
||||||
|
// const replacedPath = target.replace(/\/\*$/, "/") + match[1];
|
||||||
|
// return replacedPath;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // Handle static alias without "*" by directly matching the path
|
||||||
|
// if (path.startsWith(alias)) {
|
||||||
|
// const target = Array.isArray(targets) ? targets[0] : targets; // Take the first target (if it's an array)
|
||||||
|
// // Replace the alias part with the target path
|
||||||
|
// return path.replace(alias, target);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // If no alias matches, return the original path
|
||||||
|
// return path;
|
||||||
|
// };
|
||||||
|
const watch = async (projectPath, config) => {
|
||||||
|
const realConfig = getOverrideConfig(config);
|
||||||
|
const enableTrace = !!process.env.ENABLE_TRACE;
|
||||||
|
// 查找配置文件
|
||||||
|
const configFileName = typescript_1.default.findConfigFile(projectPath, typescript_1.default.sys.fileExists, "tsconfig.build.json");
|
||||||
|
if (!configFileName) {
|
||||||
|
throw new Error("Could not find a valid 'tsconfig.build.json'.");
|
||||||
|
}
|
||||||
|
const runFile = await (0, tsc_alias_1.prepareSingleFileReplaceTscAliasPaths)({
|
||||||
|
configFile: path_1.default.join(projectPath, "tsconfig.build.json"),
|
||||||
|
resolveFullPaths: true,
|
||||||
|
});
|
||||||
|
function treatFile(filePath) {
|
||||||
|
console.log(`Processing file for alias replacement: ${filePath}`);
|
||||||
|
const fileContents = fs_1.default.readFileSync(filePath, 'utf8');
|
||||||
|
const newContents = runFile({ fileContents, filePath });
|
||||||
|
// do stuff with newContents
|
||||||
|
fs_1.default.writeFileSync(filePath, newContents, 'utf8');
|
||||||
|
}
|
||||||
|
// // 读取配置文件
|
||||||
|
const configFile = typescript_1.default.readConfigFile(configFileName, typescript_1.default.sys.readFile);
|
||||||
|
// // 解析配置文件内容
|
||||||
|
const { options, projectReferences } = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path_1.default.dirname(configFileName));
|
||||||
|
// const aliasConfig: AliasConfig = cloneDeep(options.paths) || {};
|
||||||
|
// // 输出原始配置
|
||||||
|
// // console.log("[DEBUG] Original alias config:", aliasConfig);
|
||||||
|
// Object.keys(aliasConfig).forEach((key) => {
|
||||||
|
// const value = aliasConfig[key];
|
||||||
|
// // 替换src
|
||||||
|
// aliasConfig[key] = typeof value === "string" ? value.replace("src", "lib") : value.map((v: string) => v.replace("src", "lib"));
|
||||||
|
// });
|
||||||
|
// 输出真实的alias配置
|
||||||
|
// console.debug("[DEBUG] Running Alias config:", aliasConfig);
|
||||||
|
// 初始化polyfill
|
||||||
const polyfillLoader = () => {
|
const polyfillLoader = () => {
|
||||||
const BuiltinModule = require("module");
|
const BuiltinModule = require("module");
|
||||||
// 模拟环境下的 module 构造函数
|
// 模拟环境下的 module 构造函数
|
||||||
|
|
@ -186,7 +535,13 @@ const watch = (projectPath, config) => {
|
||||||
// 检查并编译 .ts 文件
|
// 检查并编译 .ts 文件
|
||||||
if (fs_1.default.existsSync(tsPath)) {
|
if (fs_1.default.existsSync(tsPath)) {
|
||||||
console.log(`[resolve] Found TypeScript source file: ${tsPath}, attempting to compile...`);
|
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) {
|
if (diagnostics.length) {
|
||||||
console.error(`[resolve] Compilation failed for: ${tsPath}`);
|
console.error(`[resolve] Compilation failed for: ${tsPath}`);
|
||||||
throw new Error("TypeScript compilation error");
|
throw new Error("TypeScript compilation error");
|
||||||
|
|
@ -196,6 +551,9 @@ const watch = (projectPath, config) => {
|
||||||
console.error(`[resolve] Emit skipped for: ${tsPath}`);
|
console.error(`[resolve] Emit skipped for: ${tsPath}`);
|
||||||
throw new Error("TypeScript emit skipped");
|
throw new Error("TypeScript emit skipped");
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
treatFile(jsPath);
|
||||||
|
}
|
||||||
console.log(`[resolve] Successfully compiled: ${tsPath}`);
|
console.log(`[resolve] Successfully compiled: ${tsPath}`);
|
||||||
return jsPath;
|
return jsPath;
|
||||||
}
|
}
|
||||||
|
|
@ -225,11 +583,11 @@ const watch = (projectPath, config) => {
|
||||||
rFoptions // 解析选项
|
rFoptions // 解析选项
|
||||||
) {
|
) {
|
||||||
let resolvedRequest = request;
|
let resolvedRequest = request;
|
||||||
const replacedPath = replaceAliasWithPath(request, aliasConfig);
|
// const replacedPath = replaceAliasWithPath(request, aliasConfig);
|
||||||
if (replacedPath !== request) {
|
// if (replacedPath !== request) {
|
||||||
console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`);
|
// console.log(`[resolve] Alias resolved: ${request} -> ${replacedPath}`);
|
||||||
resolvedRequest = path_1.default.join(projectPath, replacedPath);
|
// resolvedRequest = pathLib.join(projectPath, replacedPath);
|
||||||
}
|
// }
|
||||||
try {
|
try {
|
||||||
return oldResolveFilename.call(this, resolvedRequest, parent, isMain, rFoptions);
|
return oldResolveFilename.call(this, resolvedRequest, parent, isMain, rFoptions);
|
||||||
}
|
}
|
||||||
|
|
@ -247,290 +605,144 @@ const watch = (projectPath, config) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// 初始化polyfill
|
||||||
polyfillLoader();
|
polyfillLoader();
|
||||||
//polyfill console.log 添加时间
|
realConfig.polyfill.console.enable && (0, polyfill_1.polyfillConsole)("watch", enableTrace, realConfig.polyfill?.console?.formatter);
|
||||||
const polyfillConsole = (trace) => {
|
// 监听定时器相关的API,确保定时器在重启后不会重复触发
|
||||||
// 获取调用堆栈信息
|
(0, timer_manager_1.hookTimers)();
|
||||||
const getCallerInfo = () => {
|
// 初始编译检查
|
||||||
if (!trace) {
|
const initialCompile = () => {
|
||||||
return null;
|
const serverConfigFile = path_1.default.join(projectPath, "lib/configuration/server.js");
|
||||||
}
|
if (!fs_1.default.existsSync(serverConfigFile)) {
|
||||||
const originalFunc = Error.prepareStackTrace;
|
// 尝试编译src/configuration/server.ts
|
||||||
let callerInfo = null;
|
console.log(`[watch] Server configuration file not found, attempting to compile the project......`);
|
||||||
try {
|
const tryCompile = (tsFile) => {
|
||||||
const err = new Error();
|
console.log(`[watch] Compiling: ${tsFile}`);
|
||||||
Error.prepareStackTrace = (err, stack) => stack;
|
if (fs_1.default.existsSync(tsFile)) {
|
||||||
const stack = err.stack; // Type assertion here
|
const program = typescript_1.default.createProgram({
|
||||||
const currentFile = stack[0].getFileName();
|
rootNames: [tsFile],
|
||||||
for (let i = 1; i < stack.length; i++) {
|
options,
|
||||||
// Start from index 1
|
projectReferences,
|
||||||
const callSite = stack[i];
|
|
||||||
if (currentFile !== callSite.getFileName()) {
|
|
||||||
callerInfo = callSite;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
Error.prepareStackTrace = originalFunc;
|
|
||||||
return callerInfo;
|
|
||||||
};
|
|
||||||
// polyfill console.log 添加时间和文件位置
|
|
||||||
["info", "warn", "error"].forEach((level) => {
|
|
||||||
const levelStr = level;
|
|
||||||
const oldFunc = console[levelStr];
|
|
||||||
console[levelStr] = function (...args) {
|
|
||||||
oldFunc(...(defaultConfig.polyfill?.console?.formatter({
|
|
||||||
level: levelStr,
|
|
||||||
caller: getCallerInfo(),
|
|
||||||
args,
|
|
||||||
}) || []));
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
realConfig.polyfill.console.enable && polyfillConsole(enableTrace);
|
|
||||||
// 这里注意要在require之前,因为require会触发编译
|
|
||||||
const { startup } = require('./start');
|
|
||||||
// 如果lib目录是空的,则直接编译所有的ts文件
|
|
||||||
const serverConfigFile = path_1.default.join(projectPath, "lib/configuration/server.js");
|
|
||||||
if (!fs_1.default.existsSync(serverConfigFile)) {
|
|
||||||
// 尝试编译src/configuration/server.ts
|
|
||||||
console.log(`[watch] Server configuration file not found, attempting to compile the project......`);
|
|
||||||
const tryCompile = (tsFile) => {
|
|
||||||
console.log(`[watch] Compiling: ${tsFile}`);
|
|
||||||
if (fs_1.default.existsSync(tsFile)) {
|
|
||||||
const { program, diagnostics } = createProgramAndSourceFile(tsFile, options, projectReferences);
|
|
||||||
if (diagnostics.length) {
|
|
||||||
console.error(`Error in ${tsFile}`);
|
|
||||||
diagnostics.forEach((diagnostic) => {
|
|
||||||
console.error(`${typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`);
|
|
||||||
});
|
});
|
||||||
console.error(`文件存在语法错误,请检查修复后重试!`);
|
const diagnostics = typescript_1.default.getPreEmitDiagnostics(program);
|
||||||
process.exit(1);
|
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();
|
// 其他涉及到的目录会在运行的时候自动编译,这里主要处理的是动态require的文件
|
||||||
if (emitResult.emitSkipped) {
|
const compileFiles = [
|
||||||
console.error(`Emit failed for ${tsFile}!`);
|
"src/configuration/index.ts",
|
||||||
process.exit(1);
|
"src/aspects/index.ts",
|
||||||
}
|
"src/checkers/index.ts",
|
||||||
console.log(`Emit succeeded. ${tsFile} has been compiled.`);
|
"src/triggers/index.ts",
|
||||||
}
|
"src/timers/index.ts",
|
||||||
};
|
"src/routines/start.ts",
|
||||||
// 所有要编译的目录
|
"src/watchers/index.ts",
|
||||||
// 其他涉及到的目录会在运行的时候自动编译,这里主要处理的是动态require的文件
|
"src/endpoints/index.ts",
|
||||||
const compileFiles = [
|
"src/data/index.ts",
|
||||||
"src/configuration/index.ts",
|
"src/ports/index.ts",
|
||||||
"src/aspects/index.ts",
|
];
|
||||||
"src/checkers/index.ts",
|
compileFiles.forEach(tryCompile);
|
||||||
"src/triggers/index.ts",
|
// 最后替换lib目录下所有的路径别名
|
||||||
"src/timers/index.ts",
|
console.log(`[watch] Replacing path aliases in lib directory......`);
|
||||||
"src/routines/start.ts",
|
const libDir = path_1.default.join(projectPath, "lib");
|
||||||
"src/watchers/index.ts",
|
const walkDir = (dir) => {
|
||||||
"src/endpoints/index.ts",
|
const files = fs_1.default.readdirSync(dir);
|
||||||
"src/data/index.ts",
|
files.forEach((file) => {
|
||||||
"src/ports/index.ts",
|
const fullPath = path_1.default.join(dir, file);
|
||||||
];
|
const stat = fs_1.default.statSync(fullPath);
|
||||||
compileFiles.forEach(tryCompile);
|
if (stat.isDirectory()) {
|
||||||
}
|
walkDir(fullPath);
|
||||||
return new Promise((resolve, reject) => {
|
}
|
||||||
realConfig.lifecycle.onInit();
|
else if (stat.isFile() && fullPath.endsWith(".js")) {
|
||||||
let shutdown;
|
const fileContents = fs_1.default.readFileSync(fullPath, 'utf8');
|
||||||
const restart = async () => {
|
const newContents = runFile({ fileContents, filePath: fullPath });
|
||||||
if (shutdown) {
|
fs_1.default.writeFileSync(fullPath, newContents, 'utf8');
|
||||||
console.log("----> Shutting down service......");
|
}
|
||||||
await shutdown().then(realConfig.lifecycle.onServerShutdown);
|
});
|
||||||
// reset shutdown
|
};
|
||||||
shutdown = undefined;
|
walkDir(libDir);
|
||||||
}
|
console.log(`[watch] Path alias replacement completed.`);
|
||||||
console.warn("----> Clearing require cache of project......");
|
}
|
||||||
let deleteCount = 0;
|
};
|
||||||
// 清空lib以下目录的缓存
|
return new Promise(async (resolve, reject) => {
|
||||||
Object.keys(require.cache).forEach((key) => {
|
// 创建事件系统
|
||||||
// 如果不是项目目录下的文件,不删除
|
const eventEmitter = createEventEmitter();
|
||||||
if (!key.startsWith(projectPath)) {
|
// 创建各个组件
|
||||||
return;
|
const compileQueue = createCompileQueue(eventEmitter);
|
||||||
}
|
const compiler = await createCompiler(projectPath, options, projectReferences, treatFile, realConfig);
|
||||||
else if (key.includes("lib") &&
|
const serverManager = createServerManager(projectPath, eventEmitter, realConfig);
|
||||||
!key.includes("node_modules")) {
|
const fileWatcher = createFileWatcher(projectPath, eventEmitter);
|
||||||
delete require.cache[key];
|
// 设置编译器处理器
|
||||||
deleteCount++;
|
compileQueue.setTaskProcessor(compiler.compileTask);
|
||||||
|
// 设置事件监听器
|
||||||
|
eventEmitter.on("file-changed", (event) => {
|
||||||
|
const task = {
|
||||||
|
id: generateTaskId(),
|
||||||
|
filePath: event.path,
|
||||||
|
changeType: event.type,
|
||||||
|
timestamp: event.timestamp
|
||||||
|
};
|
||||||
|
compileQueue.addTask(task);
|
||||||
|
});
|
||||||
|
eventEmitter.on("compile-batch-started", (data) => {
|
||||||
|
console.log(`----> Starting compilation batch (${data.count} files)...`);
|
||||||
|
});
|
||||||
|
eventEmitter.on("compile-batch-completed", (data) => {
|
||||||
|
const successCount = data.results.filter(r => r.success).length;
|
||||||
|
console.log(`----> Compilation batch completed: ${successCount}/${data.results.length} successful`);
|
||||||
|
});
|
||||||
|
// 编译成功之后,若设置的同步i18n,则触发i18n更新
|
||||||
|
if (realConfig.autoUpdateI18n) {
|
||||||
|
const projectI18nPath = path_1.default.join("src", "data", "i18n.ts");
|
||||||
|
eventEmitter.on("compile-task-completed", async (result) => {
|
||||||
|
if (result.filePath == projectI18nPath && result.success) {
|
||||||
|
console.log("-------------start upgrade i18n.-------------");
|
||||||
|
// 这里是copy:upgradeI18n的
|
||||||
|
const { checkAndUpdateI18n } = require('oak-backend-base/lib/routines/i18n.js');
|
||||||
|
const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default;
|
||||||
|
// 这里注意要在require之前,因为require会触发编译
|
||||||
|
const { startup } = require('./start');
|
||||||
|
startup(pwd, simpleConnector, true, true, checkAndUpdateI18n).then(() => {
|
||||||
|
console.log("------------upgrade i18n success.------------");
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error("------------upgrade i18n failed!------------", err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.warn(`----> ${deleteCount} modules has been removed from require.cache.`);
|
}
|
||||||
const pwd = process.cwd();
|
eventEmitter.on("server-restart-needed", async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
if (!serverManager.isRestarting()) {
|
||||||
const simpleConnector = require(path_1.default.join(projectPath, "lib/config/connector")).default;
|
console.log("----> Restarting server...");
|
||||||
console.warn("----> Starting service......");
|
await serverManager.restart();
|
||||||
shutdown = await startup(pwd, simpleConnector).then((shutdown) => {
|
|
||||||
realConfig.lifecycle.onServerStart();
|
|
||||||
return shutdown;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const watchSourcePath = path_1.default.join(projectPath, "src");
|
|
||||||
console.log("Watching for changes in", watchSourcePath);
|
|
||||||
const watcher = chokidar_1.default.watch(watchSourcePath, {
|
|
||||||
persistent: true,
|
|
||||||
ignored: (file) => file.endsWith(".tsx") ||
|
|
||||||
file.endsWith(".xml") ||
|
|
||||||
file.includes("components") ||
|
|
||||||
file.includes("pages") ||
|
|
||||||
file.includes("hooks"),
|
|
||||||
interval: 100,
|
|
||||||
binaryInterval: 100,
|
|
||||||
cwd: projectPath,
|
|
||||||
depth: 99,
|
|
||||||
followSymlinks: true,
|
|
||||||
ignoreInitial: false,
|
|
||||||
ignorePermissionErrors: false,
|
|
||||||
usePolling: false,
|
|
||||||
alwaysStat: false,
|
|
||||||
});
|
|
||||||
let startWatching = false;
|
|
||||||
watcher.on("ready", () => {
|
|
||||||
console.warn("Initial scan complete. Ready for changes");
|
|
||||||
startWatching = true;
|
|
||||||
});
|
|
||||||
watcher.on("error", (error) => console.log(`Watcher error: ${error}`));
|
|
||||||
let processingQueue = [];
|
|
||||||
const fileChangeHandler = async (path, type) => {
|
|
||||||
// 判断一下是不是以下扩展名:ts
|
|
||||||
if (!path.endsWith(".ts")) {
|
|
||||||
// 如果是json文件,复制或者删除
|
|
||||||
if (path.endsWith(".json")) {
|
|
||||||
// const targetPath = path.replace("src", "lib"); // 这里直接替换不对,应该是拿到项目目录,把项目目录/src 换成项目目录/lib
|
|
||||||
const targetPath = path.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"));
|
|
||||||
if (type === "remove") {
|
|
||||||
fs_1.default.unlinkSync(targetPath);
|
|
||||||
console.warn(`File ${targetPath} has been removed.`);
|
|
||||||
}
|
|
||||||
else if (type === "add") {
|
|
||||||
fs_1.default.copyFileSync(path, targetPath, fs_1.default.constants.COPYFILE_FICLONE);
|
|
||||||
console.warn(`File ${path} has been created at ${targetPath}.`);
|
|
||||||
}
|
|
||||||
else if (type === "change") {
|
|
||||||
// 强制覆盖文件
|
|
||||||
if (fs_1.default.existsSync(targetPath)) {
|
|
||||||
fs_1.default.unlinkSync(targetPath);
|
|
||||||
}
|
|
||||||
fs_1.default.copyFileSync(path, targetPath, fs_1.default.constants.COPYFILE_FICLONE);
|
|
||||||
console.warn(`File ${path} has been copied to ${targetPath}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn(`File ${path} is not [ts,json] file, skiped.`);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 控制台清空
|
|
||||||
console.clear();
|
|
||||||
console.warn(`File ${path} has been ${type}d`);
|
|
||||||
// 先判断一下这个文件在不在require.cache里面
|
|
||||||
const modulePath = path_1.default.resolve(path);
|
|
||||||
// 将src替换为lib
|
|
||||||
const libPath = modulePath
|
|
||||||
.replace(path_1.default.join(projectPath, "src"), path_1.default.join(projectPath, "lib"))
|
|
||||||
.replace(/\.ts$/, ".js");
|
|
||||||
let compileOnly = false;
|
|
||||||
if (!require.cache[libPath]) {
|
|
||||||
// 如果是删除的话,直接尝试删除lib下的文件
|
|
||||||
if (type === "remove") {
|
|
||||||
try {
|
|
||||||
fs_1.default.unlinkSync(libPath);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(`Error in delete ${libPath}`, e);
|
|
||||||
}
|
|
||||||
console.warn(`File ${libPath} has been removed.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// console.warn(
|
|
||||||
// `File ${libPath} is not in module cache, will compile only.`
|
|
||||||
// );
|
|
||||||
// compileOnly = true;
|
|
||||||
// 这里现在不需要仅编译了,因为在require的时候会自动编译ts文件
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 如果是删除,则需要发出警告,文件正在被进程使用
|
|
||||||
if (type === "remove") {
|
|
||||||
console.error(`File ${libPath} is being used, skiped.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { program, sourceFile, diagnostics } = createProgramAndSourceFile(path, options, projectReferences);
|
|
||||||
if (diagnostics.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 只输出单个文件
|
|
||||||
realConfig.lifecycle.onBeforeCompile();
|
|
||||||
const emitResult = program.emit(sourceFile);
|
|
||||||
// 是否成功
|
|
||||||
const result = emitResult.emitSkipped;
|
|
||||||
if (result) {
|
|
||||||
console.error(`Emit failed for ${path}!`);
|
|
||||||
realConfig.lifecycle.onAfterCompile();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log(`Emit succeeded. ${compileOnly ? "" : "reload service......"}`);
|
|
||||||
realConfig.lifecycle.onAfterCompile();
|
|
||||||
if (compileOnly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// await restart(); // 只有在队列里面的最后一个文件编译完成后才会重启服务
|
|
||||||
if (processingQueue.length === 1) {
|
|
||||||
await restart();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Waiting for other operations to complete...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const onChangeDebounced = async (path, type) => {
|
|
||||||
if (processingQueue.includes(path)) {
|
|
||||||
console.log("Processing, please wait...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
processingQueue.push(path);
|
|
||||||
await fileChangeHandler(path, type);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.clear();
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
processingQueue = processingQueue.filter((p) => p !== path);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
watcher
|
|
||||||
.on("add", async (path) => {
|
|
||||||
if (startWatching) {
|
|
||||||
await onChangeDebounced(path, "add");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("change", async (path) => {
|
|
||||||
if (startWatching) {
|
|
||||||
await onChangeDebounced(path, "change");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("unlink", async (path) => {
|
|
||||||
if (startWatching) {
|
|
||||||
await onChangeDebounced(path, "remove");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const dispose = async () => {
|
// 初始化
|
||||||
if (shutdown) {
|
realConfig.lifecycle.onInit(realConfig);
|
||||||
await shutdown();
|
// 执行初始编译
|
||||||
}
|
initialCompile();
|
||||||
await watcher.close();
|
// 启动服务器
|
||||||
realConfig.lifecycle.onDispose();
|
serverManager.restart()
|
||||||
};
|
|
||||||
restart()
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
const dispose = async () => {
|
||||||
|
await fileWatcher.dispose();
|
||||||
|
await serverManager.dispose();
|
||||||
|
realConfig.lifecycle.onDispose(realConfig);
|
||||||
|
};
|
||||||
resolve(dispose);
|
resolve(dispose);
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,16 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
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 tslib_1 = require("tslib");
|
||||||
const child_process_1 = require("child_process");
|
const child_process_1 = require("child_process");
|
||||||
const fs_1 = require("fs");
|
const fs_1 = require("fs");
|
||||||
|
|
@ -75,7 +85,7 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
|
||||||
"build-analyze:mp:staging": "${cliBinName} build --target mp --mode staging --analyze",
|
"build-analyze:mp:staging": "${cliBinName} build --target mp --mode staging --analyze",
|
||||||
"build:mp": "${cliBinName} build --target mp --mode production",
|
"build:mp": "${cliBinName} build --target mp --mode production",
|
||||||
"build-analyze:mp": "${cliBinName} build --target mp --mode production --analyze",
|
"build-analyze:mp": "${cliBinName} build --target mp --mode production --analyze",
|
||||||
"build:watch": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json && npm run server:start:watch",
|
"build:watch": "node --stack-size=4096 ./scripts/build.js -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json && npm run server:start:watch",
|
||||||
"start:web": "${cliBinName} start --target web --mode development --devMode frontend",
|
"start:web": "${cliBinName} start --target web --mode development --devMode frontend",
|
||||||
"start:web:server": "${cliBinName} start --target web --mode development",
|
"start:web:server": "${cliBinName} start --target web --mode development",
|
||||||
"start:native": "${cliBinName} start --target rn --mode development --devMode frontend",
|
"start:native": "${cliBinName} start --target rn --mode development --devMode frontend",
|
||||||
|
|
@ -87,7 +97,7 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
|
||||||
"build-sourcemap:web": "${cliBinName} build --target web --mode production --sourcemap",
|
"build-sourcemap:web": "${cliBinName} build --target web --mode production --sourcemap",
|
||||||
"build-analyze:web": "${cliBinName} build --target web --mode production --analyze",
|
"build-analyze:web": "${cliBinName} build --target web --mode production --analyze",
|
||||||
"build-sourcemap-analyze:web": "${cliBinName} build --target web --mode production --sourcemap --analyze",
|
"build-sourcemap-analyze:web": "${cliBinName} build --target web --mode production --sourcemap --analyze",
|
||||||
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json",
|
"build": "node --stack-size=4096 ./scripts/build.js -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json",
|
||||||
"prebuild": "npm run make:locale",
|
"prebuild": "npm run make:locale",
|
||||||
"run:ios": "oak-cli run -p ios",
|
"run:ios": "oak-cli run -p ios",
|
||||||
"run:android": "oak-cli run -p android",
|
"run:android": "oak-cli run -p android",
|
||||||
|
|
@ -300,7 +310,6 @@ function packageJsonContent({ name, version, description, cliName, cliBinName, i
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
exports.packageJsonContent = packageJsonContent;
|
|
||||||
function tsConfigJsonContent() {
|
function tsConfigJsonContent() {
|
||||||
return `{
|
return `{
|
||||||
"extends": "./tsconfig.paths.json",
|
"extends": "./tsconfig.paths.json",
|
||||||
|
|
@ -352,7 +361,6 @@ function tsConfigJsonContent() {
|
||||||
]
|
]
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
exports.tsConfigJsonContent = tsConfigJsonContent;
|
|
||||||
function tsConfigBuildJsonContent() {
|
function tsConfigBuildJsonContent() {
|
||||||
return `{
|
return `{
|
||||||
"extends": "./tsconfig.build.paths.json",
|
"extends": "./tsconfig.build.paths.json",
|
||||||
|
|
@ -389,10 +397,15 @@ function tsConfigBuildJsonContent() {
|
||||||
"test",
|
"test",
|
||||||
"src/pages/**/*",
|
"src/pages/**/*",
|
||||||
"src/components/**/*"
|
"src/components/**/*"
|
||||||
]
|
],
|
||||||
|
"oakBuildChecks": {
|
||||||
|
"context": {
|
||||||
|
"checkAsyncContext": true,
|
||||||
|
"targetModules": ["context/BackendRuntimeContext"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
exports.tsConfigBuildJsonContent = tsConfigBuildJsonContent;
|
|
||||||
function tsConfigPathsJsonContent(deps) {
|
function tsConfigPathsJsonContent(deps) {
|
||||||
const paths = {
|
const paths = {
|
||||||
"@project/*": [
|
"@project/*": [
|
||||||
|
|
@ -417,11 +430,10 @@ function tsConfigPathsJsonContent(deps) {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
baseUrl: "./",
|
baseUrl: "./",
|
||||||
paths,
|
paths,
|
||||||
typeRoots: ["./typings"]
|
typeRoots: ["./typings", "node_modules/@types"]
|
||||||
}
|
}
|
||||||
}, null, '\t');
|
}, null, '\t');
|
||||||
}
|
}
|
||||||
exports.tsConfigPathsJsonContent = tsConfigPathsJsonContent;
|
|
||||||
function tsConfigMpJsonContent() {
|
function tsConfigMpJsonContent() {
|
||||||
return `{
|
return `{
|
||||||
"extends": "./tsconfig.paths.json",
|
"extends": "./tsconfig.paths.json",
|
||||||
|
|
@ -469,7 +481,6 @@ function tsConfigMpJsonContent() {
|
||||||
]
|
]
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
exports.tsConfigMpJsonContent = tsConfigMpJsonContent;
|
|
||||||
function tsConfigWebJsonContent() {
|
function tsConfigWebJsonContent() {
|
||||||
return `{
|
return `{
|
||||||
"extends": "./tsconfig.paths.json",
|
"extends": "./tsconfig.paths.json",
|
||||||
|
|
@ -519,7 +530,6 @@ function tsConfigWebJsonContent() {
|
||||||
]
|
]
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
exports.tsConfigWebJsonContent = tsConfigWebJsonContent;
|
|
||||||
function projectConfigContentWithWeChatMp(oakConfigName, projectname, miniVersion) {
|
function projectConfigContentWithWeChatMp(oakConfigName, projectname, miniVersion) {
|
||||||
return `{
|
return `{
|
||||||
"description": "项目配置文件",
|
"description": "项目配置文件",
|
||||||
|
|
@ -595,7 +605,6 @@ function projectConfigContentWithWeChatMp(oakConfigName, projectname, miniVersio
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
exports.projectConfigContentWithWeChatMp = projectConfigContentWithWeChatMp;
|
|
||||||
function appJsonContentWithWeChatMp(isDev) {
|
function appJsonContentWithWeChatMp(isDev) {
|
||||||
const pages = [
|
const pages = [
|
||||||
'@project/pages/store/list/index',
|
'@project/pages/store/list/index',
|
||||||
|
|
@ -619,25 +628,27 @@ function appJsonContentWithWeChatMp(isDev) {
|
||||||
"sitemapLocation": "sitemap.json"
|
"sitemapLocation": "sitemap.json"
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
exports.appJsonContentWithWeChatMp = appJsonContentWithWeChatMp;
|
|
||||||
function oakConfigContentWithWeChatMp() {
|
function oakConfigContentWithWeChatMp() {
|
||||||
return `{
|
return `{
|
||||||
"theme": {
|
"theme": {
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
exports.oakConfigContentWithWeChatMp = oakConfigContentWithWeChatMp;
|
|
||||||
function oakConfigContentWithWeb() {
|
function oakConfigContentWithWeb() {
|
||||||
return `{
|
return `{
|
||||||
"theme": {
|
"theme": {
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
exports.oakConfigContentWithWeb = oakConfigContentWithWeb;
|
|
||||||
function updateCompilerJsContent(directory, deps) {
|
function updateCompilerJsContent(directory, deps) {
|
||||||
const compilerJsPath = (0, path_1.join)(directory, 'configuration', 'compiler.js');
|
const compilerJsPath = (0, path_1.join)(directory, 'configuration', 'compiler.js');
|
||||||
(0, assert_1.default)((0, fs_1.existsSync)(compilerJsPath));
|
// 只有在有依赖项时才需要修改 compiler.js
|
||||||
if (deps.length > 0) {
|
if (deps.length > 0) {
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (!(0, fs_1.existsSync)(compilerJsPath)) {
|
||||||
|
console.warn(`Warning: ${compilerJsPath} does not exist, skipping compiler.js update`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { ast } = (0, core_1.transformFileSync)(compilerJsPath, { ast: true });
|
const { ast } = (0, core_1.transformFileSync)(compilerJsPath, { ast: true });
|
||||||
const { program } = ast;
|
const { program } = ast;
|
||||||
const { body } = program;
|
const { body } = program;
|
||||||
|
|
@ -692,4 +703,3 @@ function updateCompilerJsContent(directory, deps) {
|
||||||
(0, fs_1.writeFileSync)(compilerJsPath, code);
|
(0, fs_1.writeFileSync)(compilerJsPath, code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.updateCompilerJsContent = updateCompilerJsContent;
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { BaseEntityDict } from "oak-domain";
|
||||||
|
import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore";
|
||||||
|
import { EntityDict } from "oak-domain/lib/types";
|
||||||
|
export type InternalErrorType = 'aspect' | 'trigger' | 'watcher' | 'timer' | 'checkpoint';
|
||||||
|
export type InternalErrorHandler<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> = (ctx: Cxt, type: InternalErrorType, message: string, err: Error) => Promise<void>;
|
||||||
|
export type ExceptionPublisher = (type: string, message: string, err: any) => Promise<void>;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
19
lib/utils.js
19
lib/utils.js
|
|
@ -1,6 +1,14 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
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 tslib_1 = require("tslib");
|
||||||
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
||||||
/**
|
/**
|
||||||
|
|
@ -20,7 +28,6 @@ function findJson(pathArr) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
exports.findJson = findJson;
|
|
||||||
/**
|
/**
|
||||||
* @name 已知前后文取中间文本
|
* @name 已知前后文取中间文本
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -34,7 +41,6 @@ function getStr(str, start, end) {
|
||||||
let res = str.match(reg);
|
let res = str.match(reg);
|
||||||
return res ? res[1] : null;
|
return res ? res[1] : null;
|
||||||
}
|
}
|
||||||
exports.getStr = getStr;
|
|
||||||
/**
|
/**
|
||||||
* @name 差集
|
* @name 差集
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -46,7 +52,6 @@ exports.getStr = getStr;
|
||||||
function difference(current, target) {
|
function difference(current, target) {
|
||||||
return new Set([...target].filter(x => !current.has(x)));
|
return new Set([...target].filter(x => !current.has(x)));
|
||||||
}
|
}
|
||||||
exports.difference = difference;
|
|
||||||
/**
|
/**
|
||||||
* @name 获取交集
|
* @name 获取交集
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -58,7 +63,6 @@ exports.difference = difference;
|
||||||
function intersect(current, target) {
|
function intersect(current, target) {
|
||||||
return new Set([...target].filter(x => current.has(x)));
|
return new Set([...target].filter(x => current.has(x)));
|
||||||
}
|
}
|
||||||
exports.intersect = intersect;
|
|
||||||
/**
|
/**
|
||||||
* @name 获取并集
|
* @name 获取并集
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -70,7 +74,6 @@ exports.intersect = intersect;
|
||||||
function union(current, target) {
|
function union(current, target) {
|
||||||
return new Set([...current, ...target]);
|
return new Set([...current, ...target]);
|
||||||
}
|
}
|
||||||
exports.union = union;
|
|
||||||
/**
|
/**
|
||||||
* @name 格式化json
|
* @name 格式化json
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -81,7 +84,6 @@ exports.union = union;
|
||||||
function formatJsonByFile(data) {
|
function formatJsonByFile(data) {
|
||||||
return JSON.stringify(data, null, 2);
|
return JSON.stringify(data, null, 2);
|
||||||
}
|
}
|
||||||
exports.formatJsonByFile = formatJsonByFile;
|
|
||||||
/**
|
/**
|
||||||
* @name 数组对象去重
|
* @name 数组对象去重
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -98,7 +100,6 @@ function deWeight(arr, type) {
|
||||||
}
|
}
|
||||||
return new Set([...map.values()]);
|
return new Set([...map.values()]);
|
||||||
}
|
}
|
||||||
exports.deWeight = deWeight;
|
|
||||||
/**
|
/**
|
||||||
* @name 随机字符串
|
* @name 随机字符串
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -114,7 +115,6 @@ function randomString(length) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
exports.randomString = randomString;
|
|
||||||
/**
|
/**
|
||||||
* @name 检查当前nodejs运行时版本
|
* @name 检查当前nodejs运行时版本
|
||||||
* @export
|
* @export
|
||||||
|
|
@ -135,4 +135,3 @@ function checkNodeVersion() {
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.checkNodeVersion = checkNodeVersion;
|
|
||||||
|
|
|
||||||
11
package.json
11
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@xuchangzju/oak-cli",
|
"name": "@xuchangzju/oak-cli",
|
||||||
"version": "4.0.25",
|
"version": "4.0.33",
|
||||||
"description": "client for oak framework",
|
"description": "client for oak framework",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
"babel-plugin-module-resolver": "^5.0.0",
|
"babel-plugin-module-resolver": "^5.0.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^8.0.0",
|
"fork-ts-checker-webpack-plugin": "^8.0.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -112,9 +112,9 @@
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mini-css-extract-plugin": "^2.5.3",
|
"mini-css-extract-plugin": "^2.5.3",
|
||||||
"node-watch": "^0.7.4",
|
"node-watch": "^0.7.4",
|
||||||
"oak-backend-base": "^4.1.21",
|
"oak-backend-base": "file:../oak-backend-base",
|
||||||
"oak-domain": "^5.1.27",
|
"oak-domain": "file:../oak-domain",
|
||||||
"oak-frontend-base": "^5.3.34",
|
"oak-frontend-base": "file:../oak-frontend-base",
|
||||||
"parse-asn1": "5.1.6",
|
"parse-asn1": "5.1.6",
|
||||||
"postcss": "^8.4.4",
|
"postcss": "^8.4.4",
|
||||||
"postcss-flexbugs-fixes": "^5.0.2",
|
"postcss-flexbugs-fixes": "^5.0.2",
|
||||||
|
|
@ -145,6 +145,7 @@
|
||||||
"stylelint-webpack-plugin": "^3.2.0",
|
"stylelint-webpack-plugin": "^3.2.0",
|
||||||
"tailwindcss": "^3.0.2",
|
"tailwindcss": "^3.0.2",
|
||||||
"terser-webpack-plugin": "^5.2.5",
|
"terser-webpack-plugin": "^5.2.5",
|
||||||
|
"tsc-alias": "^1.8.16",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"ui-extract-webpack-plugin": "^1.0.0",
|
"ui-extract-webpack-plugin": "^1.0.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ dependencies.forEach(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
analyzeEntities(join(process.cwd(), 'src', 'entities'));
|
const projectEntitiesPath = join(process.cwd(), 'src', 'entities');
|
||||||
|
if (existsSync(projectEntitiesPath)) {
|
||||||
|
analyzeEntities(projectEntitiesPath, 'src/entities');
|
||||||
|
} else {
|
||||||
|
console.warn('no project entities found');
|
||||||
|
}
|
||||||
|
|
||||||
removeSync(join(process.cwd(), 'src', 'oak-app-domain'));
|
removeSync(join(process.cwd(), 'src', 'oak-app-domain'));
|
||||||
buildSchema(join(process.cwd(), 'src', 'oak-app-domain'));
|
buildSchema(join(process.cwd(), 'src', 'oak-app-domain'));
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync, readFileSync, readdirSync, existsSync, mkdirSync } from 'fs';
|
||||||
const { factory } = ts;
|
const { factory } = ts;
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -279,8 +279,22 @@ export async function create(dirName: string, cmd: any) {
|
||||||
checkFileExistsAndCreate(rootPath);
|
checkFileExistsAndCreate(rootPath);
|
||||||
// 复制项目文件
|
// 复制项目文件
|
||||||
if (isModule) {
|
if (isModule) {
|
||||||
// 模块化的项目,只拷贝src和typings目录
|
// 模块化的项目,只拷贝 src 下的内容,但跳过 pages 目录;同时拷贝 typings
|
||||||
copyFolder(join(emptyTemplatePath, 'src'), join(rootPath, 'src'));
|
const templateSrc = join(emptyTemplatePath, 'src');
|
||||||
|
const destSrc = join(rootPath, 'src');
|
||||||
|
// 确保目标 src 目录存在
|
||||||
|
if (!existsSync(destSrc)) {
|
||||||
|
mkdirSync(destSrc, { recursive: true });
|
||||||
|
}
|
||||||
|
const entries = readdirSync(templateSrc, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.name === 'pages') {
|
||||||
|
continue; // 模块模式下跳过 pages
|
||||||
|
}
|
||||||
|
const from = join(templateSrc, entry.name);
|
||||||
|
const to = join(destSrc, entry.name);
|
||||||
|
copyFolder(from, to);
|
||||||
|
}
|
||||||
copyFolder(join(emptyTemplatePath, 'typings'), join(rootPath, 'typings'));
|
copyFolder(join(emptyTemplatePath, 'typings'), join(rootPath, 'typings'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -356,8 +370,24 @@ export async function create(dirName: string, cmd: any) {
|
||||||
tsConfigWebJson,
|
tsConfigWebJson,
|
||||||
checkFileExistsAndCreateType.FILE
|
checkFileExistsAndCreateType.FILE
|
||||||
);
|
);
|
||||||
// 更新configuration/compiler.js
|
// 复制.gitignore
|
||||||
updateCompilerJsContent(rootPath, deps);
|
const gitignoreContent = readFile(join(__dirname, '..', 'template', '.gitignore'));
|
||||||
|
checkFileExistsAndCreate(
|
||||||
|
join(rootPath, '.gitignore'),
|
||||||
|
gitignoreContent,
|
||||||
|
checkFileExistsAndCreateType.FILE
|
||||||
|
);
|
||||||
|
// 复制oak.config.json
|
||||||
|
const oakConfigContent = readFile(join(__dirname, '..', 'template', USER_CONFIG_FILE_NAME));
|
||||||
|
checkFileExistsAndCreate(
|
||||||
|
join(rootPath, USER_CONFIG_FILE_NAME),
|
||||||
|
oakConfigContent,
|
||||||
|
checkFileExistsAndCreateType.FILE
|
||||||
|
);
|
||||||
|
// 更新configuration/compiler.js (仅在非模块化模式下)
|
||||||
|
if (!isModule) {
|
||||||
|
updateCompilerJsContent(rootPath, deps);
|
||||||
|
}
|
||||||
Success(
|
Success(
|
||||||
`${success(
|
`${success(
|
||||||
`Successfully created project ${primary(
|
`Successfully created project ${primary(
|
||||||
|
|
@ -390,9 +420,25 @@ export async function create(dirName: string, cmd: any) {
|
||||||
checkFileExistsAndCreateType.FILE
|
checkFileExistsAndCreateType.FILE
|
||||||
);
|
);
|
||||||
|
|
||||||
renameProject(rootPath, name, title, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_TITLE);
|
// 只在非模块化模式下重命名整个项目(包括web、wechatMp等)
|
||||||
|
if (!isModule) {
|
||||||
|
renameProject(rootPath, name, title, DEFAULT_PROJECT_NAME, DEFAULT_PROJECT_TITLE);
|
||||||
|
} else {
|
||||||
|
// 模块化模式下只更新 package.json 的 name
|
||||||
|
const packageJsonFilePath = join(rootPath, 'package.json');
|
||||||
|
const packageJsonContent = readFileSync(packageJsonFilePath, 'utf-8');
|
||||||
|
const packageJsonJson = JSON.parse(packageJsonContent);
|
||||||
|
packageJsonJson.name = name;
|
||||||
|
const newPackageJsonContent = JSON.stringify(packageJsonJson, undefined, 4);
|
||||||
|
writeFileSync(packageJsonFilePath, newPackageJsonContent);
|
||||||
|
Success(
|
||||||
|
`${success(
|
||||||
|
`Change project name to ${primary(name)}`
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (example) {
|
if (example && !isModule) {
|
||||||
// todo: copy template example files
|
// todo: copy template example files
|
||||||
copyFolder(examplePath, rootPath, true);
|
copyFolder(examplePath, rootPath, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { readdirSync, statSync, writeFileSync, PathLike, existsSync, unlinkSync, mkdirSync, rmdirSync, createReadStream, accessSync, createWriteStream, constants, readFileSync } from 'fs'
|
import { readdirSync, statSync, writeFileSync, PathLike, existsSync, unlinkSync, mkdirSync, rmdirSync, createReadStream, accessSync, createWriteStream, constants, readFileSync, copyFileSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { checkFileExistsAndCreateType } from './enum'
|
import { checkFileExistsAndCreateType } from './enum'
|
||||||
import { Error, error, Warn, warn } from './tip-style'
|
import { Error, error, Warn, warn } from './tip-style'
|
||||||
|
|
@ -141,9 +141,8 @@ export function copyFolder(currentDir: PathLike, targetDir: PathLike, overwrite:
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (file.isFile()) {
|
if (file.isFile()) {
|
||||||
const readStream = createReadStream(copyCurrentFileInfo);
|
// 使用同步复制确保文件完全写入
|
||||||
const writeStream = createWriteStream(copyTargetFileInfo);
|
copyFileSync(copyCurrentFileInfo, copyTargetFileInfo);
|
||||||
readStream.pipe(writeStream);
|
|
||||||
// console.log(`复制文件: ${copyCurrentFileInfo} -> ${copyTargetFileInfo}`);
|
// console.log(`复制文件: ${copyCurrentFileInfo} -> ${copyTargetFileInfo}`);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRunt
|
||||||
export async function initialize<
|
export async function initialize<
|
||||||
ED extends EntityDict & BaseEntityDict,
|
ED extends EntityDict & BaseEntityDict,
|
||||||
Cxt extends BackendRuntimeContext<ED>
|
Cxt extends BackendRuntimeContext<ED>
|
||||||
>(path: string) {
|
>(path: string, ifExists?: 'drop' | 'omit' | 'dropIfNotStatic') {
|
||||||
const appLoader = new AppLoader(path);
|
const appLoader = new AppLoader(path);
|
||||||
await appLoader.mount(true);
|
await appLoader.mount(true);
|
||||||
await appLoader.initialize();
|
await appLoader.initialize(ifExists);
|
||||||
await appLoader.unmount();
|
await appLoader.unmount();
|
||||||
console.log('data initialized');
|
console.log('data initialized');
|
||||||
}
|
}
|
||||||
|
|
@ -14,3 +14,125 @@ async function generateNewId(option?: GenerateIdOption) {
|
||||||
Object.assign(global, {
|
Object.assign(global, {
|
||||||
generateNewId,
|
generateNewId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type LogFormatterProp = {
|
||||||
|
level: "info" | "warn" | "error";
|
||||||
|
caller: NodeJS.CallSite | null;
|
||||||
|
args: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LogFormatter = (props: LogFormatterProp) => any[];
|
||||||
|
|
||||||
|
type PolyfillItem = {
|
||||||
|
id: string;
|
||||||
|
trace: boolean;
|
||||||
|
formatter?: LogFormatter;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 存储所有的 polyfill 配置栈
|
||||||
|
const polyfillStack: PolyfillItem[] = [];
|
||||||
|
const originalConsoleMethods: {
|
||||||
|
info?: typeof console.info;
|
||||||
|
warn?: typeof console.warn;
|
||||||
|
error?: typeof console.error;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
// 获取调用堆栈信息的工厂函数
|
||||||
|
const createGetCallerInfo = (trace: boolean) => (): NodeJS.CallSite | null => {
|
||||||
|
if (!trace) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const originalFunc = Error.prepareStackTrace;
|
||||||
|
let callerInfo: NodeJS.CallSite | null = null;
|
||||||
|
try {
|
||||||
|
const err = new Error();
|
||||||
|
Error.prepareStackTrace = (err, stack) => stack;
|
||||||
|
const stack = err.stack as unknown as NodeJS.CallSite[];
|
||||||
|
const currentFile = stack[0].getFileName();
|
||||||
|
|
||||||
|
for (let i = 1; i < stack.length; i++) {
|
||||||
|
const callSite = stack[i];
|
||||||
|
if (currentFile !== callSite.getFileName()) {
|
||||||
|
callerInfo = callSite;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
Error.prepareStackTrace = originalFunc;
|
||||||
|
return callerInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const polyfillConsole = (id: string, trace: boolean, formatter?: LogFormatter) => {
|
||||||
|
// 检查是否已经添加过相同 id 的 polyfill
|
||||||
|
if (polyfillStack.some(item => item.id === id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一次调用时保存原始方法
|
||||||
|
if (polyfillStack.length === 0) {
|
||||||
|
originalConsoleMethods.info = console.info;
|
||||||
|
originalConsoleMethods.warn = console.warn;
|
||||||
|
originalConsoleMethods.error = console.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新的 polyfill 到栈顶
|
||||||
|
polyfillStack.push({ id, trace, formatter });
|
||||||
|
|
||||||
|
// 重新设置 console 方法
|
||||||
|
["info", "warn", "error"].forEach((level: string) => {
|
||||||
|
const levelStr = level as "info" | "warn" | "error";
|
||||||
|
const originalFunc = originalConsoleMethods[levelStr]!;
|
||||||
|
|
||||||
|
console[levelStr] = function (...args) {
|
||||||
|
let processedArgs = args;
|
||||||
|
|
||||||
|
// 从后往前执行所有 formatter
|
||||||
|
for (let i = polyfillStack.length - 1; i >= 0; i--) {
|
||||||
|
const item = polyfillStack[i];
|
||||||
|
if (item.formatter) {
|
||||||
|
const getCallerInfo = createGetCallerInfo(item.trace);
|
||||||
|
processedArgs = item.formatter({
|
||||||
|
level: levelStr,
|
||||||
|
caller: getCallerInfo(),
|
||||||
|
args: processedArgs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
originalFunc(...processedArgs);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info(`new console polyfill added: ${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 可选:提供移除 polyfill 的功能
|
||||||
|
export const removePolyfill = (id: string) => {
|
||||||
|
const index = polyfillStack.findIndex(item => item.id === id);
|
||||||
|
if (index === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
polyfillStack.splice(index, 1);
|
||||||
|
|
||||||
|
// 如果栈为空,恢复原始方法
|
||||||
|
if (polyfillStack.length === 0) {
|
||||||
|
console.info = originalConsoleMethods.info!;
|
||||||
|
console.warn = originalConsoleMethods.warn!;
|
||||||
|
console.error = originalConsoleMethods.error!;
|
||||||
|
} else {
|
||||||
|
// 否则重新应用所有 polyfill
|
||||||
|
const tempStack = [...polyfillStack];
|
||||||
|
polyfillStack.length = 0;
|
||||||
|
Object.keys(originalConsoleMethods).forEach(level => {
|
||||||
|
const levelStr = level as "info" | "warn" | "error";
|
||||||
|
console[levelStr] = originalConsoleMethods[levelStr]!;
|
||||||
|
});
|
||||||
|
tempStack.forEach(item => {
|
||||||
|
polyfillStack.length = 0; // 清空以便重新添加
|
||||||
|
polyfillConsole(item.id, item.trace, item.formatter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -5,11 +5,11 @@ import PathLib, { join } from 'path';
|
||||||
import Koa from 'koa';
|
import Koa from 'koa';
|
||||||
import KoaRouter from 'koa-router';
|
import KoaRouter from 'koa-router';
|
||||||
import KoaBody from 'koa-body';
|
import KoaBody from 'koa-body';
|
||||||
import logger from 'koa-logger';
|
// import logger from 'koa-logger';
|
||||||
|
|
||||||
import { AppLoader, getClusterInfo, ClusterAppLoader } from 'oak-backend-base';
|
import { AppLoader, getClusterInfo, ClusterAppLoader } from 'oak-backend-base';
|
||||||
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
||||||
import { OakException, Connector, EntityDict, ClusterInfo } from 'oak-domain/lib/types';
|
import { OakException, Connector, EntityDict, ClusterInfo, OpRecord, isOakException } from 'oak-domain/lib/types';
|
||||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
import { AsyncRowStore, AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
import { AsyncRowStore, AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
|
||||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||||
|
|
@ -54,6 +54,20 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
|
||||||
omitTimers?: boolean,
|
omitTimers?: boolean,
|
||||||
routine?: (context: AsyncContext<ED>) => Promise<void>,
|
routine?: (context: AsyncContext<ED>) => Promise<void>,
|
||||||
): Promise<(() => Promise<any>) | any> {
|
): Promise<(() => Promise<any>) | any> {
|
||||||
|
|
||||||
|
// let errorHandler: InternalErrorHandler<ED, Cxt> | undefined = undefined;
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// errorHandler = require(join(
|
||||||
|
// path,
|
||||||
|
// 'lib',
|
||||||
|
// 'configuration',
|
||||||
|
// 'exception'
|
||||||
|
// )).default;
|
||||||
|
// } catch (err) {
|
||||||
|
// // 不存在exception配置
|
||||||
|
// }
|
||||||
|
|
||||||
const serverConfiguration: ServerConfiguration = require(join(
|
const serverConfiguration: ServerConfiguration = require(join(
|
||||||
path,
|
path,
|
||||||
'lib',
|
'lib',
|
||||||
|
|
@ -73,7 +87,8 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
|
||||||
|
|
||||||
const koa = new Koa();
|
const koa = new Koa();
|
||||||
// 使用 koa-logger 中间件打印日志
|
// 使用 koa-logger 中间件打印日志
|
||||||
koa.use(logger());
|
// koa.use(logger());
|
||||||
|
|
||||||
// socket
|
// socket
|
||||||
const httpServer = createServer(koa.callback());
|
const httpServer = createServer(koa.callback());
|
||||||
const socketPath = connector.getSocketPath();
|
const socketPath = connector.getSocketPath();
|
||||||
|
|
@ -200,10 +215,27 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
|
||||||
if (routine) {
|
if (routine) {
|
||||||
// 如果传入了routine,执行完成后就结束
|
// 如果传入了routine,执行完成后就结束
|
||||||
const result = await appLoader.execRoutine(routine);
|
const result = await appLoader.execRoutine(routine);
|
||||||
await appLoader.unmount();
|
// await appLoader.unmount(); // 不卸载,在进程退出时会自动卸载
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (errorHandler && typeof errorHandler === 'function') {
|
||||||
|
// // polyfillConsole("startup", true, (props) => {
|
||||||
|
// // if (props.level === "error") {
|
||||||
|
// // appLoader.execRoutine(async (ctx) => {
|
||||||
|
// // await errorHandler(props.caller, props.args, ctx);
|
||||||
|
// // }).catch((err) => {
|
||||||
|
// // console.warn('执行全局错误处理失败:', err);
|
||||||
|
// // });
|
||||||
|
// // }
|
||||||
|
// // return props.args;
|
||||||
|
// // });
|
||||||
|
// // appLoader.registerInternalErrorHandler(async (ctx, type, msg, err) => {
|
||||||
|
// // await errorHandler(ctx, type, msg, err);
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
appLoader.regAllExceptionHandler()
|
||||||
|
|
||||||
// 否则启动服务器模式
|
// 否则启动服务器模式
|
||||||
koa.use(async (ctx, next) => {
|
koa.use(async (ctx, next) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -212,26 +244,49 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
|
||||||
console.error(err);
|
console.error(err);
|
||||||
const { request } = ctx;
|
const { request } = ctx;
|
||||||
const exception =
|
const exception =
|
||||||
err instanceof OakException
|
isOakException(err)
|
||||||
? err
|
? err
|
||||||
: new OakException<ED>(
|
: new OakException<ED>(
|
||||||
serverConfiguration?.internalExceptionMask ||
|
serverConfiguration?.internalExceptionMask ||
|
||||||
ExceptionMask
|
ExceptionMask
|
||||||
);
|
);
|
||||||
const { body } = connector.serializeException(
|
const { body, headers } = connector.serializeException(
|
||||||
exception,
|
exception,
|
||||||
request.headers,
|
request.headers,
|
||||||
request.body
|
request.body
|
||||||
);
|
);
|
||||||
ctx.response.body = body;
|
ctx.response.body = body;
|
||||||
|
// headers 要拼上
|
||||||
|
Object.keys(headers || {}).forEach(key => {
|
||||||
|
ctx.set(key, headers?.[key])
|
||||||
|
})
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
koa.use(
|
koa.use(
|
||||||
KoaBody(Object.assign({
|
KoaBody(Object.assign({
|
||||||
multipart: true,
|
multipart: true,
|
||||||
}, serverConfiguration.koaBody))
|
}, serverConfiguration.koaBody))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 注册自定义中间件
|
||||||
|
if (serverConfiguration.middleware) {
|
||||||
|
if (Array.isArray(serverConfiguration.middleware)) {
|
||||||
|
serverConfiguration.middleware.forEach((mw) => {
|
||||||
|
koa.use(mw);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (typeof serverConfiguration.middleware === 'function') {
|
||||||
|
const mws = serverConfiguration.middleware(koa);
|
||||||
|
if (!Array.isArray(mws)) {
|
||||||
|
throw new Error('middleware 配置函数必须返回 Koa.Middleware 数组');
|
||||||
|
}
|
||||||
|
mws.forEach((mw) => {
|
||||||
|
koa.use(mw);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
const router = new KoaRouter();
|
const router = new KoaRouter();
|
||||||
|
|
||||||
// 如果是开发环境,允许options
|
// 如果是开发环境,允许options
|
||||||
|
|
@ -240,6 +295,10 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
|
||||||
ctx.set('Access-Control-Allow-Origin', '*');
|
ctx.set('Access-Control-Allow-Origin', '*');
|
||||||
ctx.set('Access-Control-Allow-Headers', corsHeaders.concat(connector.getCorsHeader()));
|
ctx.set('Access-Control-Allow-Headers', corsHeaders.concat(connector.getCorsHeader()));
|
||||||
ctx.set('Access-Control-Allow-Methods', corsMethods);
|
ctx.set('Access-Control-Allow-Methods', corsMethods);
|
||||||
|
if (connector.getCorsExposeHeaders) {
|
||||||
|
const exposeHeaders = connector.getCorsExposeHeaders();
|
||||||
|
ctx.set('Access-Control-Expose-Headers', exposeHeaders);
|
||||||
|
}
|
||||||
if (ctx.method == 'OPTIONS') {
|
if (ctx.method == 'OPTIONS') {
|
||||||
ctx.body = 200;
|
ctx.body = 200;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -278,20 +337,48 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectorCustomAspects = connector.registerCustomAspect ? connector.registerCustomAspect() : null;
|
||||||
|
|
||||||
router.post(connector.getRouter(), async (ctx) => {
|
router.post(connector.getRouter(), async (ctx) => {
|
||||||
const { request } = ctx;
|
const { request } = ctx;
|
||||||
const { contextString, aspectName, data } = connector.parseRequest(
|
const { contextString, aspectName, data } = await connector.parseRequest(
|
||||||
request.headers,
|
request.headers,
|
||||||
request.body,
|
request.body,
|
||||||
request.files
|
request.files
|
||||||
);
|
);
|
||||||
|
|
||||||
const { result, opRecords, message } = await appLoader.execAspect(
|
let result: any;
|
||||||
aspectName,
|
let opRecords: OpRecord<ED>[] = [];
|
||||||
request.headers,
|
let message: string | undefined = undefined;
|
||||||
contextString,
|
|
||||||
data
|
if (connectorCustomAspects &&
|
||||||
);
|
connectorCustomAspects.findIndex(a => a.name === aspectName) >= 0
|
||||||
|
) {
|
||||||
|
// 自定义aspect处理
|
||||||
|
console.log(`调用Connector自定义Aspect: ${aspectName}`);
|
||||||
|
const aspect = connectorCustomAspects!.find(a => a.name === aspectName)!;
|
||||||
|
const res = await aspect.handler(
|
||||||
|
{
|
||||||
|
headers: request.headers,
|
||||||
|
contextString,
|
||||||
|
params: data,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
result = res.result;
|
||||||
|
opRecords = res.opRecords || [];
|
||||||
|
message = res.message;
|
||||||
|
} else {
|
||||||
|
const res = await appLoader.execAspect(
|
||||||
|
aspectName,
|
||||||
|
request.headers,
|
||||||
|
contextString,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
result = res.result;
|
||||||
|
opRecords = res.opRecords || [];
|
||||||
|
message = res.message;
|
||||||
|
}
|
||||||
|
|
||||||
const { body, headers } = await connector.serializeResult(
|
const { body, headers } = await connector.serializeResult(
|
||||||
result,
|
result,
|
||||||
opRecords,
|
opRecords,
|
||||||
|
|
@ -300,6 +387,12 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
ctx.response.body = body;
|
ctx.response.body = body;
|
||||||
|
|
||||||
|
// headers 要拼上
|
||||||
|
Object.keys(headers || {}).forEach(key => {
|
||||||
|
ctx.set(key, headers?.[key])
|
||||||
|
})
|
||||||
|
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -442,16 +535,70 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
|
||||||
appLoader.startTimers();
|
appLoader.startTimers();
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('SIGINT', async () => {
|
let isShutingdown = false;
|
||||||
await appLoader.unmount();
|
const shutdown = async () => {
|
||||||
process.exit(0);
|
if (isShutingdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isShutingdown = true;
|
||||||
|
console.log('服务器正在关闭中...');
|
||||||
|
try {
|
||||||
|
await httpServer.close();
|
||||||
|
await koa.removeAllListeners();
|
||||||
|
await appLoader.execStopRoutines();
|
||||||
|
await appLoader.unmount();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('关闭服务器时出错:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理优雅关闭的统一入口
|
||||||
|
let shutdownStarted = false;
|
||||||
|
const handleShutdown = async (signal: string) => {
|
||||||
|
// 防止重复处理
|
||||||
|
if (shutdownStarted) {
|
||||||
|
console.warn('关闭流程已启动,忽略此信号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shutdownStarted = true;
|
||||||
|
|
||||||
|
console.log(`\n收到 ${signal} 信号,准备关闭服务器...`);
|
||||||
|
|
||||||
|
// 移除所有信号处理器,防止重复触发
|
||||||
|
process.removeAllListeners('SIGINT');
|
||||||
|
process.removeAllListeners('SIGTERM');
|
||||||
|
process.removeAllListeners('SIGQUIT');
|
||||||
|
process.removeAllListeners('SIGHUP');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await shutdown();
|
||||||
|
process.exit(0);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('关闭过程出错:', err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听终止信号进行优雅关闭
|
||||||
|
// SIGINT - Ctrl+C 发送的中断信号
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
handleShutdown('SIGINT');
|
||||||
});
|
});
|
||||||
|
|
||||||
const shutdown = async () => {
|
// SIGTERM - 系统/容器管理器发送的终止信号(Docker、K8s、PM2等)
|
||||||
await httpServer.close();
|
process.on('SIGTERM', () => {
|
||||||
await koa.removeAllListeners();
|
handleShutdown('SIGTERM');
|
||||||
await appLoader.unmount();
|
});
|
||||||
}
|
|
||||||
|
// SIGQUIT - Ctrl+\ 发送的退出信号
|
||||||
|
process.on('SIGQUIT', () => {
|
||||||
|
handleShutdown('SIGQUIT');
|
||||||
|
});
|
||||||
|
|
||||||
|
// SIGHUP - 终端关闭时发送(可选)
|
||||||
|
process.on('SIGHUP', () => {
|
||||||
|
handleShutdown('SIGHUP');
|
||||||
|
});
|
||||||
|
|
||||||
return shutdown
|
return shutdown
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
// timer-manager.ts
|
||||||
|
|
||||||
|
type TimerType = 'timeout' | 'interval' | 'immediate';
|
||||||
|
|
||||||
|
interface TimerRecord {
|
||||||
|
id: NodeJS.Timeout | NodeJS.Immediate;
|
||||||
|
type: TimerType;
|
||||||
|
createdAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GlobalTimerManager {
|
||||||
|
private timers = new Map<NodeJS.Timeout | NodeJS.Immediate, TimerRecord>();
|
||||||
|
private isHooked = false;
|
||||||
|
|
||||||
|
// 保存原始函数
|
||||||
|
private readonly original = {
|
||||||
|
setTimeout: global.setTimeout,
|
||||||
|
setInterval: global.setInterval,
|
||||||
|
setImmediate: global.setImmediate,clearTimeout: global.clearTimeout,
|
||||||
|
clearInterval: global.clearInterval,
|
||||||
|
clearImmediate: global.clearImmediate,};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始拦截全局定时器
|
||||||
|
*/
|
||||||
|
hook(): void {
|
||||||
|
if (this.isHooked) {
|
||||||
|
console.warn('[TimerManager] Already hooked');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
// Hook setTimeout
|
||||||
|
global.setTimeout = function (
|
||||||
|
callback: (...args: any[]) => void,
|
||||||
|
ms?: number,
|
||||||
|
...args: any[]
|
||||||
|
): any {
|
||||||
|
const id = self.original.setTimeout(
|
||||||
|
(...callbackArgs: any[]) => {
|
||||||
|
self.timers.delete(id); // 执行完后移除
|
||||||
|
callback(...callbackArgs);
|
||||||
|
},
|
||||||
|
ms,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'timeout',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Hook setInterval
|
||||||
|
global.setInterval = function (
|
||||||
|
callback: (...args: any[]) => void,
|
||||||
|
ms?: number,
|
||||||
|
...args: any[]
|
||||||
|
): any {
|
||||||
|
const id = self.original.setInterval(callback, ms, ...args);
|
||||||
|
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'interval',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Hook setImmediate
|
||||||
|
global.setImmediate = function (
|
||||||
|
callback: (...args: any[]) => void,
|
||||||
|
...args: any[]
|
||||||
|
): any {
|
||||||
|
const id = self.original.setImmediate(
|
||||||
|
(...callbackArgs: any[]) => {
|
||||||
|
self.timers.delete(id); // 执行完后移除
|
||||||
|
callback(...callbackArgs);
|
||||||
|
},
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
|
||||||
|
self.timers.set(id, {
|
||||||
|
id,
|
||||||
|
type: 'immediate',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Hook clear方法(确保从追踪中移除)
|
||||||
|
global.clearTimeout = ((id: any) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearTimeout(id);
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
global.clearInterval = ((id: any) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearInterval(id);
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
global.clearImmediate = ((id: any) => {
|
||||||
|
self.timers.delete(id);
|
||||||
|
return self.original.clearImmediate(id);
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
this.isHooked = true;
|
||||||
|
console.log('[TimerManager] Hooked global timer functions');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复原始的全局定时器函数
|
||||||
|
*/
|
||||||
|
unhook(): void {
|
||||||
|
if (!this.isHooked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.setTimeout = this.original.setTimeout;
|
||||||
|
global.setInterval = this.original.setInterval;
|
||||||
|
global.setImmediate = this.original.setImmediate;
|
||||||
|
global.clearTimeout = this.original.clearTimeout;
|
||||||
|
global.clearInterval = this.original.clearInterval;
|
||||||
|
global.clearImmediate = this.original.clearImmediate;
|
||||||
|
|
||||||
|
this.isHooked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有被追踪的定时器
|
||||||
|
*/
|
||||||
|
clearAll(): number {
|
||||||
|
const count = this.timers.size;
|
||||||
|
|
||||||
|
this.timers.forEach((record) => {
|
||||||
|
if (record.type === 'immediate') {
|
||||||
|
this.original.clearImmediate.call(null, record.id as NodeJS.Immediate);
|
||||||
|
} else {
|
||||||
|
this.original.clearTimeout.call(null, record.id as NodeJS.Timeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.timers.clear();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按类型清除定时器
|
||||||
|
*/
|
||||||
|
clearByType(type: TimerType): number {
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
this.timers.forEach((record, id) => {
|
||||||
|
if (record.type === type) {
|
||||||
|
if (type === 'immediate') {
|
||||||
|
this.original.clearImmediate.call(null, id as NodeJS.Immediate);
|
||||||
|
} else {
|
||||||
|
this.original.clearTimeout.call(null, id as NodeJS.Timeout);
|
||||||
|
}
|
||||||
|
this.timers.delete(id);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前活跃的定时器数量
|
||||||
|
*/
|
||||||
|
getActiveCount(): number {
|
||||||
|
return this.timers.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取定时器统计信息
|
||||||
|
*/
|
||||||
|
getStats(): Record<TimerType, number> {
|
||||||
|
const stats: Record<TimerType, number> = {
|
||||||
|
timeout: 0,
|
||||||
|
interval: 0,
|
||||||
|
immediate: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.timers.forEach((record) => {
|
||||||
|
stats[record.type]++;
|
||||||
|
});
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有定时器的详细信息(用于调试)
|
||||||
|
*/
|
||||||
|
getTimers(): TimerRecord[] {
|
||||||
|
return Array.from(this.timers.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const timerManager = new GlobalTimerManager();
|
||||||
|
|
||||||
|
export const hookTimers = () => timerManager.hook();
|
||||||
|
export const unhookTimers = () => timerManager.unhook();
|
||||||
|
export const clearAllTimers = () => timerManager.clearAll();
|
||||||
|
export const getTimerStats = () => timerManager.getStats();
|
||||||
1153
src/server/watch.ts
1153
src/server/watch.ts
File diff suppressed because it is too large
Load Diff
|
|
@ -92,7 +92,7 @@ export function packageJsonContent({
|
||||||
"build-analyze:mp:staging": "${cliBinName} build --target mp --mode staging --analyze",
|
"build-analyze:mp:staging": "${cliBinName} build --target mp --mode staging --analyze",
|
||||||
"build:mp": "${cliBinName} build --target mp --mode production",
|
"build:mp": "${cliBinName} build --target mp --mode production",
|
||||||
"build-analyze:mp": "${cliBinName} build --target mp --mode production --analyze",
|
"build-analyze:mp": "${cliBinName} build --target mp --mode production --analyze",
|
||||||
"build:watch": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json && npm run server:start:watch",
|
"build:watch": "node --stack-size=4096 ./scripts/build.js -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json && npm run server:start:watch",
|
||||||
"start:web": "${cliBinName} start --target web --mode development --devMode frontend",
|
"start:web": "${cliBinName} start --target web --mode development --devMode frontend",
|
||||||
"start:web:server": "${cliBinName} start --target web --mode development",
|
"start:web:server": "${cliBinName} start --target web --mode development",
|
||||||
"start:native": "${cliBinName} start --target rn --mode development --devMode frontend",
|
"start:native": "${cliBinName} start --target rn --mode development --devMode frontend",
|
||||||
|
|
@ -104,7 +104,7 @@ export function packageJsonContent({
|
||||||
"build-sourcemap:web": "${cliBinName} build --target web --mode production --sourcemap",
|
"build-sourcemap:web": "${cliBinName} build --target web --mode production --sourcemap",
|
||||||
"build-analyze:web": "${cliBinName} build --target web --mode production --analyze",
|
"build-analyze:web": "${cliBinName} build --target web --mode production --analyze",
|
||||||
"build-sourcemap-analyze:web": "${cliBinName} build --target web --mode production --sourcemap --analyze",
|
"build-sourcemap-analyze:web": "${cliBinName} build --target web --mode production --sourcemap --analyze",
|
||||||
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json",
|
"build": "node --stack-size=4096 ./scripts/build.js -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && npm run copy-config-json",
|
||||||
"prebuild": "npm run make:locale",
|
"prebuild": "npm run make:locale",
|
||||||
"run:ios": "oak-cli run -p ios",
|
"run:ios": "oak-cli run -p ios",
|
||||||
"run:android": "oak-cli run -p android",
|
"run:android": "oak-cli run -p android",
|
||||||
|
|
@ -406,7 +406,13 @@ export function tsConfigBuildJsonContent() {
|
||||||
"test",
|
"test",
|
||||||
"src/pages/**/*",
|
"src/pages/**/*",
|
||||||
"src/components/**/*"
|
"src/components/**/*"
|
||||||
]
|
],
|
||||||
|
"oakBuildChecks": {
|
||||||
|
"context": {
|
||||||
|
"checkAsyncContext": true,
|
||||||
|
"targetModules": ["context/BackendRuntimeContext"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -436,7 +442,7 @@ export function tsConfigPathsJsonContent(deps: string[]) {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
baseUrl: "./",
|
baseUrl: "./",
|
||||||
paths,
|
paths,
|
||||||
typeRoots: ["./typings"]
|
typeRoots: ["./typings", "node_modules/@types"]
|
||||||
}
|
}
|
||||||
}, null, '\t');
|
}, null, '\t');
|
||||||
}
|
}
|
||||||
|
|
@ -663,9 +669,14 @@ export function oakConfigContentWithWeb() {
|
||||||
|
|
||||||
export function updateCompilerJsContent(directory: string, deps: string[]) {
|
export function updateCompilerJsContent(directory: string, deps: string[]) {
|
||||||
const compilerJsPath = join(directory, 'configuration', 'compiler.js');
|
const compilerJsPath = join(directory, 'configuration', 'compiler.js');
|
||||||
assert(existsSync(compilerJsPath));
|
|
||||||
|
// 只有在有依赖项时才需要修改 compiler.js
|
||||||
if (deps.length > 0) {
|
if (deps.length > 0) {
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (!existsSync(compilerJsPath)) {
|
||||||
|
console.warn(`Warning: ${compilerJsPath} does not exist, skipping compiler.js update`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { ast } = transformFileSync(compilerJsPath, { ast: true })!;
|
const { ast } = transformFileSync(compilerJsPath, { ast: true })!;
|
||||||
const { program } = ast!;
|
const { program } = ast!;
|
||||||
const { body } = program!;
|
const { body } = program!;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const { build } = require('oak-domain/lib/compiler/tscBuilder.js')
|
||||||
|
|
||||||
|
const pwd = process.cwd();
|
||||||
|
|
||||||
|
build(pwd, process.argv);
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
const { initialize } = require('@xuchangzju/oak-cli/lib/server/initialize');
|
const { initialize } = require('@xuchangzju/oak-cli/lib/server/initialize');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
const pwd = process.cwd();
|
const pwd = process.cwd();
|
||||||
|
|
||||||
const dropIfExists = process.argv[2];
|
const ifExists = process.argv[2];
|
||||||
// console.log(dropIfExists);
|
assert(!ifExists || ['drop' , 'omit' , 'dropIfNotStatic'].includes(ifExists), "第二个参数只能是'drop' | 'omit' | 'dropIfNotStatic'");
|
||||||
|
|
||||||
initialize(
|
initialize(
|
||||||
pwd,
|
pwd,
|
||||||
!!dropIfExists
|
ifExists || 'dropIfNotStatic'
|
||||||
).then(() => process.exit(0));
|
).then(() => process.exit(0));
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"pageTitle": "添加权限",
|
"pageTitle": "管理权限",
|
||||||
"tips": "请选择模式"
|
"tips": "请选择模式"
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"pageTitle": "权限管理",
|
"pageTitle": "修改用户权限",
|
||||||
"tips": "请选择模式"
|
"tips": "请选择模式"
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Routine } from 'oak-domain/lib/types/Timer';
|
||||||
|
import { EntityDict } from '@oak-app-domain';
|
||||||
|
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||||
|
|
||||||
|
// process.on('uncaughtException', (err) => {
|
||||||
|
// console.error(`Caught exception: ${err}`);
|
||||||
|
// // Optionally, you can exit the process or perform cleanup
|
||||||
|
// });
|
||||||
|
|
||||||
|
// process.on('unhandledRejection', (err) => {
|
||||||
|
// console.error(`Caught rejection: ${err}`);
|
||||||
|
// // Optionally, you can exit the process or perform cleanup
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
const stopRoutines: Array<Routine<EntityDict, keyof EntityDict, BackendRuntimeContext>> = [
|
||||||
|
{
|
||||||
|
name: '示例性routine_stop',
|
||||||
|
routine: async (context, env) => {
|
||||||
|
console.log('示例性routine执行,请在src/routine/stop.ts中关闭');
|
||||||
|
return context.opResult;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default stopRoutines;
|
||||||
|
|
@ -31,6 +31,8 @@ export default function render(props: WebComponentProps<EntityDict, 'user', fals
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (urlAvailable) {
|
switch (urlAvailable) {
|
||||||
|
// 现在不在menu里面的页面暂时不检查,自己保证console上下文切换时的处理 by Xc
|
||||||
|
case undefined:
|
||||||
case true: {
|
case true: {
|
||||||
return (
|
return (
|
||||||
<Layout className={Styles.mixPanel}>
|
<Layout className={Styles.mixPanel}>
|
||||||
|
|
@ -89,8 +91,7 @@ export default function render(props: WebComponentProps<EntityDict, 'user', fals
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
} /* {
|
||||||
case undefined: {
|
|
||||||
return (
|
return (
|
||||||
<Layout className={Styles.mixPanel}>
|
<Layout className={Styles.mixPanel}>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
@ -112,6 +113,6 @@ export default function render(props: WebComponentProps<EntityDict, 'user', fals
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue