befly 3.5.1 → 3.5.2
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/bin/index.ts +17 -130
- package/checks/table.ts +5 -7
- package/commands/index.ts +1 -97
- package/commands/sync.ts +60 -28
- package/commands/syncApi.ts +22 -52
- package/commands/syncDb/index.ts +31 -31
- package/commands/syncDb.ts +5 -5
- package/commands/syncDev.ts +35 -33
- package/commands/syncMenu.ts +25 -76
- package/lib/database.ts +0 -9
- package/lib/logger.ts +7 -5
- package/lifecycle/checker.ts +8 -25
- package/lifecycle/lifecycle.ts +5 -34
- package/lifecycle/loader.ts +10 -100
- package/package.json +2 -4
- package/commands/build.ts +0 -62
package/bin/index.ts
CHANGED
|
@@ -1,147 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
3
|
* Befly CLI - 命令行工具入口
|
|
4
|
-
*
|
|
4
|
+
* 只提供 sync 命令,用于同步所有数据
|
|
5
|
+
*
|
|
6
|
+
* 使用方法:
|
|
7
|
+
* befly sync # 使用当前环境(默认 development)
|
|
5
8
|
*
|
|
6
9
|
* 环境变量加载:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
+
* Bun 自动根据 NODE_ENV 加载对应的 .env 文件:
|
|
11
|
+
* - NODE_ENV=development → .env.development
|
|
12
|
+
* - NODE_ENV=production → .env.production
|
|
13
|
+
* - NODE_ENV=test → .env.test
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
|
-
import { Command } from 'commander';
|
|
13
|
-
import { buildCommand } from '../commands/build.js';
|
|
14
16
|
import { syncCommand } from '../commands/sync.js';
|
|
15
|
-
import { syncDbCommand } from '../commands/syncDb.js';
|
|
16
|
-
import { syncApiCommand } from '../commands/syncApi.js';
|
|
17
|
-
import { syncMenuCommand } from '../commands/syncMenu.js';
|
|
18
|
-
import { syncDevCommand } from '../commands/syncDev.js';
|
|
19
17
|
import { Logger } from '../lib/logger.js';
|
|
20
|
-
import { join } from 'pathe';
|
|
21
|
-
import { getPackageVersion } from '../commands/util.js';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 读取 package.json 版本号
|
|
25
|
-
*/
|
|
26
|
-
function getVersion(): string {
|
|
27
|
-
const coreDir = join(import.meta.dir, '..');
|
|
28
|
-
return getPackageVersion(coreDir);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Bun 版本要求
|
|
33
|
-
*/
|
|
34
|
-
const REQUIRED_BUN_VERSION = '1.3.0';
|
|
35
18
|
|
|
36
19
|
/**
|
|
37
|
-
*
|
|
20
|
+
* 主函数
|
|
38
21
|
*/
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
const parts2 = v2.split('.').map(Number);
|
|
42
|
-
|
|
43
|
-
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
44
|
-
const num1 = parts1[i] || 0;
|
|
45
|
-
const num2 = parts2[i] || 0;
|
|
46
|
-
|
|
47
|
-
if (num1 > num2) return 1;
|
|
48
|
-
if (num1 < num2) return -1;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return 0;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 获取 Bun 版本
|
|
56
|
-
*/
|
|
57
|
-
function getBunVersion(): string | null {
|
|
22
|
+
async function main() {
|
|
23
|
+
// 执行 sync 命令
|
|
58
24
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const proc = Bun.spawnSync(['bun', '--version'], {
|
|
64
|
-
stdout: 'pipe',
|
|
65
|
-
stderr: 'pipe'
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (proc.exitCode === 0) {
|
|
69
|
-
const version = proc.stdout.toString().trim();
|
|
70
|
-
return version;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return null;
|
|
74
|
-
} catch {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 检查 Bun 版本
|
|
81
|
-
*/
|
|
82
|
-
function checkBunVersion(): void {
|
|
83
|
-
const currentVersion = getBunVersion();
|
|
84
|
-
|
|
85
|
-
if (!currentVersion) {
|
|
86
|
-
Logger.error('未检测到 Bun 运行时');
|
|
87
|
-
Logger.info('\nBefly CLI 需要 Bun v1.3.0 或更高版本');
|
|
88
|
-
Logger.info('请访问 https://bun.sh 安装 Bun\n');
|
|
89
|
-
Logger.info('安装命令:');
|
|
90
|
-
Logger.info(' Windows (PowerShell): powershell -c "irm bun.sh/install.ps1 | iex"');
|
|
91
|
-
Logger.info(' macOS/Linux: curl -fsSL https://bun.sh/install | bash\n');
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const comparison = compareVersions(currentVersion, REQUIRED_BUN_VERSION);
|
|
96
|
-
|
|
97
|
-
if (comparison < 0) {
|
|
98
|
-
Logger.error(`Bun 版本过低: ${currentVersion}`);
|
|
99
|
-
Logger.info(`\n需要 Bun v${REQUIRED_BUN_VERSION} 或更高版本`);
|
|
100
|
-
Logger.info('请升级 Bun:\n');
|
|
101
|
-
Logger.info(' bun upgrade\n');
|
|
25
|
+
await syncCommand();
|
|
26
|
+
Logger.printEnv();
|
|
27
|
+
} catch (error: any) {
|
|
28
|
+
Logger.error('命令执行失败:', error.message || error);
|
|
102
29
|
process.exit(1);
|
|
103
30
|
}
|
|
104
31
|
}
|
|
105
32
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const program = new Command();
|
|
110
|
-
|
|
111
|
-
program.name('befly').description('Befly CLI - 为 Befly 框架提供命令行工具').version(getVersion());
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 包装命令处理函数,在执行后打印环境
|
|
115
|
-
*/
|
|
116
|
-
function wrapCommand<T extends (...args: any[]) => any>(fn: T): T {
|
|
117
|
-
return (async (...args: any[]) => {
|
|
118
|
-
const result = await fn(...args);
|
|
119
|
-
Logger.printEnv();
|
|
120
|
-
return result;
|
|
121
|
-
}) as T;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// build 命令 - 构建项目
|
|
125
|
-
program.command('build').description('构建项目').option('-o, --outdir <path>', '输出目录', 'dist').option('--minify', '压缩代码', false).option('--sourcemap', '生成 sourcemap', false).action(wrapCommand(buildCommand));
|
|
126
|
-
|
|
127
|
-
// sync 命令 - 一次性执行所有同步
|
|
128
|
-
program.command('sync').description('一次性执行所有同步操作(syncApi + syncMenu + syncDev)').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncCommand));
|
|
129
|
-
|
|
130
|
-
// syncDb 命令 - 同步数据库
|
|
131
|
-
program.command('syncDb').description('同步数据库表结构').option('-t, --table <name>', '指定表名').option('--dry-run', '预览模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncDbCommand));
|
|
132
|
-
|
|
133
|
-
// syncApi 命令 - 同步 API 接口
|
|
134
|
-
program.command('syncApi').description('同步 API 接口到数据库').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncApiCommand));
|
|
135
|
-
|
|
136
|
-
// syncMenu 命令 - 同步菜单
|
|
137
|
-
program.command('syncMenu').description('同步菜单配置到数据库').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncMenuCommand));
|
|
138
|
-
|
|
139
|
-
// syncDev 命令 - 同步开发者账号
|
|
140
|
-
program.command('syncDev').description('同步开发者管理员账号').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncDevCommand));
|
|
141
|
-
|
|
142
|
-
// 显示建议和错误
|
|
143
|
-
program.showSuggestionAfterError();
|
|
144
|
-
program.showHelpAfterError();
|
|
145
|
-
|
|
146
|
-
// 解析命令行参数
|
|
147
|
-
program.parse();
|
|
33
|
+
// 运行主函数
|
|
34
|
+
main();
|
package/checks/table.ts
CHANGED
|
@@ -221,7 +221,7 @@ export default async function (): Promise<boolean> {
|
|
|
221
221
|
|
|
222
222
|
if (fileValid) {
|
|
223
223
|
validFiles++;
|
|
224
|
-
Logger.info(`${fileType}表 ${fileName} 验证通过(${fileRules} 个字段)`);
|
|
224
|
+
// Logger.info(`${fileType}表 ${fileName} 验证通过(${fileRules} 个字段)`);
|
|
225
225
|
} else {
|
|
226
226
|
invalidFiles++;
|
|
227
227
|
}
|
|
@@ -232,17 +232,15 @@ export default async function (): Promise<boolean> {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
// 输出统计信息
|
|
235
|
-
Logger.info(
|
|
236
|
-
Logger.info(`
|
|
237
|
-
Logger.info(`
|
|
238
|
-
Logger.info(`
|
|
239
|
-
Logger.info(` 失败文件: ${invalidFiles}`);
|
|
235
|
+
// Logger.info(` 总文件数: ${totalFiles}`);
|
|
236
|
+
// Logger.info(` 总规则数: ${totalRules}`);
|
|
237
|
+
// Logger.info(` 通过文件: ${validFiles}`);
|
|
238
|
+
// Logger.info(` 失败文件: ${invalidFiles}`);
|
|
240
239
|
|
|
241
240
|
if (invalidFiles > 0) {
|
|
242
241
|
Logger.warn(`表定义检查失败,请修复上述错误后重试`);
|
|
243
242
|
return false;
|
|
244
243
|
} else {
|
|
245
|
-
Logger.info(`所有表定义检查通过 ✓`);
|
|
246
244
|
return true;
|
|
247
245
|
}
|
|
248
246
|
} catch (error: any) {
|
package/commands/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Sync 命令实现
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { join } from 'pathe';
|
|
@@ -7,102 +7,6 @@ import { existsSync } from 'node:fs';
|
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { getProjectRoot } from './util.js';
|
|
9
9
|
|
|
10
|
-
// ========== Build 命令 ==========
|
|
11
|
-
interface BuildOptions {
|
|
12
|
-
outdir: string;
|
|
13
|
-
minify: boolean;
|
|
14
|
-
sourcemap: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function buildCommand(options: BuildOptions) {
|
|
18
|
-
try {
|
|
19
|
-
const projectRoot = getProjectRoot();
|
|
20
|
-
const mainFile = join(projectRoot, 'main.ts');
|
|
21
|
-
|
|
22
|
-
if (!existsSync(mainFile)) {
|
|
23
|
-
Logger.error('未找到 main.ts 文件');
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
Logger.info('正在构建项目...');
|
|
28
|
-
|
|
29
|
-
const args = ['build', mainFile, '--outdir', options.outdir, '--target', 'bun'];
|
|
30
|
-
|
|
31
|
-
if (options.minify) {
|
|
32
|
-
args.push('--minify');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (options.sourcemap) {
|
|
36
|
-
args.push('--sourcemap');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const proc = Bun.spawn(['bun', ...args], {
|
|
40
|
-
cwd: projectRoot,
|
|
41
|
-
stdout: 'pipe',
|
|
42
|
-
stderr: 'pipe'
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
await proc.exited;
|
|
46
|
-
|
|
47
|
-
if (proc.exitCode === 0) {
|
|
48
|
-
Logger.success('项目构建完成');
|
|
49
|
-
Logger.success(`输出目录: ${options.outdir}`);
|
|
50
|
-
} else {
|
|
51
|
-
Logger.error('项目构建失败');
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
} catch (error) {
|
|
55
|
-
Logger.error('构建失败:');
|
|
56
|
-
console.error(error);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ========== Start 命令 ==========
|
|
62
|
-
interface StartOptions {
|
|
63
|
-
port: string;
|
|
64
|
-
host: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function startCommand(options: StartOptions) {
|
|
68
|
-
try {
|
|
69
|
-
const projectRoot = getProjectRoot();
|
|
70
|
-
const mainFile = join(projectRoot, 'main.ts');
|
|
71
|
-
|
|
72
|
-
if (!existsSync(mainFile)) {
|
|
73
|
-
Logger.error('未找到 main.ts 文件');
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
process.env.NODE_ENV = 'production';
|
|
78
|
-
process.env.APP_PORT = options.port;
|
|
79
|
-
process.env.APP_HOST = options.host;
|
|
80
|
-
|
|
81
|
-
Logger.info('正在启动生产服务器...\n');
|
|
82
|
-
Logger.info(`端口: ${options.port}`);
|
|
83
|
-
Logger.info(`主机: ${options.host}`);
|
|
84
|
-
Logger.info(`环境: production\n`);
|
|
85
|
-
|
|
86
|
-
const proc = Bun.spawn(['bun', 'run', mainFile], {
|
|
87
|
-
cwd: projectRoot,
|
|
88
|
-
stdout: 'inherit',
|
|
89
|
-
stderr: 'inherit',
|
|
90
|
-
stdin: 'inherit',
|
|
91
|
-
env: {
|
|
92
|
-
...process.env,
|
|
93
|
-
FORCE_COLOR: '1'
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
await proc.exited;
|
|
98
|
-
process.exit(proc.exitCode || 0);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
Logger.error('启动失败:');
|
|
101
|
-
console.error(error);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
10
|
// ========== Sync 命令 ==========
|
|
107
11
|
interface SyncOptions {
|
|
108
12
|
table?: string;
|
package/commands/sync.ts
CHANGED
|
@@ -1,54 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sync 命令 - 一次性执行所有同步操作
|
|
3
|
-
* 按顺序执行:syncApi → syncMenu → syncDev
|
|
3
|
+
* 按顺序执行:syncDb → syncApi → syncMenu → syncDev
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Logger } from '../lib/logger.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { Env } from '../config/env.js';
|
|
8
|
+
import { syncDbCommand, type SyncDbStats } from './syncDb.js';
|
|
9
|
+
import { syncApiCommand, type SyncApiStats } from './syncApi.js';
|
|
10
|
+
import { syncMenuCommand, type SyncMenuStats } from './syncMenu.js';
|
|
11
|
+
import { syncDevCommand, type SyncDevStats } from './syncDev.js';
|
|
10
12
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
11
13
|
|
|
12
|
-
interface SyncOptions {
|
|
13
|
-
env?: string;
|
|
14
|
-
plan?: boolean;
|
|
15
|
-
}
|
|
14
|
+
interface SyncOptions {}
|
|
16
15
|
|
|
17
16
|
export async function syncCommand(options: SyncOptions = {}) {
|
|
18
17
|
try {
|
|
19
|
-
Logger.info('========================================');
|
|
20
|
-
Logger.info('开始执行完整同步流程');
|
|
21
|
-
Logger.info('========================================\n');
|
|
22
|
-
|
|
23
18
|
const startTime = Date.now();
|
|
24
19
|
|
|
25
20
|
// 确保 logs 目录存在
|
|
26
21
|
if (!existsSync('./logs')) {
|
|
27
22
|
mkdirSync('./logs', { recursive: true });
|
|
28
|
-
Logger.info('✅ 已创建 logs 目录\n');
|
|
29
23
|
}
|
|
30
24
|
|
|
31
|
-
// 1.
|
|
32
|
-
|
|
33
|
-
await syncApiCommand(options);
|
|
34
|
-
Logger.info('\n✅ 接口同步完成\n');
|
|
25
|
+
// 1. 同步数据库表结构
|
|
26
|
+
const dbStats = await syncDbCommand({ dryRun: false });
|
|
35
27
|
|
|
36
|
-
// 2.
|
|
37
|
-
|
|
38
|
-
await syncMenuCommand(options);
|
|
39
|
-
Logger.info('\n✅ 菜单同步完成\n');
|
|
28
|
+
// 2. 同步接口(并缓存)
|
|
29
|
+
const apiStats = await syncApiCommand();
|
|
40
30
|
|
|
41
|
-
// 3.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
// 3. 同步菜单(并缓存)
|
|
32
|
+
const menuStats = await syncMenuCommand();
|
|
33
|
+
|
|
34
|
+
// 4. 同步开发管理员(并缓存角色权限)
|
|
35
|
+
const devStats = await syncDevCommand();
|
|
45
36
|
|
|
46
37
|
// 输出总结
|
|
47
38
|
const totalTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
48
|
-
Logger.info('========================================');
|
|
49
|
-
Logger.info('🎉 所有同步操作已完成!');
|
|
50
39
|
Logger.info(`总耗时: ${totalTime} 秒`);
|
|
51
|
-
|
|
40
|
+
|
|
41
|
+
console.log(
|
|
42
|
+
Bun.inspect.table([
|
|
43
|
+
{ 项目: '处理表数', 数量: dbStats.processedTables },
|
|
44
|
+
{ 项目: '创建表', 数量: dbStats.createdTables },
|
|
45
|
+
{ 项目: '修改表', 数量: dbStats.modifiedTables },
|
|
46
|
+
{ 项目: '新增字段', 数量: dbStats.addFields },
|
|
47
|
+
{ 项目: '字段名称变更', 数量: dbStats.nameChanges },
|
|
48
|
+
{ 项目: '字段类型变更', 数量: dbStats.typeChanges },
|
|
49
|
+
{ 项目: '索引新增', 数量: dbStats.indexCreate },
|
|
50
|
+
{ 项目: '索引删除', 数量: dbStats.indexDrop }
|
|
51
|
+
])
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
Logger.info('\n📊 接口同步统计');
|
|
55
|
+
console.log(
|
|
56
|
+
Bun.inspect.table([
|
|
57
|
+
{ 项目: '总接口数', 数量: apiStats.totalApis },
|
|
58
|
+
{ 项目: '新增接口', 数量: apiStats.created },
|
|
59
|
+
{ 项目: '更新接口', 数量: apiStats.updated },
|
|
60
|
+
{ 项目: '删除接口', 数量: apiStats.deleted }
|
|
61
|
+
])
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
Logger.info('\n📊 菜单同步统计');
|
|
65
|
+
console.log(
|
|
66
|
+
Bun.inspect.table([
|
|
67
|
+
{ 项目: '总菜单数', 数量: menuStats.totalMenus },
|
|
68
|
+
{ 项目: '父级菜单', 数量: menuStats.parentMenus },
|
|
69
|
+
{ 项目: '子级菜单', 数量: menuStats.childMenus },
|
|
70
|
+
{ 项目: '新增菜单', 数量: menuStats.created },
|
|
71
|
+
{ 项目: '更新菜单', 数量: menuStats.updated },
|
|
72
|
+
{ 项目: '删除菜单', 数量: menuStats.deleted }
|
|
73
|
+
])
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
Logger.info('\n📊 开发账号同步统计');
|
|
77
|
+
console.log(
|
|
78
|
+
Bun.inspect.table([
|
|
79
|
+
{ 项目: '管理员数量', 数量: devStats.adminCount },
|
|
80
|
+
{ 项目: '角色数量', 数量: devStats.roleCount },
|
|
81
|
+
{ 项目: '缓存角色数', 数量: devStats.cachedRoles }
|
|
82
|
+
])
|
|
83
|
+
);
|
|
52
84
|
} catch (error: any) {
|
|
53
85
|
Logger.error('同步过程中发生错误:', error);
|
|
54
86
|
process.exit(1);
|
package/commands/syncApi.ts
CHANGED
|
@@ -32,6 +32,13 @@ interface ApiInfo {
|
|
|
32
32
|
addonTitle: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface SyncApiStats {
|
|
36
|
+
totalApis: number;
|
|
37
|
+
created: number;
|
|
38
|
+
updated: number;
|
|
39
|
+
deleted: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
/**
|
|
36
43
|
* 递归扫描目录下的所有 .ts 文件
|
|
37
44
|
*/
|
|
@@ -111,36 +118,29 @@ async function scanAllApis(projectRoot: string): Promise<ApiInfo[]> {
|
|
|
111
118
|
const apis: ApiInfo[] = [];
|
|
112
119
|
|
|
113
120
|
// 1. 扫描 Core 框架 API
|
|
114
|
-
Logger.debug('=== 扫描 Core 框架 API (core/apis) ===');
|
|
115
121
|
const coreApisDir = join(dirname(projectRoot), 'core', 'apis');
|
|
116
122
|
try {
|
|
117
123
|
const coreApiFiles = scanTsFiles(coreApisDir);
|
|
118
|
-
Logger.debug(` 找到 ${coreApiFiles.length} 个核心 API 文件`);
|
|
119
124
|
|
|
120
125
|
for (const filePath of coreApiFiles) {
|
|
121
126
|
const apiInfo = await extractApiInfo(filePath, coreApisDir, 'core', '', '核心接口');
|
|
122
127
|
if (apiInfo) {
|
|
123
128
|
apis.push(apiInfo);
|
|
124
|
-
Logger.debug(` └ ${apiInfo.path} - ${apiInfo.name}`);
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
// 2. 扫描项目 API
|
|
129
|
-
Logger.info('\n=== 扫描项目 API (apis) ===');
|
|
130
133
|
const projectApisDir = join(projectRoot, 'apis');
|
|
131
134
|
const projectApiFiles = scanTsFiles(projectApisDir);
|
|
132
|
-
Logger.debug(` 找到 ${projectApiFiles.length} 个项目 API 文件`);
|
|
133
135
|
|
|
134
136
|
for (const filePath of projectApiFiles) {
|
|
135
137
|
const apiInfo = await extractApiInfo(filePath, projectApisDir, 'app', '', '项目接口');
|
|
136
138
|
if (apiInfo) {
|
|
137
139
|
apis.push(apiInfo);
|
|
138
|
-
Logger.debug(` └ ${apiInfo.path} - ${apiInfo.name}`);
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
// 3. 扫描组件 API (node_modules/@befly-addon/*)
|
|
143
|
-
Logger.info('\n=== 扫描组件 API (node_modules/@befly-addon/*) ===');
|
|
144
144
|
const addonNames = scanAddons();
|
|
145
145
|
|
|
146
146
|
for (const addonName of addonNames) {
|
|
@@ -148,7 +148,6 @@ async function scanAllApis(projectRoot: string): Promise<ApiInfo[]> {
|
|
|
148
148
|
|
|
149
149
|
// 检查 apis 子目录是否存在
|
|
150
150
|
if (!addonDirExists(addonName, 'apis')) {
|
|
151
|
-
Logger.debug(` [${addonName}] 无 apis 目录,跳过`);
|
|
152
151
|
continue;
|
|
153
152
|
}
|
|
154
153
|
|
|
@@ -162,17 +161,15 @@ async function scanAllApis(projectRoot: string): Promise<ApiInfo[]> {
|
|
|
162
161
|
const config = await configFile.json();
|
|
163
162
|
addonTitle = config.title || addonName;
|
|
164
163
|
} catch (error) {
|
|
165
|
-
|
|
164
|
+
// 忽略配置读取错误
|
|
166
165
|
}
|
|
167
166
|
|
|
168
167
|
const addonApiFiles = scanTsFiles(addonApisDir);
|
|
169
|
-
Logger.debug(` [${addonName}] 找到 ${addonApiFiles.length} 个 API 文件`);
|
|
170
168
|
|
|
171
169
|
for (const filePath of addonApiFiles) {
|
|
172
170
|
const apiInfo = await extractApiInfo(filePath, addonApisDir, 'addon', addonName, addonTitle);
|
|
173
171
|
if (apiInfo) {
|
|
174
172
|
apis.push(apiInfo);
|
|
175
|
-
Logger.debug(` └ ${apiInfo.path} - ${apiInfo.name}`);
|
|
176
173
|
}
|
|
177
174
|
}
|
|
178
175
|
}
|
|
@@ -180,6 +177,7 @@ async function scanAllApis(projectRoot: string): Promise<ApiInfo[]> {
|
|
|
180
177
|
return apis;
|
|
181
178
|
} catch (error: any) {
|
|
182
179
|
Logger.error(`接口扫描失败:`, error);
|
|
180
|
+
return apis;
|
|
183
181
|
}
|
|
184
182
|
}
|
|
185
183
|
|
|
@@ -191,6 +189,7 @@ async function syncApis(helper: any, apis: ApiInfo[]): Promise<{ created: number
|
|
|
191
189
|
|
|
192
190
|
for (const api of apis) {
|
|
193
191
|
try {
|
|
192
|
+
// 根据 path 查询是否存在
|
|
194
193
|
const existing = await helper.getOne({
|
|
195
194
|
table: 'core_api',
|
|
196
195
|
where: { path: api.path }
|
|
@@ -209,7 +208,6 @@ async function syncApis(helper: any, apis: ApiInfo[]): Promise<{ created: number
|
|
|
209
208
|
}
|
|
210
209
|
});
|
|
211
210
|
stats.updated++;
|
|
212
|
-
Logger.debug(` └ 更新接口: ${api.name} (ID: ${existing.id}, Path: ${api.path})`);
|
|
213
211
|
} else {
|
|
214
212
|
const id = await helper.insData({
|
|
215
213
|
table: 'core_api',
|
|
@@ -223,7 +221,6 @@ async function syncApis(helper: any, apis: ApiInfo[]): Promise<{ created: number
|
|
|
223
221
|
}
|
|
224
222
|
});
|
|
225
223
|
stats.created++;
|
|
226
|
-
Logger.debug(` └ 新增接口: ${api.name} (ID: ${id}, Path: ${api.path})`);
|
|
227
224
|
}
|
|
228
225
|
} catch (error: any) {
|
|
229
226
|
Logger.error(`同步接口 "${api.name}" 失败:`, error);
|
|
@@ -237,8 +234,6 @@ async function syncApis(helper: any, apis: ApiInfo[]): Promise<{ created: number
|
|
|
237
234
|
* 删除配置中不存在的记录
|
|
238
235
|
*/
|
|
239
236
|
async function deleteObsoleteRecords(helper: any, apiPaths: Set<string>): Promise<number> {
|
|
240
|
-
Logger.info(`\n=== 删除配置中不存在的记录 ===`);
|
|
241
|
-
|
|
242
237
|
const allRecords = await helper.getAll({
|
|
243
238
|
table: 'core_api',
|
|
244
239
|
fields: ['id', 'path', 'name'],
|
|
@@ -253,34 +248,22 @@ async function deleteObsoleteRecords(helper: any, apiPaths: Set<string>): Promis
|
|
|
253
248
|
where: { id: record.id }
|
|
254
249
|
});
|
|
255
250
|
deletedCount++;
|
|
256
|
-
Logger.debug(` └ 删除记录: ${record.name} (ID: ${record.id}, path: ${record.path})`);
|
|
257
251
|
}
|
|
258
252
|
}
|
|
259
253
|
|
|
260
|
-
if (deletedCount === 0) {
|
|
261
|
-
Logger.info(' ✅ 无需删除的记录');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
254
|
return deletedCount;
|
|
265
255
|
}
|
|
266
256
|
|
|
267
257
|
/**
|
|
268
258
|
* SyncApi 命令主函数
|
|
269
259
|
*/
|
|
270
|
-
export async function syncApiCommand(options: SyncApiOptions = {}) {
|
|
260
|
+
export async function syncApiCommand(options: SyncApiOptions = {}): Promise<SyncApiStats> {
|
|
271
261
|
try {
|
|
272
262
|
if (options.plan) {
|
|
273
263
|
Logger.info('[计划] 同步 API 接口到数据库(plan 模式不执行)');
|
|
274
|
-
|
|
275
|
-
Logger.info('[计划] 2. 提取每个 API 的配置信息');
|
|
276
|
-
Logger.info('[计划] 3. 根据 path 检查接口是否存在');
|
|
277
|
-
Logger.info('[计划] 4. 存在则更新,不存在则新增');
|
|
278
|
-
Logger.info('[计划] 5. 删除文件中不存在的接口记录');
|
|
279
|
-
return;
|
|
264
|
+
return { totalApis: 0, created: 0, updated: 0, deleted: 0 };
|
|
280
265
|
}
|
|
281
266
|
|
|
282
|
-
Logger.info('开始同步 API 接口到数据库...\n');
|
|
283
|
-
|
|
284
267
|
const projectRoot = process.cwd();
|
|
285
268
|
|
|
286
269
|
// 连接数据库(SQL + Redis)
|
|
@@ -289,7 +272,6 @@ export async function syncApiCommand(options: SyncApiOptions = {}) {
|
|
|
289
272
|
const helper = Database.getDbHelper();
|
|
290
273
|
|
|
291
274
|
// 1. 检查表是否存在
|
|
292
|
-
Logger.debug('=== 检查数据表 ===');
|
|
293
275
|
const exists = await helper.tableExists('core_api');
|
|
294
276
|
|
|
295
277
|
if (!exists) {
|
|
@@ -297,30 +279,17 @@ export async function syncApiCommand(options: SyncApiOptions = {}) {
|
|
|
297
279
|
process.exit(1);
|
|
298
280
|
}
|
|
299
281
|
|
|
300
|
-
Logger.info(`✅ 表 core_api 存在\n`);
|
|
301
|
-
|
|
302
282
|
// 2. 扫描所有 API 文件
|
|
303
|
-
Logger.debug('=== 步骤 2: 扫描 API 文件 ===');
|
|
304
283
|
const apis = await scanAllApis(projectRoot);
|
|
305
284
|
const apiPaths = new Set(apis.map((api) => api.path));
|
|
306
|
-
Logger.info(`\n✅ 共扫描到 ${apis.length} 个 API 接口\n`);
|
|
307
285
|
|
|
308
286
|
// 3. 同步 API 数据
|
|
309
|
-
Logger.debug('=== 步骤 3: 同步 API 数据(新增/更新) ===');
|
|
310
287
|
const stats = await syncApis(helper, apis);
|
|
311
288
|
|
|
312
289
|
// 4. 删除文件中不存在的接口
|
|
313
290
|
const deletedCount = await deleteObsoleteRecords(helper, apiPaths);
|
|
314
291
|
|
|
315
|
-
// 5.
|
|
316
|
-
Logger.info(`\n=== 接口同步完成 ===`);
|
|
317
|
-
Logger.info(`新增接口: ${stats.created} 个`);
|
|
318
|
-
Logger.info(`更新接口: ${stats.updated} 个`);
|
|
319
|
-
Logger.info(`删除接口: ${deletedCount} 个`);
|
|
320
|
-
Logger.info(`当前总接口数: ${apis.length} 个`);
|
|
321
|
-
|
|
322
|
-
// 6. 缓存接口数据到 Redis
|
|
323
|
-
Logger.info('\n=== 步骤 4: 缓存接口数据到 Redis ===');
|
|
292
|
+
// 5. 缓存接口数据到 Redis
|
|
324
293
|
try {
|
|
325
294
|
const apiList = await helper.getAll({
|
|
326
295
|
table: 'core_api',
|
|
@@ -328,16 +297,17 @@ export async function syncApiCommand(options: SyncApiOptions = {}) {
|
|
|
328
297
|
orderBy: ['addonName#ASC', 'path#ASC']
|
|
329
298
|
});
|
|
330
299
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (result === null) {
|
|
334
|
-
Logger.warn('⚠️ 接口缓存失败');
|
|
335
|
-
} else {
|
|
336
|
-
Logger.info(`✅ 已缓存 ${apiList.length} 个接口到 Redis (Key: apis:all)`);
|
|
337
|
-
}
|
|
300
|
+
await RedisHelper.setObject('apis:all', apiList);
|
|
338
301
|
} catch (error: any) {
|
|
339
|
-
|
|
302
|
+
// 忽略缓存错误
|
|
340
303
|
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
totalApis: apis.length,
|
|
307
|
+
created: stats.created,
|
|
308
|
+
updated: stats.updated,
|
|
309
|
+
deleted: deletedCount
|
|
310
|
+
};
|
|
341
311
|
} catch (error: any) {
|
|
342
312
|
Logger.error('API 同步失败:', error);
|
|
343
313
|
process.exit(1);
|