befly 3.2.0 → 3.3.0

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.
Files changed (73) hide show
  1. package/bin/index.ts +138 -0
  2. package/checks/conflict.ts +35 -25
  3. package/checks/table.ts +6 -6
  4. package/commands/addon.ts +57 -0
  5. package/commands/build.ts +74 -0
  6. package/commands/dev.ts +94 -0
  7. package/commands/index.ts +252 -0
  8. package/commands/script.ts +308 -0
  9. package/commands/start.ts +80 -0
  10. package/commands/syncApi.ts +328 -0
  11. package/{scripts → commands}/syncDb/apply.ts +2 -2
  12. package/{scripts → commands}/syncDb/constants.ts +13 -7
  13. package/{scripts → commands}/syncDb/ddl.ts +7 -5
  14. package/{scripts → commands}/syncDb/helpers.ts +18 -18
  15. package/{scripts → commands}/syncDb/index.ts +37 -23
  16. package/{scripts → commands}/syncDb/sqlite.ts +1 -1
  17. package/{scripts → commands}/syncDb/state.ts +10 -4
  18. package/{scripts → commands}/syncDb/table.ts +7 -7
  19. package/{scripts → commands}/syncDb/tableCreate.ts +7 -6
  20. package/{scripts → commands}/syncDb/types.ts +5 -5
  21. package/{scripts → commands}/syncDb/version.ts +1 -1
  22. package/commands/syncDb.ts +35 -0
  23. package/commands/syncDev.ts +174 -0
  24. package/commands/syncMenu.ts +368 -0
  25. package/config/env.ts +4 -4
  26. package/config/menu.json +67 -0
  27. package/{utils/crypto.ts → lib/cipher.ts} +16 -67
  28. package/lib/database.ts +296 -0
  29. package/{utils → lib}/dbHelper.ts +102 -56
  30. package/{utils → lib}/jwt.ts +124 -151
  31. package/{utils → lib}/logger.ts +47 -24
  32. package/lib/middleware.ts +271 -0
  33. package/{utils → lib}/redisHelper.ts +4 -4
  34. package/{utils/validate.ts → lib/validator.ts} +101 -78
  35. package/lifecycle/bootstrap.ts +63 -0
  36. package/lifecycle/checker.ts +165 -0
  37. package/lifecycle/cluster.ts +241 -0
  38. package/lifecycle/lifecycle.ts +139 -0
  39. package/lifecycle/loader.ts +513 -0
  40. package/main.ts +14 -12
  41. package/package.json +21 -9
  42. package/paths.ts +34 -0
  43. package/plugins/cache.ts +187 -0
  44. package/plugins/db.ts +4 -4
  45. package/plugins/logger.ts +1 -1
  46. package/plugins/redis.ts +4 -4
  47. package/router/api.ts +155 -0
  48. package/router/root.ts +53 -0
  49. package/router/static.ts +76 -0
  50. package/types/api.d.ts +0 -36
  51. package/types/befly.d.ts +8 -6
  52. package/types/common.d.ts +1 -1
  53. package/types/context.d.ts +3 -3
  54. package/types/util.d.ts +45 -0
  55. package/util.ts +301 -0
  56. package/config/fields.ts +0 -55
  57. package/config/regexAliases.ts +0 -51
  58. package/config/reserved.ts +0 -96
  59. package/scripts/syncDb/tests/constants.test.ts +0 -105
  60. package/scripts/syncDb/tests/ddl.test.ts +0 -134
  61. package/scripts/syncDb/tests/helpers.test.ts +0 -70
  62. package/scripts/syncDb.ts +0 -10
  63. package/types/index.d.ts +0 -450
  64. package/types/index.ts +0 -438
  65. package/types/validator.ts +0 -43
  66. package/utils/colors.ts +0 -221
  67. package/utils/database.ts +0 -348
  68. package/utils/helper.ts +0 -812
  69. package/utils/index.ts +0 -33
  70. package/utils/requestContext.ts +0 -167
  71. /package/{scripts → commands}/syncDb/schema.ts +0 -0
  72. /package/{utils → lib}/sqlBuilder.ts +0 -0
  73. /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();
@@ -3,11 +3,19 @@
3
3
  * 在系统启动前检测表名、API 路由、插件名等资源是否存在冲突
4
4
  */
5
5
 
6
- import path from 'node:path';
7
- import { Logger } from '../utils/logger.js';
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 '../utils/helper.js';
10
- import { isReservedTableName, isReservedPluginName, isReservedAddonName, getReservedTablePrefixes, getReservedPlugins, getReservedAddonNames } from '../config/reserved.js';
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.rootPluginDir,
44
+ cwd: paths.projectPluginDir,
37
45
  onlyFiles: true,
38
46
  absolute: true
39
47
  })) {
40
- const pluginName = path.basename(file).replace(/\.ts$/, '');
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 (isReservedAddonName(addonName)) {
62
- conflicts.push(`组件名称 "${addonName}" 使用了保留名称,保留名称包括: ${getReservedAddonNames().join(', ')}`);
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: addonTablesDir,
80
+ cwd: paths.rootTableDir,
73
81
  onlyFiles: true,
74
82
  absolute: true
75
83
  })) {
76
- const fileName = path.basename(file, '.json');
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 (isReservedTableName(tableName)) {
85
- conflicts.push(`组件 ${addonName} 表 "${tableName}" 使用了保留前缀,保留前缀包括: ${getReservedTablePrefixes().join(', ')}`);
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 = path.relative(addonApisDir, file).replace(/\.ts$/, '').replace(/\\/g, '/');
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 = path.basename(file).replace(/\.ts$/, '');
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
- if (isReservedPluginName(pluginName)) {
154
- conflicts.push(`组件 ${addonName} 插件 "${pluginName}" 使用了保留名称,保留名称包括: ${getReservedPlugins().join(', ')}`);
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 = path.basename(file, '.json');
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 (isReservedTableName(tableName)) {
194
- conflicts.push(`用户表 "${tableName}" 使用了保留前缀,保留前缀包括: ${getReservedTablePrefixes().join(', ')}`);
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 = path.relative(userApisDir, file).replace(/\.ts$/, '').replace(/\\/g, '/');
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 = path.basename(file).replace(/\.ts$/, '');
266
+ const pluginName = basename(file).replace(/\.ts$/, '');
258
267
  if (pluginName.startsWith('_')) continue;
259
268
 
260
- // 检查是否使用保留名称
261
- if (isReservedPluginName(pluginName)) {
262
- conflicts.push(`用户插件 "${pluginName}" 使用了保留名称,保留名称包括: ${getReservedPlugins().join(', ')}`);
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 path from 'node:path';
7
- import { Logger } from '../utils/logger.js';
8
- import { parseRule } from '../utils/index.js';
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 '../utils/helper.js';
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 = path.basename(file);
101
- const fileBaseName = path.basename(file, '.json');
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
+ }
@@ -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
+ }