befly 3.3.5 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/checks/conflict.ts +6 -6
- package/checks/table.ts +2 -2
- package/commands/build.ts +8 -4
- package/commands/dev.ts +19 -30
- package/commands/start.ts +24 -8
- package/commands/syncDb/index.ts +3 -3
- package/commands/syncDb/table.ts +1 -1
- package/commands/syncMenu.ts +40 -28
- package/config/env.ts +1 -17
- package/lifecycle/checker.ts +3 -3
- package/lifecycle/cluster-worker.ts +49 -0
- package/lifecycle/cluster.ts +31 -18
- package/lifecycle/lifecycle.ts +2 -2
- package/lifecycle/loader.ts +5 -5
- package/main.ts +2 -2
- package/package.json +3 -2
- package/paths.ts +125 -19
- package/router/static.ts +2 -2
- package/util.ts +3 -3
- /package/{config/menu.json → menu.json} +0 -0
package/checks/conflict.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { relative, basename } from 'pathe';
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
|
-
import {
|
|
8
|
+
import { projectPluginDir, coreTableDir, projectTableDir, projectApiDir } from '../paths.js';
|
|
9
9
|
import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -41,7 +41,7 @@ async function collectCorePlugins(registry: ResourceRegistry): Promise<void> {
|
|
|
41
41
|
try {
|
|
42
42
|
const glob = new Bun.Glob('*.ts');
|
|
43
43
|
for await (const file of glob.scan({
|
|
44
|
-
cwd:
|
|
44
|
+
cwd: projectPluginDir,
|
|
45
45
|
onlyFiles: true,
|
|
46
46
|
absolute: true
|
|
47
47
|
})) {
|
|
@@ -77,7 +77,7 @@ async function collectAddonResources(addonName: string, registry: ResourceRegist
|
|
|
77
77
|
const glob = new Bun.Glob('*.json');
|
|
78
78
|
|
|
79
79
|
for await (const file of glob.scan({
|
|
80
|
-
cwd:
|
|
80
|
+
cwd: addonTablesDir,
|
|
81
81
|
onlyFiles: true,
|
|
82
82
|
absolute: true
|
|
83
83
|
})) {
|
|
@@ -183,7 +183,7 @@ async function collectUserResources(registry: ResourceRegistry): Promise<string[
|
|
|
183
183
|
const conflicts: string[] = [];
|
|
184
184
|
|
|
185
185
|
// 收集用户表定义
|
|
186
|
-
const userTablesDir =
|
|
186
|
+
const userTablesDir = projectTableDir;
|
|
187
187
|
try {
|
|
188
188
|
const glob = new Bun.Glob('*.json');
|
|
189
189
|
for await (const file of glob.scan({
|
|
@@ -219,7 +219,7 @@ async function collectUserResources(registry: ResourceRegistry): Promise<string[
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// 收集用户 API 路由
|
|
222
|
-
const userApisDir =
|
|
222
|
+
const userApisDir = projectApiDir;
|
|
223
223
|
try {
|
|
224
224
|
const glob = new Bun.Glob('**/*.ts');
|
|
225
225
|
for await (const file of glob.scan({
|
|
@@ -255,7 +255,7 @@ async function collectUserResources(registry: ResourceRegistry): Promise<string[
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
// 收集用户插件
|
|
258
|
-
const userPluginsDir =
|
|
258
|
+
const userPluginsDir = projectPluginDir;
|
|
259
259
|
try {
|
|
260
260
|
const glob = new Bun.Glob('*.ts');
|
|
261
261
|
for await (const file of glob.scan({
|
package/checks/table.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { basename } from 'pathe';
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { parseRule } from '../util.js';
|
|
9
|
-
import {
|
|
9
|
+
import { projectTableDir } from '../paths.js';
|
|
10
10
|
import { scanAddons, getAddonDir } from '../util.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -69,7 +69,7 @@ export default async function (): Promise<boolean> {
|
|
|
69
69
|
|
|
70
70
|
// 收集项目表字段定义文件
|
|
71
71
|
for await (const file of tablesGlob.scan({
|
|
72
|
-
cwd:
|
|
72
|
+
cwd: projectTableDir,
|
|
73
73
|
absolute: true,
|
|
74
74
|
onlyFiles: true
|
|
75
75
|
})) {
|
package/commands/build.ts
CHANGED
|
@@ -28,20 +28,24 @@ interface BuildOptions {
|
|
|
28
28
|
export async function buildCommand(options: BuildOptions) {
|
|
29
29
|
try {
|
|
30
30
|
const projectRoot = getProjectRoot();
|
|
31
|
-
const mainFile = join(projectRoot, 'main.ts');
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// 验证是否在项目目录下
|
|
33
|
+
const packageJsonPath = join(projectRoot, 'package.json');
|
|
34
|
+
if (!existsSync(packageJsonPath)) {
|
|
35
|
+
Logger.error('未找到 package.json 文件,请确保在项目目录下');
|
|
35
36
|
process.exit(1);
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
// 使用内置默认入口文件
|
|
40
|
+
const entryFile = join(import.meta.dir, '..', 'entry.ts');
|
|
41
|
+
|
|
38
42
|
const spinner = ora({
|
|
39
43
|
text: '正在构建项目...',
|
|
40
44
|
color: 'cyan',
|
|
41
45
|
spinner: 'dots'
|
|
42
46
|
}).start();
|
|
43
47
|
|
|
44
|
-
const args = ['build',
|
|
48
|
+
const args = ['build', entryFile, '--outdir', options.outdir, '--target', 'bun'];
|
|
45
49
|
|
|
46
50
|
if (options.minify) {
|
|
47
51
|
args.push('--minify');
|
package/commands/dev.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { join } from 'pathe';
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
|
+
import { Befly } from '../main.js';
|
|
8
9
|
|
|
9
10
|
interface DevOptions {
|
|
10
11
|
port: string;
|
|
@@ -27,10 +28,11 @@ function getProjectRoot(): string {
|
|
|
27
28
|
export async function devCommand(options: DevOptions) {
|
|
28
29
|
try {
|
|
29
30
|
const projectRoot = getProjectRoot();
|
|
30
|
-
const mainFile = join(projectRoot, 'main.ts');
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// 验证是否在 Befly 项目目录下
|
|
33
|
+
const packageJsonPath = join(projectRoot, 'package.json');
|
|
34
|
+
if (!existsSync(packageJsonPath)) {
|
|
35
|
+
Logger.error('未找到 package.json 文件,请确保在项目目录下');
|
|
34
36
|
process.exit(1);
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -54,38 +56,25 @@ export async function devCommand(options: DevOptions) {
|
|
|
54
56
|
Logger.info(`环境变量文件: .env.development\n`);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
//
|
|
59
|
+
// 直接启动 Befly 实例
|
|
60
|
+
const app = new Befly();
|
|
61
|
+
const server = await app.listen();
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
stdout: 'inherit',
|
|
62
|
-
stderr: 'inherit',
|
|
63
|
-
stdin: 'inherit',
|
|
64
|
-
env: {
|
|
65
|
-
// ...process.env,
|
|
66
|
-
// NODE_ENV: 'development',
|
|
67
|
-
// APP_PORT: options.port,
|
|
68
|
-
// APP_HOST: options.host,
|
|
69
|
-
// LOG_DEBUG: options.verbose ? '1' : process.env.LOG_DEBUG,
|
|
70
|
-
// FORCE_COLOR: '1'
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// 添加信号处理,确保优雅关闭
|
|
75
|
-
const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP'];
|
|
63
|
+
// 设置信号处理,确保优雅关闭
|
|
64
|
+
const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];
|
|
76
65
|
signals.forEach((signal) => {
|
|
77
|
-
process.on(signal, () => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
process.on(signal, async () => {
|
|
67
|
+
Logger.info(`\n收到 ${signal} 信号,正在关闭开发服务器...`);
|
|
68
|
+
try {
|
|
69
|
+
server.stop(true);
|
|
70
|
+
Logger.info('开发服务器已关闭');
|
|
71
|
+
process.exit(0);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
Logger.error('关闭开发服务器失败:', error);
|
|
82
74
|
process.exit(1);
|
|
83
|
-
}
|
|
75
|
+
}
|
|
84
76
|
});
|
|
85
77
|
});
|
|
86
|
-
|
|
87
|
-
const exitCode = await proc.exited;
|
|
88
|
-
process.exit(exitCode || 0);
|
|
89
78
|
} catch (error) {
|
|
90
79
|
Logger.error('启动开发服务器失败:');
|
|
91
80
|
console.error(error);
|
package/commands/start.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from 'pathe';
|
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { ClusterManager } from '../lifecycle/cluster.js';
|
|
9
|
+
import { Befly } from '../main.js';
|
|
9
10
|
|
|
10
11
|
function getProjectRoot(): string {
|
|
11
12
|
let current = process.cwd();
|
|
@@ -28,10 +29,11 @@ interface StartOptions {
|
|
|
28
29
|
export async function startCommand(options: StartOptions) {
|
|
29
30
|
try {
|
|
30
31
|
const projectRoot = getProjectRoot();
|
|
31
|
-
const mainFile = join(projectRoot, 'main.ts');
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// 验证是否在项目目录下
|
|
34
|
+
const packageJsonPath = join(projectRoot, 'package.json');
|
|
35
|
+
if (!existsSync(packageJsonPath)) {
|
|
36
|
+
Logger.error('未找到 package.json 文件,请确保在项目目录下');
|
|
35
37
|
process.exit(1);
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -49,8 +51,7 @@ export async function startCommand(options: StartOptions) {
|
|
|
49
51
|
instances,
|
|
50
52
|
startPort: parseInt(options.port),
|
|
51
53
|
host: options.host,
|
|
52
|
-
projectRoot
|
|
53
|
-
mainFile
|
|
54
|
+
projectRoot
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
await clusterManager.start();
|
|
@@ -67,10 +68,25 @@ export async function startCommand(options: StartOptions) {
|
|
|
67
68
|
Logger.info(`环境变量文件: .env.production\n`);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
//
|
|
71
|
-
|
|
71
|
+
// 直接启动 Befly 实例
|
|
72
|
+
const app = new Befly();
|
|
73
|
+
const server = await app.listen();
|
|
72
74
|
|
|
73
|
-
//
|
|
75
|
+
// 设置信号处理,确保优雅关闭
|
|
76
|
+
const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];
|
|
77
|
+
signals.forEach((signal) => {
|
|
78
|
+
process.on(signal, async () => {
|
|
79
|
+
Logger.info(`\n收到 ${signal} 信号,正在关闭生产服务器...`);
|
|
80
|
+
try {
|
|
81
|
+
server.stop(true);
|
|
82
|
+
Logger.info('生产服务器已关闭');
|
|
83
|
+
process.exit(0);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
Logger.error('关闭生产服务器失败:', error);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
74
90
|
}
|
|
75
91
|
} catch (error) {
|
|
76
92
|
Logger.error('启动失败:');
|
package/commands/syncDb/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { Env } from '../../config/env.js';
|
|
|
14
14
|
import { scanAddons, addonDirExists, getAddonDir } from '../../util.js';
|
|
15
15
|
import { Database } from '../../lib/database.js';
|
|
16
16
|
import checkTable from '../../checks/table.js';
|
|
17
|
-
import {
|
|
17
|
+
import { coreTableDir, projectTableDir } from '../../paths.js';
|
|
18
18
|
|
|
19
19
|
// 导入模块化的功能
|
|
20
20
|
import { ensureDbVersion } from './version.js';
|
|
@@ -81,9 +81,9 @@ export const SyncDb = async (): Promise<void> => {
|
|
|
81
81
|
const tablesGlob = new Bun.Glob('*.json');
|
|
82
82
|
const directories: Array<{ path: string; type: 'core' | 'app' | 'addon'; addonName?: string }> = [
|
|
83
83
|
// 1. core 框架表(core_ 前缀)
|
|
84
|
-
{ path:
|
|
84
|
+
{ path: coreTableDir, type: 'core' },
|
|
85
85
|
// 2. 项目表(无前缀)
|
|
86
|
-
{ path:
|
|
86
|
+
{ path: projectTableDir, type: 'app' }
|
|
87
87
|
];
|
|
88
88
|
|
|
89
89
|
// 添加所有 addon 的 tables 目录(addon_{name}_ 前缀)
|
package/commands/syncDb/table.ts
CHANGED
|
@@ -80,7 +80,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
|
|
|
80
80
|
|
|
81
81
|
if (isStringOrArrayType(fieldType) && existingColumns[dbFieldName].length) {
|
|
82
82
|
if (existingColumns[dbFieldName].length! > fieldMax) {
|
|
83
|
-
Logger.warn(`[跳过危险变更] ${tableName}.${dbFieldName} 长度收缩 ${existingColumns[dbFieldName].length} -> ${fieldMax}
|
|
83
|
+
Logger.warn(`[跳过危险变更] ${tableName}.${dbFieldName} 长度收缩 ${existingColumns[dbFieldName].length} -> ${fieldMax} 已被跳过`);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
package/commands/syncMenu.ts
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* 说明:根据 menu.json 配置文件增量同步菜单数据(最多2级:父级和子级)
|
|
4
4
|
*
|
|
5
5
|
* 流程:
|
|
6
|
-
* 1. 读取 core
|
|
7
|
-
* 2.
|
|
8
|
-
* 3.
|
|
9
|
-
* 4.
|
|
10
|
-
* 5.
|
|
11
|
-
* 6.
|
|
12
|
-
* 7.
|
|
6
|
+
* 1. 读取 core/config/menu.json 和项目根目录的 menu.json 配置文件
|
|
7
|
+
* 2. core 配置优先覆盖项目配置(根据 path 匹配)
|
|
8
|
+
* 3. 文件不存在或格式错误时默认为空数组
|
|
9
|
+
* 4. 子级菜单自动追加父级路径作为前缀
|
|
10
|
+
* 5. 根据菜单的 path 字段检查是否存在
|
|
11
|
+
* 6. 存在则更新其他字段(name、icon、sort、type、pid)
|
|
12
|
+
* 7. 不存在则新增菜单记录
|
|
13
|
+
* 8. 强制删除配置中不存在的菜单记录
|
|
13
14
|
* 注:state 字段由框架自动管理(1=正常,2=禁用,0=删除)
|
|
14
15
|
*/
|
|
15
16
|
|
|
@@ -17,7 +18,7 @@ import { Logger } from '../lib/logger.js';
|
|
|
17
18
|
import { Database } from '../lib/database.js';
|
|
18
19
|
import { join } from 'pathe';
|
|
19
20
|
import { existsSync } from 'node:fs';
|
|
20
|
-
import {
|
|
21
|
+
import { coreDIr, projectDir } from '../paths.js';
|
|
21
22
|
|
|
22
23
|
interface SyncMenuOptions {
|
|
23
24
|
plan?: boolean;
|
|
@@ -34,35 +35,46 @@ interface MenuConfig {
|
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* 读取菜单配置文件
|
|
38
|
+
* 如果文件不存在或不是数组格式,返回空数组
|
|
37
39
|
*/
|
|
38
40
|
async function readMenuConfig(filePath: string): Promise<MenuConfig[]> {
|
|
39
41
|
try {
|
|
40
42
|
if (!existsSync(filePath)) {
|
|
43
|
+
Logger.warn(`菜单配置文件不存在: ${filePath},使用空数组`);
|
|
41
44
|
return [];
|
|
42
45
|
}
|
|
46
|
+
|
|
43
47
|
const file = Bun.file(filePath);
|
|
44
|
-
|
|
48
|
+
const content = await file.json();
|
|
49
|
+
|
|
50
|
+
// 验证是否为数组
|
|
51
|
+
if (!Array.isArray(content)) {
|
|
52
|
+
Logger.warn(`菜单配置文件格式错误(非数组): ${filePath},使用空数组`);
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return content;
|
|
45
57
|
} catch (error: any) {
|
|
46
|
-
Logger.warn(`读取菜单配置失败: ${filePath}
|
|
58
|
+
Logger.warn(`读取菜单配置失败: ${filePath},使用空数组`, error.message);
|
|
47
59
|
return [];
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
/**
|
|
52
|
-
* 合并菜单配置(core
|
|
64
|
+
* 合并菜单配置(core 优先覆盖项目)
|
|
53
65
|
* 支持二级菜单结构:父级和子级
|
|
54
66
|
*/
|
|
55
|
-
function mergeMenuConfigs(
|
|
67
|
+
function mergeMenuConfigs(projectMenus: MenuConfig[], coreMenus: MenuConfig[]): MenuConfig[] {
|
|
56
68
|
const menuMap = new Map<string, MenuConfig>();
|
|
57
69
|
|
|
58
|
-
// 1.
|
|
59
|
-
for (const menu of
|
|
70
|
+
// 1. 先添加项目菜单
|
|
71
|
+
for (const menu of projectMenus) {
|
|
60
72
|
if (menu.path) {
|
|
61
73
|
menuMap.set(menu.path, { ...menu });
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
// 2. core 菜单覆盖同 path
|
|
77
|
+
// 2. core 菜单覆盖同 path 的项目菜单
|
|
66
78
|
for (const menu of coreMenus) {
|
|
67
79
|
if (menu.path) {
|
|
68
80
|
menuMap.set(menu.path, { ...menu });
|
|
@@ -78,10 +90,10 @@ function mergeMenuConfigs(tplMenus: MenuConfig[], coreMenus: MenuConfig[]): Menu
|
|
|
78
90
|
if (menu.children && menu.children.length > 0) {
|
|
79
91
|
const childMap = new Map<string, MenuConfig>();
|
|
80
92
|
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
for (const child of
|
|
93
|
+
// 先添加项目的子菜单
|
|
94
|
+
const projectMenu = projectMenus.find((m) => m.path === menu.path);
|
|
95
|
+
if (projectMenu?.children) {
|
|
96
|
+
for (const child of projectMenu.children) {
|
|
85
97
|
if (child.path) {
|
|
86
98
|
childMap.set(child.path, { ...child });
|
|
87
99
|
}
|
|
@@ -269,8 +281,8 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}) {
|
|
|
269
281
|
try {
|
|
270
282
|
if (options.plan) {
|
|
271
283
|
Logger.info('[计划] 同步菜单配置到数据库(plan 模式不执行)');
|
|
272
|
-
Logger.info('[计划] 1. 读取 core
|
|
273
|
-
Logger.info('[计划] 2. 合并菜单配置(core
|
|
284
|
+
Logger.info('[计划] 1. 读取 core/config/menu.json 和项目根目录 menu.json');
|
|
285
|
+
Logger.info('[计划] 2. 合并菜单配置(core 优先覆盖项目)');
|
|
274
286
|
Logger.info('[计划] 3. 子级菜单自动追加父级路径前缀');
|
|
275
287
|
Logger.info('[计划] 4. 根据 path 检查菜单是否存在');
|
|
276
288
|
Logger.info('[计划] 5. 存在则更新,不存在则新增');
|
|
@@ -283,21 +295,21 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}) {
|
|
|
283
295
|
|
|
284
296
|
// 1. 读取两个配置文件
|
|
285
297
|
Logger.info('=== 步骤 1: 读取菜单配置文件 ===');
|
|
286
|
-
const
|
|
287
|
-
const coreMenuPath = join(
|
|
298
|
+
const projectMenuPath = join(projectDir, 'menu.json');
|
|
299
|
+
const coreMenuPath = join(coreDIr, 'menu.json');
|
|
288
300
|
|
|
289
|
-
Logger.info(`
|
|
301
|
+
Logger.info(` 项目路径: ${projectMenuPath}`);
|
|
290
302
|
Logger.info(` core 路径: ${coreMenuPath}`);
|
|
291
303
|
|
|
292
|
-
const
|
|
304
|
+
const projectMenus = await readMenuConfig(projectMenuPath);
|
|
293
305
|
const coreMenus = await readMenuConfig(coreMenuPath);
|
|
294
306
|
|
|
295
|
-
Logger.info(`✅
|
|
307
|
+
Logger.info(`✅ 项目配置: ${projectMenus.length} 个父级菜单`);
|
|
296
308
|
Logger.info(`✅ core 配置: ${coreMenus.length} 个父级菜单`);
|
|
297
309
|
|
|
298
310
|
// 2. 合并菜单配置
|
|
299
|
-
Logger.info('\n=== 步骤 2: 合并菜单配置(core
|
|
300
|
-
const mergedMenus = mergeMenuConfigs(
|
|
311
|
+
Logger.info('\n=== 步骤 2: 合并菜单配置(core 优先覆盖项目) ===');
|
|
312
|
+
const mergedMenus = mergeMenuConfigs(projectMenus, coreMenus);
|
|
301
313
|
Logger.info(`✅ 合并后共有 ${mergedMenus.length} 个父级菜单`);
|
|
302
314
|
|
|
303
315
|
// 打印合并后的菜单结构
|
package/config/env.ts
CHANGED
|
@@ -116,16 +116,6 @@ export interface EnvConfig {
|
|
|
116
116
|
MAIL_SENDER: string;
|
|
117
117
|
/** 发件人地址 */
|
|
118
118
|
MAIL_ADDRESS: string;
|
|
119
|
-
|
|
120
|
-
// ========== 同步脚本配置 ==========
|
|
121
|
-
/** 是否合并 ALTER 语句 */
|
|
122
|
-
SYNC_MERGE_ALTER: string;
|
|
123
|
-
/** 是否同步在线索引 */
|
|
124
|
-
SYNC_ONLINE_INDEX: string;
|
|
125
|
-
/** 是否禁止字段缩小 */
|
|
126
|
-
SYNC_DISALLOW_SHRINK: string;
|
|
127
|
-
/** 是否允许类型变更 */
|
|
128
|
-
SYNC_ALLOW_TYPE_CHANGE: string;
|
|
129
119
|
}
|
|
130
120
|
|
|
131
121
|
/**
|
|
@@ -208,11 +198,5 @@ export const Env: EnvConfig = {
|
|
|
208
198
|
MAIL_USER: getEnv('MAIL_USER', ''),
|
|
209
199
|
MAIL_PASS: getEnv('MAIL_PASS', ''),
|
|
210
200
|
MAIL_SENDER: getEnv('MAIL_SENDER', ''),
|
|
211
|
-
MAIL_ADDRESS: getEnv('MAIL_ADDRESS', '')
|
|
212
|
-
|
|
213
|
-
// ========== 同步脚本配置 ==========
|
|
214
|
-
SYNC_MERGE_ALTER: getEnv('SYNC_MERGE_ALTER', 'false'),
|
|
215
|
-
SYNC_ONLINE_INDEX: getEnv('SYNC_ONLINE_INDEX', 'false'),
|
|
216
|
-
SYNC_DISALLOW_SHRINK: getEnv('SYNC_DISALLOW_SHRINK', 'true'),
|
|
217
|
-
SYNC_ALLOW_TYPE_CHANGE: getEnv('SYNC_ALLOW_TYPE_CHANGE', 'false')
|
|
201
|
+
MAIL_ADDRESS: getEnv('MAIL_ADDRESS', '')
|
|
218
202
|
};
|
package/lifecycle/checker.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { join, basename } from 'pathe';
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { calcPerfTime } from '../util.js';
|
|
9
|
-
import {
|
|
9
|
+
import { coreCheckDir } from '../paths.js';
|
|
10
10
|
import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -31,7 +31,7 @@ export class Checker {
|
|
|
31
31
|
|
|
32
32
|
// 1. 优先执行资源冲突检测(如果存在)
|
|
33
33
|
try {
|
|
34
|
-
const conflictCheckPath = join(
|
|
34
|
+
const conflictCheckPath = join(coreCheckDir, 'conflict.ts');
|
|
35
35
|
const conflictCheckFile = Bun.file(conflictCheckPath);
|
|
36
36
|
|
|
37
37
|
if (await conflictCheckFile.exists()) {
|
|
@@ -71,7 +71,7 @@ export class Checker {
|
|
|
71
71
|
|
|
72
72
|
// 2. 检查目录列表:先核心,后项目,最后 addons
|
|
73
73
|
// 检查所有 checks 目录
|
|
74
|
-
const checkDirs = [{ path:
|
|
74
|
+
const checkDirs = [{ path: coreCheckDir, type: 'core' as const }]; // 添加所有 addon 的 checks 目录
|
|
75
75
|
const addons = scanAddons();
|
|
76
76
|
for (const addon of addons) {
|
|
77
77
|
if (addonDirExists(addon, 'checks')) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cluster Worker 入口
|
|
3
|
+
* 由 ClusterManager 启动的子进程入口文件
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Befly } from '../main.js';
|
|
7
|
+
import { Logger } from '../lib/logger.js';
|
|
8
|
+
|
|
9
|
+
// 启动 Befly 实例
|
|
10
|
+
const app = new Befly();
|
|
11
|
+
const server = await app.listen();
|
|
12
|
+
|
|
13
|
+
// Bun 原生信号处理:当收到 SIGTERM/SIGINT 时优雅关闭
|
|
14
|
+
const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT'];
|
|
15
|
+
|
|
16
|
+
signals.forEach((signal) => {
|
|
17
|
+
process.on(signal, async () => {
|
|
18
|
+
const workerId = process.env.CLUSTER_WORKER_ID || 'unknown';
|
|
19
|
+
Logger.info(`Worker ${workerId} 收到 ${signal} 信号,正在关闭...`);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Bun Server 的 stop() 方法:关闭服务器
|
|
23
|
+
// 参数 true 表示强制关闭(不等待现有连接)
|
|
24
|
+
server.stop(true);
|
|
25
|
+
Logger.info(`Worker ${workerId} HTTP 服务器已关闭`);
|
|
26
|
+
|
|
27
|
+
// 给予短暂时间让资源清理完成
|
|
28
|
+
await Bun.sleep(100);
|
|
29
|
+
|
|
30
|
+
process.exit(0);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
Logger.error(`Worker ${workerId} 关闭失败:`, error);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// 处理未捕获的异常,防止进程意外退出
|
|
39
|
+
process.on('uncaughtException', (error) => {
|
|
40
|
+
const workerId = process.env.CLUSTER_WORKER_ID || 'unknown';
|
|
41
|
+
Logger.error(`Worker ${workerId} 发生未捕获异常:`, error);
|
|
42
|
+
// 不退出进程,让 ClusterManager 决定是否重启
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
process.on('unhandledRejection', (reason) => {
|
|
46
|
+
const workerId = process.env.CLUSTER_WORKER_ID || 'unknown';
|
|
47
|
+
Logger.error(`Worker ${workerId} 发生未处理的 Promise 拒绝:`, reason);
|
|
48
|
+
// 不退出进程,让 ClusterManager 决定是否重启
|
|
49
|
+
});
|
package/lifecycle/cluster.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { join } from 'pathe';
|
|
7
7
|
import type { Subprocess } from 'bun';
|
|
8
8
|
import { Logger } from '../lib/logger.js';
|
|
9
|
+
import { Befly } from '../main.js';
|
|
9
10
|
|
|
10
11
|
export interface ClusterOptions {
|
|
11
12
|
/** 实例数量(数字或 'max') */
|
|
@@ -16,8 +17,6 @@ export interface ClusterOptions {
|
|
|
16
17
|
host: string;
|
|
17
18
|
/** 项目根目录 */
|
|
18
19
|
projectRoot: string;
|
|
19
|
-
/** main.ts 文件路径 */
|
|
20
|
-
mainFile: string;
|
|
21
20
|
/** 环境变量 */
|
|
22
21
|
env?: Record<string, string>;
|
|
23
22
|
}
|
|
@@ -88,14 +87,14 @@ export class ClusterManager {
|
|
|
88
87
|
* 启动单个 Worker
|
|
89
88
|
*/
|
|
90
89
|
private spawnWorker(id: number, port: number): void {
|
|
91
|
-
const { projectRoot,
|
|
90
|
+
const { projectRoot, host, env = {} } = this.options;
|
|
92
91
|
|
|
93
92
|
Logger.info(`启动 Worker ${id} (端口 ${port})...`);
|
|
94
93
|
|
|
95
|
-
//
|
|
96
|
-
const
|
|
94
|
+
// 使用内置的 worker 入口文件
|
|
95
|
+
const workerFile = join(import.meta.dir, 'cluster-worker.ts');
|
|
97
96
|
|
|
98
|
-
const proc = Bun.spawn(['bun', 'run', '--env-file=.env.production',
|
|
97
|
+
const proc = Bun.spawn(['bun', 'run', '--env-file=.env.production', workerFile], {
|
|
99
98
|
cwd: projectRoot,
|
|
100
99
|
stdout: 'inherit',
|
|
101
100
|
stderr: 'inherit',
|
|
@@ -208,24 +207,38 @@ export class ClusterManager {
|
|
|
208
207
|
// 向所有 Worker 发送 SIGTERM
|
|
209
208
|
for (const [id, worker] of this.workers.entries()) {
|
|
210
209
|
Logger.info(`关闭 Worker ${id} (端口 ${worker.port})...`);
|
|
211
|
-
|
|
210
|
+
try {
|
|
211
|
+
worker.process.kill('SIGTERM');
|
|
212
|
+
} catch (error) {
|
|
213
|
+
Logger.warn(`无法向 Worker ${id} 发送 SIGTERM:`, error);
|
|
214
|
+
}
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
// 等待所有进程退出,最多
|
|
217
|
+
// 等待所有进程退出,最多 3 秒(Bun 的 server.stop() 很快)
|
|
215
218
|
const timeout = setTimeout(() => {
|
|
216
|
-
Logger.warn('
|
|
219
|
+
Logger.warn('等待超时(3秒),强制关闭所有 Worker');
|
|
217
220
|
for (const worker of this.workers.values()) {
|
|
218
|
-
|
|
221
|
+
try {
|
|
222
|
+
worker.process.kill('SIGKILL');
|
|
223
|
+
} catch (error) {
|
|
224
|
+
// 忽略 SIGKILL 失败(进程可能已退出)
|
|
225
|
+
}
|
|
219
226
|
}
|
|
227
|
+
// 强制退出主进程
|
|
228
|
+
setTimeout(() => process.exit(1), 500);
|
|
229
|
+
}, 3000);
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
// 等待所有进程退出
|
|
233
|
+
await Promise.all(Array.from(this.workers.values()).map((w) => w.process.exited));
|
|
234
|
+
clearTimeout(timeout);
|
|
235
|
+
Logger.info('集群已安全关闭');
|
|
236
|
+
process.exit(0);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
clearTimeout(timeout);
|
|
239
|
+
Logger.error('等待 Worker 退出时发生错误:', error);
|
|
220
240
|
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// 等待所有进程退出
|
|
224
|
-
await Promise.all(Array.from(this.workers.values()).map((w) => w.process.exited));
|
|
225
|
-
|
|
226
|
-
clearTimeout(timeout);
|
|
227
|
-
Logger.info('集群已安全关闭');
|
|
228
|
-
process.exit(0);
|
|
241
|
+
}
|
|
229
242
|
}
|
|
230
243
|
|
|
231
244
|
/**
|
package/lifecycle/lifecycle.ts
CHANGED
|
@@ -37,7 +37,7 @@ export class Lifecycle {
|
|
|
37
37
|
* @param appContext - 应用上下文
|
|
38
38
|
* @param callback - 启动完成后的回调函数
|
|
39
39
|
*/
|
|
40
|
-
async start(appContext: BeflyContext, callback?: (server: Server) => void): Promise<
|
|
40
|
+
async start(appContext: BeflyContext, callback?: (server: Server) => void): Promise<Server> {
|
|
41
41
|
const serverStartTime = Bun.nanoseconds();
|
|
42
42
|
Logger.info('开始启动 Befly 服务器...');
|
|
43
43
|
|
|
@@ -61,7 +61,7 @@ export class Lifecycle {
|
|
|
61
61
|
const totalStartupTime = calcPerfTime(serverStartTime);
|
|
62
62
|
Logger.info(`服务器启动准备完成,总耗时: ${totalStartupTime}`);
|
|
63
63
|
|
|
64
|
-
await Bootstrap.start(
|
|
64
|
+
return await Bootstrap.start(
|
|
65
65
|
{
|
|
66
66
|
apiRoutes: this.apiRoutes,
|
|
67
67
|
pluginLists: this.pluginLists,
|
package/lifecycle/loader.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { relative, basename } from 'pathe';
|
|
|
7
7
|
import { isPlainObject } from 'es-toolkit/compat';
|
|
8
8
|
import { Logger } from '../lib/logger.js';
|
|
9
9
|
import { calcPerfTime } from '../util.js';
|
|
10
|
-
import {
|
|
10
|
+
import { corePluginDir, projectPluginDir, coreApiDir, projectApiDir } from '../paths.js';
|
|
11
11
|
import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
|
|
12
12
|
import type { Plugin } from '../types/plugin.js';
|
|
13
13
|
import type { ApiRoute } from '../types/api.js';
|
|
@@ -98,7 +98,7 @@ export class Loader {
|
|
|
98
98
|
// 扫描核心插件目录
|
|
99
99
|
const corePluginsScanStart = Bun.nanoseconds();
|
|
100
100
|
for await (const file of glob.scan({
|
|
101
|
-
cwd:
|
|
101
|
+
cwd: corePluginDir,
|
|
102
102
|
onlyFiles: true,
|
|
103
103
|
absolute: true
|
|
104
104
|
})) {
|
|
@@ -262,7 +262,7 @@ export class Loader {
|
|
|
262
262
|
// 扫描用户插件目录
|
|
263
263
|
const userPluginsScanStart = Bun.nanoseconds();
|
|
264
264
|
for await (const file of glob.scan({
|
|
265
|
-
cwd:
|
|
265
|
+
cwd: projectPluginDir,
|
|
266
266
|
onlyFiles: true,
|
|
267
267
|
absolute: true
|
|
268
268
|
})) {
|
|
@@ -393,11 +393,11 @@ export class Loader {
|
|
|
393
393
|
let apiDir: string;
|
|
394
394
|
|
|
395
395
|
if (where === 'core') {
|
|
396
|
-
apiDir =
|
|
396
|
+
apiDir = coreApiDir;
|
|
397
397
|
} else if (where === 'addon') {
|
|
398
398
|
apiDir = getAddonDir(addonName, 'apis');
|
|
399
399
|
} else {
|
|
400
|
-
apiDir =
|
|
400
|
+
apiDir = projectApiDir;
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
let totalApis = 0;
|
package/main.ts
CHANGED
|
@@ -32,8 +32,8 @@ export class Befly {
|
|
|
32
32
|
* 启动服务器
|
|
33
33
|
* @param callback - 启动完成后的回调函数
|
|
34
34
|
*/
|
|
35
|
-
async listen(callback?: (server: Server) => void): Promise<
|
|
36
|
-
await this.lifecycle.start(this.appContext, callback);
|
|
35
|
+
async listen(callback?: (server: Server) => void): Promise<Server> {
|
|
36
|
+
return await this.lifecycle.start(this.appContext, callback);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"tsconfig.json",
|
|
65
65
|
"LICENSE",
|
|
66
66
|
"main.ts",
|
|
67
|
+
"menu.json",
|
|
67
68
|
"paths.ts",
|
|
68
69
|
"util.ts",
|
|
69
70
|
"package.json",
|
|
@@ -80,5 +81,5 @@
|
|
|
80
81
|
"ora": "^9.0.0",
|
|
81
82
|
"pathe": "^2.0.3"
|
|
82
83
|
},
|
|
83
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "3131d53e72d22577a42c02620249f39c8930e621"
|
|
84
85
|
}
|
package/paths.ts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Befly 框架路径配置
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* 提供统一的路径常量,供整个框架使用
|
|
5
|
+
* 所有路径常量采用具名导出方式,避免通过对象访问
|
|
6
|
+
*
|
|
7
|
+
* 路径分类:
|
|
8
|
+
* - root* 系列:Core 框架内部路径(packages/core/*)
|
|
9
|
+
* - project* 系列:用户项目路径(process.cwd()/*)
|
|
10
|
+
*
|
|
11
|
+
* 目录结构:
|
|
12
|
+
* ```
|
|
13
|
+
* packages/core/ (coreDir)
|
|
14
|
+
* ├── scripts/ (coreScriptDir)
|
|
15
|
+
* ├── config/ (coreConfigDir)
|
|
16
|
+
* ├── checks/ (coreCheckDir)
|
|
17
|
+
* ├── plugins/ (corePluginDir)
|
|
18
|
+
* ├── apis/ (coreApiDir)
|
|
19
|
+
* └── tables/ (coreTableDir)
|
|
20
|
+
*
|
|
21
|
+
* project/ (projectDir)
|
|
22
|
+
* ├── scripts/ (projectScriptDir)
|
|
23
|
+
* ├── config/ (projectConfigDir)
|
|
24
|
+
* ├── checks/ (projectCheckDir)
|
|
25
|
+
* ├── plugins/ (projectPluginDir)
|
|
26
|
+
* ├── apis/ (projectApiDir)
|
|
27
|
+
* └── tables/ (projectTableDir)
|
|
28
|
+
* ```
|
|
4
29
|
*/
|
|
5
30
|
|
|
6
31
|
import { fileURLToPath } from 'node:url';
|
|
@@ -13,22 +38,103 @@ const __dirname = dirname(__filename);
|
|
|
13
38
|
// 项目根目录(befly 框架的使用方项目)
|
|
14
39
|
const projectRoot = process.cwd();
|
|
15
40
|
|
|
41
|
+
// ==================== Core 框架路径 ====================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Core 框架根目录
|
|
45
|
+
* @description packages/core/
|
|
46
|
+
*/
|
|
47
|
+
export const coreDir = __dirname;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Core 框架脚本目录
|
|
51
|
+
* @description packages/core/scripts/
|
|
52
|
+
* @usage 存放框架级别的脚本工具
|
|
53
|
+
*/
|
|
54
|
+
export const coreScriptDir = join(__dirname, 'scripts');
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Core 框架配置目录
|
|
58
|
+
* @description packages/core/config/
|
|
59
|
+
* @usage 存放框架默认配置(env.ts, fields.ts 等)
|
|
60
|
+
*/
|
|
61
|
+
export const coreConfigDir = join(__dirname, 'config');
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Core 框架检查目录
|
|
65
|
+
* @description packages/core/checks/
|
|
66
|
+
* @usage 存放启动检查模块(返回 boolean 的 default 函数)
|
|
67
|
+
*/
|
|
68
|
+
export const coreCheckDir = join(__dirname, 'checks');
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Core 框架插件目录
|
|
72
|
+
* @description packages/core/plugins/
|
|
73
|
+
* @usage 存放内置插件(db, logger, redis, tool 等)
|
|
74
|
+
*/
|
|
75
|
+
export const corePluginDir = join(__dirname, 'plugins');
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Core 框架 API 目录
|
|
79
|
+
* @description packages/core/apis/
|
|
80
|
+
* @usage 存放框架级别的 API 接口
|
|
81
|
+
*/
|
|
82
|
+
export const coreApiDir = join(__dirname, 'apis');
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Core 框架表定义目录
|
|
86
|
+
* @description packages/core/tables/
|
|
87
|
+
* @usage 存放框架核心表定义(JSON 格式)
|
|
88
|
+
*/
|
|
89
|
+
export const coreTableDir = join(__dirname, 'tables');
|
|
90
|
+
|
|
91
|
+
// ==================== 用户项目路径 ====================
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 项目根目录
|
|
95
|
+
* @description process.cwd()
|
|
96
|
+
* @usage 用户项目的根目录
|
|
97
|
+
*/
|
|
98
|
+
export const projectDir = projectRoot;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 项目脚本目录
|
|
102
|
+
* @description {projectDir}/scripts/
|
|
103
|
+
* @usage 存放用户自定义脚本工具
|
|
104
|
+
*/
|
|
105
|
+
export const projectScriptDir = join(projectRoot, 'scripts');
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 项目配置目录
|
|
109
|
+
* @description {projectDir}/config/
|
|
110
|
+
* @usage 存放用户项目配置(覆盖框架默认配置)
|
|
111
|
+
*/
|
|
112
|
+
export const projectConfigDir = join(projectRoot, 'config');
|
|
113
|
+
|
|
16
114
|
/**
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
115
|
+
* 项目检查目录
|
|
116
|
+
* @description {projectDir}/checks/
|
|
117
|
+
* @usage 存放用户自定义启动检查模块
|
|
118
|
+
*/
|
|
119
|
+
export const projectCheckDir = join(projectRoot, 'checks');
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 项目插件目录
|
|
123
|
+
* @description {projectDir}/plugins/
|
|
124
|
+
* @usage 存放用户自定义插件
|
|
125
|
+
*/
|
|
126
|
+
export const projectPluginDir = join(projectRoot, 'plugins');
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 项目 API 目录
|
|
130
|
+
* @description {projectDir}/apis/
|
|
131
|
+
* @usage 存放用户业务 API 接口
|
|
132
|
+
*/
|
|
133
|
+
export const projectApiDir = join(projectRoot, 'apis');
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 项目表定义目录
|
|
137
|
+
* @description {projectDir}/tables/
|
|
138
|
+
* @usage 存放用户业务表定义(JSON 格式)
|
|
139
|
+
*/
|
|
140
|
+
export const projectTableDir = join(projectRoot, 'tables');
|
package/router/static.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { join } from 'pathe';
|
|
7
|
-
import {
|
|
7
|
+
import { projectDir } from '../paths.js';
|
|
8
8
|
import { No } from '../util.js';
|
|
9
9
|
import { setCorsOptions } from '../lib/middleware.js';
|
|
10
10
|
import { Logger } from '../lib/logger.js';
|
|
@@ -16,7 +16,7 @@ import { Env } from '../config/env.js';
|
|
|
16
16
|
export async function staticHandler(req: Request): Promise<Response> {
|
|
17
17
|
const corsOptions = setCorsOptions(req);
|
|
18
18
|
const url = new URL(req.url);
|
|
19
|
-
const filePath = join(
|
|
19
|
+
const filePath = join(projectDir, 'public', url.pathname);
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
// OPTIONS预检请求
|
package/util.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { isEmpty, isPlainObject } from 'es-toolkit/compat';
|
|
|
22
22
|
import { snakeCase, camelCase, kebabCase } from 'es-toolkit/string';
|
|
23
23
|
import { Env } from './config/env.js';
|
|
24
24
|
import { Logger } from './lib/logger.js';
|
|
25
|
-
import {
|
|
25
|
+
import { projectDir } from './paths.js';
|
|
26
26
|
import type { KeyValue } from './types/common.js';
|
|
27
27
|
import type { JwtPayload, JwtSignOptions, JwtVerifyOptions } from './types/jwt';
|
|
28
28
|
import type { Plugin } from './types/plugin.js';
|
|
@@ -241,7 +241,7 @@ export const parseRule = (rule: string): ParsedFieldRule => {
|
|
|
241
241
|
* 扫描所有可用的 addon
|
|
242
242
|
*/
|
|
243
243
|
export const scanAddons = (): string[] => {
|
|
244
|
-
const beflyDir = join(
|
|
244
|
+
const beflyDir = join(projectDir, 'node_modules', '@befly-addon');
|
|
245
245
|
|
|
246
246
|
if (!existsSync(beflyDir)) {
|
|
247
247
|
return [];
|
|
@@ -270,7 +270,7 @@ export const scanAddons = (): string[] => {
|
|
|
270
270
|
* 获取 addon 的指定子目录路径
|
|
271
271
|
*/
|
|
272
272
|
export const getAddonDir = (addonName: string, subDir: string): string => {
|
|
273
|
-
return join(
|
|
273
|
+
return join(projectDir, 'node_modules', '@befly-addon', addonName, subDir);
|
|
274
274
|
};
|
|
275
275
|
|
|
276
276
|
/**
|
|
File without changes
|