befly 3.2.1 → 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
@@ -7,9 +7,9 @@
7
7
  * - 应用变更计划
8
8
  */
9
9
 
10
- import { Logger } from '../../utils/logger.js';
11
- import { toSnakeCase } from '../../utils/helper.js';
12
- import { parseRule } from '../../utils/helper.js';
10
+ import { snakeCase } from 'es-toolkit/string';
11
+ import { parseRule } from '../../util.js';
12
+ import { Logger } from '../../lib/logger.js';
13
13
  import { IS_MYSQL, IS_PG, IS_SQLITE, SYSTEM_INDEX_FIELDS, CHANGE_TYPE_LABELS, typeMapping } from './constants.js';
14
14
  import { quoteIdentifier, logFieldChange, resolveDefaultValue, generateDefaultSql, isStringOrArrayType, getSqlType } from './helpers.js';
15
15
  import { buildIndexSQL, generateDDLClause, isPgCompatibleTypeChange } from './ddl.js';
@@ -58,7 +58,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
58
58
 
59
59
  for (const [fieldKey, fieldRule] of Object.entries(fields)) {
60
60
  // 转换字段名为下划线格式(用于与数据库字段对比)
61
- const dbFieldName = toSnakeCase(fieldKey);
61
+ const dbFieldName = snakeCase(fieldKey);
62
62
 
63
63
  if (existingColumns[dbFieldName]) {
64
64
  const comparison = compareFieldDefinition(existingColumns[dbFieldName], fieldRule, dbFieldName);
@@ -145,7 +145,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
145
145
  const parsed = parseRule(fieldRule);
146
146
  const { name: fieldName, type: fieldType, max: fieldMax, default: fieldDefault } = parsed;
147
147
  const lenPart = isStringOrArrayType(fieldType) ? ` 长度:${parseInt(String(fieldMax))}` : '';
148
- Logger.info(`[新增字段] ${tableName}.${dbFieldName} 类型:${fieldType}${lenPart} 默认:${fieldDefault ?? 'NULL'}`);
148
+ Logger.info(` + 新增字段 ${dbFieldName} (${fieldType}${lenPart})`);
149
149
  addClauses.push(generateDDLClause(fieldKey, fieldRule, true));
150
150
  changed = true;
151
151
  globalCount.addFields++;
@@ -165,7 +165,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
165
165
  // 检查业务字段索引
166
166
  for (const [fieldKey, fieldRule] of Object.entries(fields)) {
167
167
  // 转换字段名为下划线格式
168
- const dbFieldName = toSnakeCase(fieldKey);
168
+ const dbFieldName = snakeCase(fieldKey);
169
169
 
170
170
  const parsed = parseRule(fieldRule);
171
171
  const indexName = `idx_${dbFieldName}`;
@@ -185,7 +185,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
185
185
  if (IS_PG) {
186
186
  for (const [fieldKey, fieldRule] of Object.entries(fields)) {
187
187
  // 转换字段名为下划线格式
188
- const dbFieldName = toSnakeCase(fieldKey);
188
+ const dbFieldName = snakeCase(fieldKey);
189
189
 
190
190
  if (existingColumns[dbFieldName]) {
191
191
  const parsed = parseRule(fieldRule);
@@ -8,12 +8,13 @@
8
8
  *
9
9
  * 注意:此模块从 table.ts 中提取,用于解除循环依赖
10
10
  */
11
-
12
- import { Logger } from '../../utils/logger.js';
11
+ import { snakeCase } from 'es-toolkit/string';
12
+ import { parseRule } from '../../util.js';
13
+ import { Logger } from '../../lib/logger.js';
13
14
  import { IS_MYSQL, IS_PG, MYSQL_TABLE_CONFIG } from './constants.js';
14
15
  import { quoteIdentifier } from './helpers.js';
15
16
  import { buildSystemColumnDefs, buildBusinessColumnDefs, buildIndexSQL } from './ddl.js';
16
- import { parseRule, toSnakeCase } from '../../utils/helper.js';
17
+
17
18
  import type { SQL } from 'bun';
18
19
 
19
20
  // 是否为计划模式(从环境变量读取)
@@ -48,7 +49,7 @@ async function addPostgresComments(sql: SQL, tableName: string, fields: Record<s
48
49
  // 业务字段注释
49
50
  for (const [fieldKey, fieldRule] of Object.entries(fields)) {
50
51
  // 转换字段名为下划线格式
51
- const dbFieldName = toSnakeCase(fieldKey);
52
+ const dbFieldName = snakeCase(fieldKey);
52
53
 
53
54
  const parsed = parseRule(fieldRule);
54
55
  const { name: fieldName } = parsed;
@@ -85,7 +86,7 @@ async function createTableIndexes(sql: SQL, tableName: string, fields: Record<st
85
86
  // 业务字段索引
86
87
  for (const [fieldKey, fieldRule] of Object.entries(fields)) {
87
88
  // 转换字段名为下划线格式
88
- const dbFieldName = toSnakeCase(fieldKey);
89
+ const dbFieldName = snakeCase(fieldKey);
89
90
 
90
91
  const parsed = parseRule(fieldRule);
91
92
  if (parsed.index === 1) {
@@ -132,7 +133,7 @@ export async function createTable(sql: SQL, tableName: string, fields: Record<st
132
133
  Logger.info(`[计划] ${createSQL.replace(/\n+/g, ' ')}`);
133
134
  } else {
134
135
  await sql.unsafe(createSQL);
135
- Logger.info(`[新建表] ${tableName}`);
136
+ Logger.info(` 新建表`);
136
137
  }
137
138
 
138
139
  // PostgreSQL: 添加列注释
@@ -83,10 +83,10 @@ export interface GlobalStats {
83
83
  */
84
84
  export interface ParsedFieldRule {
85
85
  name: string;
86
- type: string;
87
- min: any;
88
- max: any;
86
+ type: 'string' | 'number' | 'text' | 'array_string' | 'array_text';
87
+ min: number | null;
88
+ max: number | null;
89
89
  default: any;
90
- index: number;
91
- regex: string;
90
+ index: 0 | 1;
91
+ regex: string | null;
92
92
  }
@@ -5,8 +5,8 @@
5
5
  * - 数据库版本验证(MySQL/PostgreSQL/SQLite)
6
6
  */
7
7
 
8
- import { Logger } from '../../utils/logger.js';
9
8
  import { Env } from '../../config/env.js';
9
+ import { Logger } from '../../lib/logger.js';
10
10
  import { DB_VERSION_REQUIREMENTS, IS_MYSQL, IS_PG, IS_SQLITE } from './constants.js';
11
11
  import type { SQL } from 'bun';
12
12
 
@@ -0,0 +1,35 @@
1
+ /**
2
+ * SyncDb 命令 - 同步数据库表结构
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { join } from 'pathe';
7
+ import { existsSync } from 'node:fs';
8
+ import ora from 'ora';
9
+ import { Logger } from '../lib/logger.js';
10
+ import { SyncDb } from './syncDb/index.js';
11
+
12
+ interface SyncDbOptions {
13
+ table?: string;
14
+ dryRun: boolean;
15
+ }
16
+
17
+ export async function syncDbCommand(options: SyncDbOptions) {
18
+ try {
19
+ // 设置环境变量
20
+ if (options.dryRun) {
21
+ process.env.SYNC_DRY_RUN = '1';
22
+ }
23
+
24
+ if (options.table) {
25
+ process.env.SYNC_TABLE = options.table;
26
+ }
27
+
28
+ // 执行同步
29
+ await SyncDb();
30
+ Logger.info('数据库表结构同步完成');
31
+ } catch (error: any) {
32
+ Logger.error('数据库同步失败:', error);
33
+ process.exit(1);
34
+ }
35
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * SyncDev 命令 - 同步开发者管理员到数据库
3
+ * - 邮箱: 通过 DEV_EMAIL 环境变量配置(默认 dev@qq.com)
4
+ * - 姓名: 开发者
5
+ * - 密码: 使用 bcrypt 加密,通过 DEV_PASSWORD 环境变量配置
6
+ * - 角色: roleCode=dev, roleType=admin
7
+ * - 表名: core_admin
8
+ */
9
+
10
+ import { Logger } from '../lib/logger.js';
11
+ import { Cipher } from '../lib/cipher.js';
12
+ import { Database } from '../lib/database.js';
13
+ import { Env } from '../config/env.js';
14
+
15
+ interface SyncDevOptions {
16
+ plan?: boolean;
17
+ }
18
+
19
+ /**
20
+ * SyncDev 命令主函数
21
+ */
22
+ export async function syncDevCommand(options: SyncDevOptions = {}) {
23
+ try {
24
+ if (options.plan) {
25
+ Logger.info('[计划] 同步完成后将初始化/更新开发管理员账号(plan 模式不执行)');
26
+ return;
27
+ }
28
+
29
+ if (!Env.DEV_PASSWORD) {
30
+ Logger.warn('跳过开发管理员初始化:缺少 DEV_PASSWORD 配置');
31
+ return;
32
+ }
33
+
34
+ Logger.info('开始同步开发管理员账号...\n');
35
+
36
+ // 连接数据库(SQL + Redis)
37
+ await Database.connect();
38
+
39
+ const helper = Database.getDbHelper();
40
+
41
+ // 检查 core_admin 表是否存在
42
+ const existAdmin = await helper.tableExists('core_admin');
43
+ if (!existAdmin) {
44
+ Logger.warn('跳过开发管理员初始化:未检测到 core_admin 表');
45
+ return;
46
+ }
47
+
48
+ // 检查 core_role 表是否存在
49
+ const existRole = await helper.tableExists('core_role');
50
+ if (!existRole) {
51
+ Logger.warn('跳过开发管理员初始化:未检测到 core_role 表');
52
+ return;
53
+ }
54
+
55
+ // 检查 core_menu 表是否存在
56
+ const existMenu = await helper.tableExists('core_menu');
57
+ if (!existMenu) {
58
+ Logger.warn('跳过开发管理员初始化:未检测到 core_menu 表');
59
+ return;
60
+ }
61
+
62
+ // 查询所有菜单 ID
63
+ const allMenus = await helper.getAll({
64
+ table: 'core_menu',
65
+ fields: ['id']
66
+ });
67
+
68
+ if (!allMenus || !Array.isArray(allMenus)) {
69
+ Logger.warn('查询菜单失败或菜单表为空');
70
+ return;
71
+ }
72
+
73
+ const menuIds = allMenus.length > 0 ? allMenus.map((m: any) => m.id).join(',') : '';
74
+ Logger.info(`查询到 ${allMenus.length} 个菜单,ID 列表: ${menuIds || '(空)'}`);
75
+
76
+ // 查询所有接口 ID
77
+ const existApi = await helper.tableExists('core_api');
78
+ let apiIds = '';
79
+ if (existApi) {
80
+ const allApis = await helper.getAll({
81
+ table: 'core_api',
82
+ fields: ['id']
83
+ });
84
+
85
+ if (allApis && Array.isArray(allApis) && allApis.length > 0) {
86
+ apiIds = allApis.map((a: any) => a.id).join(',');
87
+ Logger.info(`查询到 ${allApis.length} 个接口,ID 列表: ${apiIds}`);
88
+ } else {
89
+ Logger.info('未查询到接口数据');
90
+ }
91
+ } else {
92
+ Logger.info('接口表不存在,跳过接口权限配置');
93
+ }
94
+
95
+ // 查询或创建 dev 角色
96
+ let devRole = await helper.getOne({
97
+ table: 'core_role',
98
+ where: { code: 'dev' }
99
+ });
100
+
101
+ if (devRole) {
102
+ // 更新 dev 角色的菜单和接口权限
103
+ await helper.updData({
104
+ table: 'core_role',
105
+ where: { code: 'dev' },
106
+ data: {
107
+ name: '开发者角色',
108
+ description: '拥有所有菜单和接口权限的开发者角色',
109
+ menus: menuIds,
110
+ apis: apiIds
111
+ }
112
+ });
113
+ Logger.info('dev 角色菜单和接口权限已更新');
114
+ } else {
115
+ // 创建 dev 角色
116
+ const roleId = await helper.insData({
117
+ table: 'core_role',
118
+ data: {
119
+ name: '开发者角色',
120
+ code: 'dev',
121
+ description: '拥有所有菜单和接口权限的开发者角色',
122
+ menus: menuIds,
123
+ apis: apiIds,
124
+ sort: 0
125
+ }
126
+ });
127
+ devRole = { id: roleId };
128
+ Logger.info('dev 角色已创建');
129
+ }
130
+
131
+ // 使用 bcrypt 加密密码
132
+ const hashed = await Cipher.hashPassword(Env.DEV_PASSWORD);
133
+
134
+ // 准备开发管理员数据
135
+ const devData = {
136
+ name: '开发者',
137
+ nickname: '开发者',
138
+ email: Env.DEV_EMAIL,
139
+ username: 'dev',
140
+ password: hashed,
141
+ roleId: devRole.id,
142
+ roleCode: 'dev',
143
+ roleType: 'admin'
144
+ };
145
+
146
+ // 查询现有账号
147
+ const existing = await helper.getOne({
148
+ table: 'core_admin',
149
+ where: { email: Env.DEV_EMAIL }
150
+ });
151
+
152
+ if (existing) {
153
+ // 更新现有账号
154
+ await helper.updData({
155
+ table: 'core_admin',
156
+ where: { email: Env.DEV_EMAIL },
157
+ data: devData
158
+ });
159
+ Logger.info(`✅ 开发管理员已更新:email=${Env.DEV_EMAIL}, username=dev, roleCode=dev, roleType=admin`);
160
+ } else {
161
+ // 插入新账号
162
+ await helper.insData({
163
+ table: 'core_admin',
164
+ data: devData
165
+ });
166
+ Logger.info(`✅ 开发管理员已初始化:email=${Env.DEV_EMAIL}, username=dev, roleCode=dev, roleType=admin`);
167
+ }
168
+ } catch (error: any) {
169
+ Logger.error('开发管理员同步失败:', error);
170
+ process.exit(1);
171
+ } finally {
172
+ await Database?.disconnect();
173
+ }
174
+ }