befly 3.2.1 → 3.3.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/bin/index.ts +138 -0
- package/checks/conflict.ts +35 -25
- package/checks/table.ts +6 -6
- package/commands/addon.ts +57 -0
- package/commands/build.ts +74 -0
- package/commands/dev.ts +94 -0
- package/commands/index.ts +252 -0
- package/commands/script.ts +303 -0
- package/commands/start.ts +80 -0
- package/commands/syncApi.ts +327 -0
- package/{scripts → commands}/syncDb/apply.ts +2 -2
- package/{scripts → commands}/syncDb/constants.ts +13 -7
- package/{scripts → commands}/syncDb/ddl.ts +7 -5
- package/{scripts → commands}/syncDb/helpers.ts +18 -18
- package/{scripts → commands}/syncDb/index.ts +37 -23
- package/{scripts → commands}/syncDb/sqlite.ts +1 -1
- package/{scripts → commands}/syncDb/state.ts +10 -4
- package/{scripts → commands}/syncDb/table.ts +7 -7
- package/{scripts → commands}/syncDb/tableCreate.ts +7 -6
- package/{scripts → commands}/syncDb/types.ts +5 -5
- package/{scripts → commands}/syncDb/version.ts +1 -1
- package/commands/syncDb.ts +35 -0
- package/commands/syncDev.ts +174 -0
- package/commands/syncMenu.ts +368 -0
- package/config/env.ts +4 -4
- package/config/menu.json +67 -0
- package/{utils/crypto.ts → lib/cipher.ts} +16 -67
- package/lib/database.ts +296 -0
- package/{utils → lib}/dbHelper.ts +102 -56
- package/{utils → lib}/jwt.ts +124 -151
- package/{utils → lib}/logger.ts +47 -24
- package/lib/middleware.ts +271 -0
- package/{utils → lib}/redisHelper.ts +4 -4
- package/{utils/validate.ts → lib/validator.ts} +101 -78
- package/lifecycle/bootstrap.ts +63 -0
- package/lifecycle/checker.ts +165 -0
- package/lifecycle/cluster.ts +241 -0
- package/lifecycle/lifecycle.ts +139 -0
- package/lifecycle/loader.ts +513 -0
- package/main.ts +14 -12
- package/package.json +21 -9
- package/paths.ts +34 -0
- package/plugins/cache.ts +187 -0
- package/plugins/db.ts +4 -4
- package/plugins/logger.ts +1 -1
- package/plugins/redis.ts +4 -4
- package/router/api.ts +155 -0
- package/router/root.ts +53 -0
- package/router/static.ts +76 -0
- package/types/api.d.ts +0 -36
- package/types/befly.d.ts +8 -6
- package/types/common.d.ts +1 -1
- package/types/context.d.ts +3 -3
- package/types/util.d.ts +45 -0
- package/util.ts +299 -0
- package/config/fields.ts +0 -55
- package/config/regexAliases.ts +0 -51
- package/config/reserved.ts +0 -96
- package/scripts/syncDb/tests/constants.test.ts +0 -105
- package/scripts/syncDb/tests/ddl.test.ts +0 -134
- package/scripts/syncDb/tests/helpers.test.ts +0 -70
- package/scripts/syncDb.ts +0 -10
- package/types/index.d.ts +0 -450
- package/types/index.ts +0 -438
- package/types/validator.ts +0 -43
- package/utils/colors.ts +0 -221
- package/utils/database.ts +0 -348
- package/utils/helper.ts +0 -812
- package/utils/index.ts +0 -33
- package/utils/requestContext.ts +0 -167
- /package/{scripts → commands}/syncDb/schema.ts +0 -0
- /package/{utils → lib}/sqlBuilder.ts +0 -0
- /package/{utils → lib}/xml.ts +0 -0
package/bin/index.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Befly CLI - 命令行工具入口
|
|
4
|
+
* 为 Befly 框架提供项目管理和脚本执行功能
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import { scriptCommand } from '../commands/script.js';
|
|
8
|
+
import { devCommand } from '../commands/dev.js';
|
|
9
|
+
import { buildCommand } from '../commands/build.js';
|
|
10
|
+
import { startCommand } from '../commands/start.js';
|
|
11
|
+
import { syncDbCommand } from '../commands/syncDb.js';
|
|
12
|
+
import { addonCommand } from '../commands/addon.js';
|
|
13
|
+
import { syncApiCommand } from '../commands/syncApi.js';
|
|
14
|
+
import { syncMenuCommand } from '../commands/syncMenu.js';
|
|
15
|
+
import { syncDevCommand } from '../commands/syncDev.js';
|
|
16
|
+
import { Logger } from '../lib/logger.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Bun 版本要求
|
|
20
|
+
*/
|
|
21
|
+
const REQUIRED_BUN_VERSION = '1.3.0';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 比较版本号
|
|
25
|
+
*/
|
|
26
|
+
function compareVersions(v1: string, v2: string): number {
|
|
27
|
+
const parts1 = v1.split('.').map(Number);
|
|
28
|
+
const parts2 = v2.split('.').map(Number);
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
31
|
+
const num1 = parts1[i] || 0;
|
|
32
|
+
const num2 = parts2[i] || 0;
|
|
33
|
+
|
|
34
|
+
if (num1 > num2) return 1;
|
|
35
|
+
if (num1 < num2) return -1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 获取 Bun 版本
|
|
43
|
+
*/
|
|
44
|
+
function getBunVersion(): string | null {
|
|
45
|
+
try {
|
|
46
|
+
if (typeof Bun !== 'undefined' && Bun.version) {
|
|
47
|
+
return Bun.version;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const proc = Bun.spawnSync(['bun', '--version'], {
|
|
51
|
+
stdout: 'pipe',
|
|
52
|
+
stderr: 'pipe'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (proc.exitCode === 0) {
|
|
56
|
+
const version = proc.stdout.toString().trim();
|
|
57
|
+
return version;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 检查 Bun 版本
|
|
68
|
+
*/
|
|
69
|
+
function checkBunVersion(): void {
|
|
70
|
+
const currentVersion = getBunVersion();
|
|
71
|
+
|
|
72
|
+
if (!currentVersion) {
|
|
73
|
+
Logger.error('未检测到 Bun 运行时');
|
|
74
|
+
Logger.info('\nBefly CLI 需要 Bun v1.3.0 或更高版本');
|
|
75
|
+
Logger.info('请访问 https://bun.sh 安装 Bun\n');
|
|
76
|
+
Logger.info('安装命令:');
|
|
77
|
+
Logger.info(' Windows (PowerShell): powershell -c "irm bun.sh/install.ps1 | iex"');
|
|
78
|
+
Logger.info(' macOS/Linux: curl -fsSL https://bun.sh/install | bash\n');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const comparison = compareVersions(currentVersion, REQUIRED_BUN_VERSION);
|
|
83
|
+
|
|
84
|
+
if (comparison < 0) {
|
|
85
|
+
Logger.error(`Bun 版本过低: ${currentVersion}`);
|
|
86
|
+
Logger.info(`\n需要 Bun v${REQUIRED_BUN_VERSION} 或更高版本`);
|
|
87
|
+
Logger.info('请升级 Bun:\n');
|
|
88
|
+
Logger.info(' bun upgrade\n');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 检查 Bun 版本
|
|
94
|
+
checkBunVersion();
|
|
95
|
+
|
|
96
|
+
const program = new Command();
|
|
97
|
+
|
|
98
|
+
program.name('befly').description('Befly CLI - 为 Befly 框架提供命令行工具').version('3.0.0');
|
|
99
|
+
|
|
100
|
+
// script 命令 - 执行脚本
|
|
101
|
+
program.command('script').description('列出并执行 befly 脚本').option('--dry-run', '预演模式,只显示不执行', false).action(scriptCommand);
|
|
102
|
+
|
|
103
|
+
// dev 命令 - 开发服务器
|
|
104
|
+
program.command('dev').description('启动开发服务器').option('-p, --port <number>', '端口号', '3000').option('-h, --host <string>', '主机地址', '0.0.0.0').option('--no-sync', '跳过表同步', false).option('-v, --verbose', '详细日志', false).action(devCommand);
|
|
105
|
+
|
|
106
|
+
// build 命令 - 构建项目
|
|
107
|
+
program.command('build').description('构建项目').option('-o, --outdir <path>', '输出目录', 'dist').option('--minify', '压缩代码', false).option('--sourcemap', '生成 sourcemap', false).action(buildCommand);
|
|
108
|
+
|
|
109
|
+
// start 命令 - 启动生产服务器
|
|
110
|
+
program.command('start').description('启动生产服务器').option('-p, --port <number>', '端口号', '3000').option('-h, --host <string>', '主机地址', '0.0.0.0').option('-c, --cluster <instances>', '集群模式(数字或 max)').action(startCommand);
|
|
111
|
+
|
|
112
|
+
// syncDb 命令 - 同步数据库
|
|
113
|
+
program.command('syncDb').description('同步数据库表结构').option('-t, --table <name>', '指定表名').option('--dry-run', '预览模式,只显示不执行', false).action(syncDbCommand);
|
|
114
|
+
|
|
115
|
+
// syncApi 命令 - 同步 API 接口
|
|
116
|
+
program.command('syncApi').description('同步 API 接口到数据库').option('--plan', '计划模式,只显示不执行', false).action(syncApiCommand);
|
|
117
|
+
|
|
118
|
+
// syncMenu 命令 - 同步菜单
|
|
119
|
+
program.command('syncMenu').description('同步菜单配置到数据库').option('--plan', '计划模式,只显示不执行', false).action(syncMenuCommand);
|
|
120
|
+
|
|
121
|
+
// syncDev 命令 - 同步开发者账号
|
|
122
|
+
program.command('syncDev').description('同步开发者管理员账号').option('--plan', '计划模式,只显示不执行', false).action(syncDevCommand);
|
|
123
|
+
|
|
124
|
+
// addon 命令 - 插件管理
|
|
125
|
+
const addon = program.command('addon').description('管理 Befly 插件');
|
|
126
|
+
|
|
127
|
+
addon.command('install <name>').description('安装插件').option('-s, --source <url>', '插件源地址').action(addonCommand.install);
|
|
128
|
+
|
|
129
|
+
addon.command('uninstall <name>').description('卸载插件').option('--keep-data', '保留插件数据', false).action(addonCommand.uninstall);
|
|
130
|
+
|
|
131
|
+
addon.command('list').description('列出已安装的插件').action(addonCommand.list);
|
|
132
|
+
|
|
133
|
+
// 显示建议和错误
|
|
134
|
+
program.showSuggestionAfterError();
|
|
135
|
+
program.showHelpAfterError();
|
|
136
|
+
|
|
137
|
+
// 解析命令行参数
|
|
138
|
+
program.parse();
|
package/checks/conflict.ts
CHANGED
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
* 在系统启动前检测表名、API 路由、插件名等资源是否存在冲突
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import { Logger } from '../
|
|
6
|
+
import { relative, basename } from 'pathe';
|
|
7
|
+
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { paths } from '../paths.js';
|
|
9
|
-
import { scanAddons, getAddonDir, addonDirExists } from '../
|
|
10
|
-
|
|
9
|
+
import { scanAddons, getAddonDir, addonDirExists } from '../util.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 保留名称配置
|
|
13
|
+
*/
|
|
14
|
+
const RESERVED_NAMES = {
|
|
15
|
+
tablePrefix: ['sys_'],
|
|
16
|
+
plugins: ['db', 'logger', 'redis', 'tool'],
|
|
17
|
+
addonNames: ['app', 'api']
|
|
18
|
+
} as const;
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
21
|
* 资源注册表
|
|
@@ -33,11 +41,11 @@ async function collectCorePlugins(registry: ResourceRegistry): Promise<void> {
|
|
|
33
41
|
try {
|
|
34
42
|
const glob = new Bun.Glob('*.ts');
|
|
35
43
|
for await (const file of glob.scan({
|
|
36
|
-
cwd: paths.
|
|
44
|
+
cwd: paths.projectPluginDir,
|
|
37
45
|
onlyFiles: true,
|
|
38
46
|
absolute: true
|
|
39
47
|
})) {
|
|
40
|
-
const pluginName =
|
|
48
|
+
const pluginName = basename(file).replace(/\.ts$/, '');
|
|
41
49
|
if (pluginName.startsWith('_')) continue;
|
|
42
50
|
|
|
43
51
|
if (registry.plugins.has(pluginName)) {
|
|
@@ -58,8 +66,8 @@ async function collectAddonResources(addonName: string, registry: ResourceRegist
|
|
|
58
66
|
const conflicts: string[] = [];
|
|
59
67
|
|
|
60
68
|
// 检查 addon 名称是否使用保留名称
|
|
61
|
-
if (
|
|
62
|
-
conflicts.push(`组件名称 "${addonName}" 使用了保留名称,保留名称包括: ${
|
|
69
|
+
if (RESERVED_NAMES.addonNames.includes(addonName.toLowerCase())) {
|
|
70
|
+
conflicts.push(`组件名称 "${addonName}" 使用了保留名称,保留名称包括: ${RESERVED_NAMES.addonNames.join(', ')}`);
|
|
63
71
|
return conflicts;
|
|
64
72
|
}
|
|
65
73
|
|
|
@@ -69,11 +77,11 @@ async function collectAddonResources(addonName: string, registry: ResourceRegist
|
|
|
69
77
|
const glob = new Bun.Glob('*.json');
|
|
70
78
|
|
|
71
79
|
for await (const file of glob.scan({
|
|
72
|
-
cwd:
|
|
80
|
+
cwd: paths.rootTableDir,
|
|
73
81
|
onlyFiles: true,
|
|
74
82
|
absolute: true
|
|
75
83
|
})) {
|
|
76
|
-
const fileName =
|
|
84
|
+
const fileName = basename(file, '.json');
|
|
77
85
|
if (fileName.startsWith('_')) continue;
|
|
78
86
|
|
|
79
87
|
try {
|
|
@@ -81,8 +89,8 @@ async function collectAddonResources(addonName: string, registry: ResourceRegist
|
|
|
81
89
|
const tableName = tableDefine.tableName || `${addonName}_${fileName}`;
|
|
82
90
|
|
|
83
91
|
// 检查是否使用保留前缀
|
|
84
|
-
if (
|
|
85
|
-
conflicts.push(`组件 ${addonName} 表 "${tableName}" 使用了保留前缀,保留前缀包括: ${
|
|
92
|
+
if (RESERVED_NAMES.tablePrefix.some((prefix) => tableName.startsWith(prefix))) {
|
|
93
|
+
conflicts.push(`组件 ${addonName} 表 "${tableName}" 使用了保留前缀,保留前缀包括: ${RESERVED_NAMES.tablePrefix.join(', ')}`);
|
|
86
94
|
continue;
|
|
87
95
|
}
|
|
88
96
|
|
|
@@ -108,7 +116,7 @@ async function collectAddonResources(addonName: string, registry: ResourceRegist
|
|
|
108
116
|
onlyFiles: true,
|
|
109
117
|
absolute: true
|
|
110
118
|
})) {
|
|
111
|
-
const apiPath =
|
|
119
|
+
const apiPath = relative(addonApisDir, file).replace(/\.ts$/, '');
|
|
112
120
|
if (apiPath.indexOf('_') !== -1) continue;
|
|
113
121
|
|
|
114
122
|
try {
|
|
@@ -143,15 +151,16 @@ async function collectAddonResources(addonName: string, registry: ResourceRegist
|
|
|
143
151
|
onlyFiles: true,
|
|
144
152
|
absolute: true
|
|
145
153
|
})) {
|
|
146
|
-
const fileName =
|
|
154
|
+
const fileName = basename(file).replace(/\.ts$/, '');
|
|
147
155
|
if (fileName.startsWith('_')) continue;
|
|
148
156
|
|
|
149
157
|
// Addon 插件使用点号命名空间
|
|
150
158
|
const pluginName = `${addonName}.${fileName}`;
|
|
151
159
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
// 检查是否使用保留名称(检测核心插件名或点号前缀是保留名称)
|
|
161
|
+
const isReserved = RESERVED_NAMES.plugins.includes(pluginName) || (pluginName.includes('.') && RESERVED_NAMES.plugins.includes(pluginName.split('.')[0]));
|
|
162
|
+
if (isReserved) {
|
|
163
|
+
conflicts.push(`组件 ${addonName} 插件 "${pluginName}" 使用了保留名称,保留名称包括: ${RESERVED_NAMES.plugins.join(', ')}`);
|
|
155
164
|
continue;
|
|
156
165
|
}
|
|
157
166
|
|
|
@@ -182,7 +191,7 @@ async function collectUserResources(registry: ResourceRegistry): Promise<string[
|
|
|
182
191
|
onlyFiles: true,
|
|
183
192
|
absolute: true
|
|
184
193
|
})) {
|
|
185
|
-
const fileName =
|
|
194
|
+
const fileName = basename(file, '.json');
|
|
186
195
|
if (fileName.startsWith('_')) continue;
|
|
187
196
|
|
|
188
197
|
try {
|
|
@@ -190,8 +199,8 @@ async function collectUserResources(registry: ResourceRegistry): Promise<string[
|
|
|
190
199
|
const tableName = tableDefine.tableName || fileName;
|
|
191
200
|
|
|
192
201
|
// 检查是否使用保留前缀
|
|
193
|
-
if (
|
|
194
|
-
conflicts.push(`用户表 "${tableName}" 使用了保留前缀,保留前缀包括: ${
|
|
202
|
+
if (RESERVED_NAMES.tablePrefix.some((prefix) => tableName.startsWith(prefix))) {
|
|
203
|
+
conflicts.push(`用户表 "${tableName}" 使用了保留前缀,保留前缀包括: ${RESERVED_NAMES.tablePrefix.join(', ')}`);
|
|
195
204
|
continue;
|
|
196
205
|
}
|
|
197
206
|
|
|
@@ -218,7 +227,7 @@ async function collectUserResources(registry: ResourceRegistry): Promise<string[
|
|
|
218
227
|
onlyFiles: true,
|
|
219
228
|
absolute: true
|
|
220
229
|
})) {
|
|
221
|
-
const apiPath =
|
|
230
|
+
const apiPath = relative(userApisDir, file).replace(/\.ts$/, '');
|
|
222
231
|
if (apiPath.indexOf('_') !== -1) continue;
|
|
223
232
|
|
|
224
233
|
try {
|
|
@@ -254,12 +263,13 @@ async function collectUserResources(registry: ResourceRegistry): Promise<string[
|
|
|
254
263
|
onlyFiles: true,
|
|
255
264
|
absolute: true
|
|
256
265
|
})) {
|
|
257
|
-
const pluginName =
|
|
266
|
+
const pluginName = basename(file).replace(/\.ts$/, '');
|
|
258
267
|
if (pluginName.startsWith('_')) continue;
|
|
259
268
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
269
|
+
// 检查是否使用保留名称(检测核心插件名或点号前缀是保留名称)
|
|
270
|
+
const isReserved = RESERVED_NAMES.plugins.includes(pluginName) || (pluginName.includes('.') && RESERVED_NAMES.plugins.includes(pluginName.split('.')[0]));
|
|
271
|
+
if (isReserved) {
|
|
272
|
+
conflicts.push(`用户插件 "${pluginName}" 使用了保留名称,保留名称包括: ${RESERVED_NAMES.plugins.join(', ')}`);
|
|
263
273
|
continue;
|
|
264
274
|
}
|
|
265
275
|
|
package/checks/table.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* 验证表定义文件的格式和规则
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import { Logger } from '../
|
|
8
|
-
import { parseRule } from '../
|
|
6
|
+
import { basename } from 'pathe';
|
|
7
|
+
import { Logger } from '../lib/logger.js';
|
|
8
|
+
import { parseRule } from '../util.js';
|
|
9
9
|
import { paths } from '../paths.js';
|
|
10
|
-
import { scanAddons, getAddonDir } from '../
|
|
10
|
+
import { scanAddons, getAddonDir } from '../util.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* 表文件信息接口
|
|
@@ -97,8 +97,8 @@ export default async function (): Promise<boolean> {
|
|
|
97
97
|
// 合并进行验证逻辑
|
|
98
98
|
for (const { file, type, addonName } of allTableFiles) {
|
|
99
99
|
totalFiles++;
|
|
100
|
-
const fileName =
|
|
101
|
-
const fileBaseName =
|
|
100
|
+
const fileName = basename(file);
|
|
101
|
+
const fileBaseName = basename(file, '.json');
|
|
102
102
|
const fileType = type === 'project' ? '项目' : `组件${addonName}`;
|
|
103
103
|
|
|
104
104
|
try {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Addon 命令 - 插件管理
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
6
|
+
import { join } from 'pathe';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { Logger } from '../lib/logger.js';
|
|
9
|
+
|
|
10
|
+
export const addonCommand = {
|
|
11
|
+
async install(name: string, options: { source?: string }) {
|
|
12
|
+
const spinner = ora({
|
|
13
|
+
text: `正在安装插件: ${name}`,
|
|
14
|
+
color: 'cyan',
|
|
15
|
+
spinner: 'dots'
|
|
16
|
+
}).start();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// TODO: 实现插件安装逻辑
|
|
20
|
+
// 1. 从 source 或默认源下载插件
|
|
21
|
+
// 2. 解压到 addons 目录
|
|
22
|
+
// 3. 安装插件依赖
|
|
23
|
+
// 4. 执行插件安装脚本
|
|
24
|
+
|
|
25
|
+
spinner.warn(`插件安装功能开发中`);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
spinner.fail(`插件 ${name} 安装失败`);
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async uninstall(name: string, options: { keepData: boolean }) {
|
|
33
|
+
const spinner = ora({
|
|
34
|
+
text: `正在卸载插件: ${name}`,
|
|
35
|
+
color: 'cyan',
|
|
36
|
+
spinner: 'dots'
|
|
37
|
+
}).start();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// TODO: 实现插件卸载逻辑
|
|
41
|
+
// 1. 执行插件卸载脚本
|
|
42
|
+
// 2. 删除插件文件
|
|
43
|
+
// 3. 可选:删除插件数据
|
|
44
|
+
|
|
45
|
+
spinner.warn(`插件卸载功能开发中`);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
spinner.fail(`插件 ${name} 卸载失败`);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
async list() {
|
|
53
|
+
// TODO: 读取已安装的插件列表
|
|
54
|
+
console.log('已安装的插件:\n');
|
|
55
|
+
console.log('(功能开发中)');
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build 命令 - 构建项目
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from 'pathe';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { Logger } from '../lib/logger.js';
|
|
9
|
+
|
|
10
|
+
function getProjectRoot(): string {
|
|
11
|
+
let current = process.cwd();
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
while (current !== path.parse(current).root) {
|
|
14
|
+
if (existsSync(join(current, 'package.json'))) {
|
|
15
|
+
return current;
|
|
16
|
+
}
|
|
17
|
+
current = path.dirname(current);
|
|
18
|
+
}
|
|
19
|
+
return process.cwd();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface BuildOptions {
|
|
23
|
+
outdir: string;
|
|
24
|
+
minify: boolean;
|
|
25
|
+
sourcemap: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function buildCommand(options: BuildOptions) {
|
|
29
|
+
try {
|
|
30
|
+
const projectRoot = getProjectRoot();
|
|
31
|
+
const mainFile = join(projectRoot, 'main.ts');
|
|
32
|
+
|
|
33
|
+
if (!existsSync(mainFile)) {
|
|
34
|
+
Logger.error('未找到 main.ts 文件');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const spinner = ora({
|
|
39
|
+
text: '正在构建项目...',
|
|
40
|
+
color: 'cyan',
|
|
41
|
+
spinner: 'dots'
|
|
42
|
+
}).start();
|
|
43
|
+
|
|
44
|
+
const args = ['build', mainFile, '--outdir', options.outdir, '--target', 'bun'];
|
|
45
|
+
|
|
46
|
+
if (options.minify) {
|
|
47
|
+
args.push('--minify');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (options.sourcemap) {
|
|
51
|
+
args.push('--sourcemap');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const proc = Bun.spawn(['bun', ...args], {
|
|
55
|
+
cwd: projectRoot,
|
|
56
|
+
stdout: 'pipe',
|
|
57
|
+
stderr: 'pipe'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await proc.exited;
|
|
61
|
+
|
|
62
|
+
if (proc.exitCode === 0) {
|
|
63
|
+
spinner.succeed('项目构建完成');
|
|
64
|
+
Logger.success(`输出目录: ${options.outdir}`);
|
|
65
|
+
} else {
|
|
66
|
+
spinner.fail('项目构建失败');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
Logger.error('构建失败:');
|
|
71
|
+
console.error(error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
package/commands/dev.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev 命令 - 启动开发服务器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from 'pathe';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { Logger } from '../lib/logger.js';
|
|
8
|
+
|
|
9
|
+
interface DevOptions {
|
|
10
|
+
port: string;
|
|
11
|
+
host: string;
|
|
12
|
+
sync: boolean;
|
|
13
|
+
verbose: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getProjectRoot(): string {
|
|
17
|
+
let current = process.cwd();
|
|
18
|
+
while (current !== require('node:path').parse(current).root) {
|
|
19
|
+
if (existsSync(join(current, 'package.json'))) {
|
|
20
|
+
return current;
|
|
21
|
+
}
|
|
22
|
+
current = require('node:path').dirname(current);
|
|
23
|
+
}
|
|
24
|
+
return process.cwd();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function devCommand(options: DevOptions) {
|
|
28
|
+
try {
|
|
29
|
+
const projectRoot = getProjectRoot();
|
|
30
|
+
const mainFile = join(projectRoot, 'main.ts');
|
|
31
|
+
|
|
32
|
+
if (!existsSync(mainFile)) {
|
|
33
|
+
Logger.error('未找到 main.ts 文件,请确保在 Befly 项目目录下');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 设置环境变量
|
|
38
|
+
process.env.NODE_ENV = 'development';
|
|
39
|
+
process.env.APP_PORT = options.port;
|
|
40
|
+
process.env.APP_HOST = options.host;
|
|
41
|
+
|
|
42
|
+
if (options.verbose) {
|
|
43
|
+
process.env.LOG_DEBUG = '1';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Logger.info('正在启动开发服务器...\n');
|
|
47
|
+
Logger.info(`端口: ${options.port}`);
|
|
48
|
+
Logger.info(`主机: ${options.host}`);
|
|
49
|
+
Logger.info(`环境: development\n`);
|
|
50
|
+
|
|
51
|
+
// 检查环境变量文件
|
|
52
|
+
const envFile = join(projectRoot, '.env.development');
|
|
53
|
+
if (existsSync(envFile)) {
|
|
54
|
+
Logger.info(`环境变量文件: .env.development\n`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 使用 Bun.spawn 启动开发服务器(不使用 --watch 避免监听 node_modules)
|
|
58
|
+
|
|
59
|
+
const proc = Bun.spawn(['bun', '--env-file=.env.development', 'run', mainFile], {
|
|
60
|
+
cwd: projectRoot,
|
|
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'];
|
|
76
|
+
signals.forEach((signal) => {
|
|
77
|
+
process.on(signal, () => {
|
|
78
|
+
console.log(`\nShutting down dev server (${signal})...`);
|
|
79
|
+
proc.kill(signal);
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
proc.kill('SIGKILL');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}, 5000); // 5 秒强制关闭
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const exitCode = await proc.exited;
|
|
88
|
+
process.exit(exitCode || 0);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
Logger.error('启动开发服务器失败:');
|
|
91
|
+
console.error(error);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|