befly 3.5.7 → 3.6.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 (80) hide show
  1. package/lib/addon.ts +77 -0
  2. package/lib/logger.ts +6 -15
  3. package/lifecycle/checker.ts +20 -49
  4. package/lifecycle/lifecycle.ts +7 -5
  5. package/lifecycle/loader.ts +5 -5
  6. package/main.ts +10 -1
  7. package/package.json +2 -9
  8. package/paths.ts +5 -54
  9. package/util.ts +1 -83
  10. package/apis/admin/del.ts +0 -35
  11. package/apis/admin/info.ts +0 -50
  12. package/apis/admin/ins.ts +0 -61
  13. package/apis/admin/list.ts +0 -20
  14. package/apis/admin/roleDetail.ts +0 -35
  15. package/apis/admin/roleSave.ts +0 -40
  16. package/apis/admin/upd.ts +0 -51
  17. package/apis/api/all.ts +0 -37
  18. package/apis/auth/login.ts +0 -78
  19. package/apis/auth/logout.ts +0 -23
  20. package/apis/auth/register.ts +0 -50
  21. package/apis/auth/sendSmsCode.ts +0 -36
  22. package/apis/cache/refresh.ts +0 -34
  23. package/apis/dashboard/addonList.ts +0 -47
  24. package/apis/dashboard/changelog.ts +0 -37
  25. package/apis/dashboard/configStatus.ts +0 -54
  26. package/apis/dashboard/environmentInfo.ts +0 -46
  27. package/apis/dashboard/performanceMetrics.ts +0 -23
  28. package/apis/dashboard/permissionStats.ts +0 -31
  29. package/apis/dashboard/serviceStatus.ts +0 -82
  30. package/apis/dashboard/systemInfo.ts +0 -26
  31. package/apis/dashboard/systemOverview.ts +0 -32
  32. package/apis/dashboard/systemResources.ts +0 -119
  33. package/apis/dict/all.ts +0 -25
  34. package/apis/dict/del.ts +0 -19
  35. package/apis/dict/detail.ts +0 -21
  36. package/apis/dict/ins.ts +0 -27
  37. package/apis/dict/list.ts +0 -18
  38. package/apis/dict/upd.ts +0 -31
  39. package/apis/menu/all.ts +0 -68
  40. package/apis/menu/del.ts +0 -37
  41. package/apis/menu/ins.ts +0 -20
  42. package/apis/menu/list.ts +0 -21
  43. package/apis/menu/upd.ts +0 -29
  44. package/apis/role/apiDetail.ts +0 -30
  45. package/apis/role/apiSave.ts +0 -41
  46. package/apis/role/del.ts +0 -44
  47. package/apis/role/detail.ts +0 -24
  48. package/apis/role/ins.ts +0 -39
  49. package/apis/role/list.ts +0 -14
  50. package/apis/role/menuDetail.ts +0 -30
  51. package/apis/role/menuSave.ts +0 -38
  52. package/apis/role/save.ts +0 -44
  53. package/apis/role/upd.ts +0 -40
  54. package/bin/index.ts +0 -34
  55. package/checks/conflict.ts +0 -351
  56. package/checks/table.ts +0 -250
  57. package/commands/index.ts +0 -73
  58. package/commands/sync.ts +0 -88
  59. package/commands/syncApi.ts +0 -316
  60. package/commands/syncDb/apply.ts +0 -171
  61. package/commands/syncDb/constants.ts +0 -77
  62. package/commands/syncDb/ddl.ts +0 -191
  63. package/commands/syncDb/helpers.ts +0 -173
  64. package/commands/syncDb/index.ts +0 -217
  65. package/commands/syncDb/schema.ts +0 -199
  66. package/commands/syncDb/sqlite.ts +0 -50
  67. package/commands/syncDb/state.ts +0 -112
  68. package/commands/syncDb/table.ts +0 -214
  69. package/commands/syncDb/tableCreate.ts +0 -149
  70. package/commands/syncDb/types.ts +0 -92
  71. package/commands/syncDb/version.ts +0 -73
  72. package/commands/syncDb.ts +0 -34
  73. package/commands/syncDev.ts +0 -237
  74. package/commands/syncMenu.ts +0 -349
  75. package/commands/util.ts +0 -58
  76. package/tables/admin.json +0 -14
  77. package/tables/api.json +0 -8
  78. package/tables/dict.json +0 -8
  79. package/tables/menu.json +0 -8
  80. package/tables/role.json +0 -8
@@ -1,191 +0,0 @@
1
- /**
2
- * syncDb DDL 构建模块
3
- *
4
- * 包含:
5
- * - 构建索引 SQL
6
- * - 生成 DDL 子句(添加/修改列)
7
- * - 安全执行 DDL(MySQL 降级策略)
8
- * - 构建系统列和业务列定义
9
- */
10
-
11
- import { snakeCase } from 'es-toolkit/string';
12
- import { parseRule } from '../../util.js';
13
- import { Logger } from '../../lib/logger.js';
14
- import { IS_MYSQL, IS_PG, typeMapping } from './constants.js';
15
- import { quoteIdentifier, resolveDefaultValue, generateDefaultSql, getSqlType, escapeComment } from './helpers.js';
16
-
17
- import type { SQL } from 'bun';
18
- import type { ParsedFieldRule, AnyObject } from 'befly/types/common.js';
19
-
20
- /**
21
- * 构建索引操作 SQL(统一使用在线策略)
22
- *
23
- * @param tableName - 表名
24
- * @param indexName - 索引名
25
- * @param fieldName - 字段名
26
- * @param action - 操作类型(create/drop)
27
- * @returns SQL 语句
28
- */
29
- export function buildIndexSQL(tableName: string, indexName: string, fieldName: string, action: 'create' | 'drop'): string {
30
- const tableQuoted = quoteIdentifier(tableName);
31
- const indexQuoted = quoteIdentifier(indexName);
32
- const fieldQuoted = quoteIdentifier(fieldName);
33
-
34
- if (IS_MYSQL) {
35
- const parts = [];
36
- if (action === 'create') {
37
- parts.push(`ADD INDEX ${indexQuoted} (${fieldQuoted})`);
38
- } else {
39
- parts.push(`DROP INDEX ${indexQuoted}`);
40
- }
41
- // 始终使用在线算法
42
- parts.push('ALGORITHM=INPLACE');
43
- parts.push('LOCK=NONE');
44
- return `ALTER TABLE ${tableQuoted} ${parts.join(', ')}`;
45
- }
46
-
47
- if (IS_PG) {
48
- if (action === 'create') {
49
- // 始终使用 CONCURRENTLY
50
- return `CREATE INDEX CONCURRENTLY IF NOT EXISTS ${indexQuoted} ON ${tableQuoted}(${fieldQuoted})`;
51
- }
52
- return `DROP INDEX CONCURRENTLY IF EXISTS ${indexQuoted}`;
53
- }
54
-
55
- // SQLite
56
- if (action === 'create') {
57
- return `CREATE INDEX IF NOT EXISTS ${indexQuoted} ON ${tableQuoted}(${fieldQuoted})`;
58
- }
59
- return `DROP INDEX IF EXISTS ${indexQuoted}`;
60
- }
61
-
62
- /**
63
- * 构建系统字段列定义
64
- *
65
- * @returns 系统字段的列定义数组
66
- */
67
- export function buildSystemColumnDefs(): string[] {
68
- if (IS_MYSQL) {
69
- return ['`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT "主键ID"', '`created_at` BIGINT NOT NULL DEFAULT 0 COMMENT "创建时间"', '`updated_at` BIGINT NOT NULL DEFAULT 0 COMMENT "更新时间"', '`deleted_at` BIGINT NOT NULL DEFAULT 0 COMMENT "删除时间"', '`state` BIGINT NOT NULL DEFAULT 0 COMMENT "状态字段"'];
70
- }
71
- return ['"id" INTEGER PRIMARY KEY', '"created_at" INTEGER NOT NULL DEFAULT 0', '"updated_at" INTEGER NOT NULL DEFAULT 0', '"deleted_at" INTEGER NOT NULL DEFAULT 0', '"state" INTEGER NOT NULL DEFAULT 0'];
72
- }
73
-
74
- /**
75
- * 构建业务字段列定义
76
- *
77
- * @param fields - 字段定义对象
78
- * @returns 业务字段的列定义数组
79
- */
80
- export function buildBusinessColumnDefs(fields: Record<string, string>): string[] {
81
- const colDefs: string[] = [];
82
-
83
- for (const [fieldKey, fieldRule] of Object.entries(fields)) {
84
- // 转换字段名为下划线格式
85
- const dbFieldName = snakeCase(fieldKey);
86
-
87
- const parsed = parseRule(fieldRule);
88
- const { name: fieldName, type: fieldType, max: fieldMax, default: fieldDefault } = parsed;
89
- const sqlType = getSqlType(fieldType, fieldMax);
90
-
91
- // 使用公共函数处理默认值
92
- const actualDefault = resolveDefaultValue(fieldDefault, fieldType);
93
- const defaultSql = generateDefaultSql(actualDefault, fieldType);
94
-
95
- if (IS_MYSQL) {
96
- colDefs.push(`\`${dbFieldName}\` ${sqlType} NOT NULL${defaultSql} COMMENT "${escapeComment(fieldName)}"`);
97
- } else {
98
- colDefs.push(`"${dbFieldName}" ${sqlType} NOT NULL${defaultSql}`);
99
- }
100
- }
101
-
102
- return colDefs;
103
- }
104
-
105
- /**
106
- * 生成字段 DDL 子句(不含 ALTER TABLE 前缀)
107
- *
108
- * @param fieldKey - 字段键名
109
- * @param fieldRule - 字段规则字符串
110
- * @param isAdd - 是否为添加字段(true)还是修改字段(false)
111
- * @returns DDL 子句
112
- */
113
- export function generateDDLClause(fieldKey: string, fieldRule: string, isAdd: boolean = false): string {
114
- // 转换字段名为下划线格式
115
- const dbFieldName = snakeCase(fieldKey);
116
-
117
- const parsed = parseRule(fieldRule);
118
- const { name: fieldName, type: fieldType, max: fieldMax, default: fieldDefault } = parsed;
119
- const sqlType = getSqlType(fieldType, fieldMax);
120
-
121
- // 使用公共函数处理默认值
122
- const actualDefault = resolveDefaultValue(fieldDefault, fieldType);
123
- const defaultSql = generateDefaultSql(actualDefault, fieldType);
124
-
125
- if (IS_MYSQL) {
126
- return `${isAdd ? 'ADD COLUMN' : 'MODIFY COLUMN'} \`${dbFieldName}\` ${sqlType} NOT NULL${defaultSql} COMMENT "${escapeComment(fieldName)}"`;
127
- }
128
- if (IS_PG) {
129
- if (isAdd) return `ADD COLUMN IF NOT EXISTS "${dbFieldName}" ${sqlType} NOT NULL${defaultSql}`;
130
- // PG 修改:类型与非空可分条执行,生成 TYPE 改变;非空另由上层统一控制
131
- return `ALTER COLUMN "${dbFieldName}" TYPE ${sqlType}`;
132
- }
133
- // SQLite 仅支持 ADD COLUMN(>=3.50.0:支持 IF NOT EXISTS)
134
- if (isAdd) return `ADD COLUMN IF NOT EXISTS "${dbFieldName}" ${sqlType} NOT NULL${defaultSql}`;
135
- return '';
136
- }
137
-
138
- /**
139
- * 安全执行 DDL 语句(MySQL 降级策略)
140
- *
141
- * 执行 DDL 时按以下顺序尝试:
142
- * 1. ALGORITHM=INSTANT (最快,无表锁)
143
- * 2. ALGORITHM=INPLACE (在线 DDL)
144
- * 3. 传统 DDL (可能需要表锁)
145
- *
146
- * @param sql - SQL 客户端实例
147
- * @param stmt - DDL 语句
148
- * @returns 是否执行成功
149
- * @throws {Error} 如果所有尝试都失败
150
- */
151
- export async function executeDDLSafely(sql: SQL, stmt: string): Promise<boolean> {
152
- try {
153
- await sql.unsafe(stmt);
154
- return true;
155
- } catch (error: any) {
156
- // MySQL 专用降级路径
157
- if (stmt.includes('ALGORITHM=INSTANT')) {
158
- const inplaceSql = stmt.replace(/ALGORITHM=INSTANT/g, 'ALGORITHM=INPLACE');
159
- try {
160
- await sql.unsafe(inplaceSql);
161
- return true;
162
- } catch (inplaceError) {
163
- // 最后尝试传统DDL:移除 ALGORITHM/LOCK 附加子句后执行
164
- const traditionSql = stmt
165
- .replace(/,\s*ALGORITHM=INPLACE/g, '')
166
- .replace(/,\s*ALGORITHM=INSTANT/g, '')
167
- .replace(/,\s*LOCK=(NONE|SHARED|EXCLUSIVE)/g, '');
168
- await sql.unsafe(traditionSql);
169
- return true;
170
- }
171
- } else {
172
- throw error;
173
- }
174
- }
175
- }
176
-
177
- /**
178
- * PG 兼容类型变更识别:无需数据重写的宽化型变更
179
- *
180
- * @param currentType - 当前类型
181
- * @param newType - 新类型
182
- * @returns 是否为兼容变更
183
- */
184
- export function isPgCompatibleTypeChange(currentType: string, newType: string): boolean {
185
- const c = String(currentType || '').toLowerCase();
186
- const n = String(newType || '').toLowerCase();
187
- // varchar -> text 视为宽化
188
- if (c === 'character varying' && n === 'text') return true;
189
- // text -> character varying 非宽化(可能截断),不兼容
190
- return false;
191
- }
@@ -1,173 +0,0 @@
1
- /**
2
- * syncDb 辅助工具模块
3
- *
4
- * 包含:
5
- * - 标识符引用(反引号/双引号转义)
6
- * - 默认值处理
7
- * - 日志输出格式化
8
- */
9
-
10
- import { IS_MYSQL, IS_PG, typeMapping } from './constants.js';
11
- import { Logger } from '../../lib/logger.js';
12
-
13
- /**
14
- * 根据数据库类型引用标识符
15
- *
16
- * @param identifier - 标识符(表名、列名等)
17
- * @returns 引用后的标识符
18
- *
19
- * @example
20
- * quoteIdentifier('user_table')
21
- * // MySQL: `user_table`
22
- * // PostgreSQL: "user_table"
23
- * // SQLite: user_table
24
- */
25
- export function quoteIdentifier(identifier: string): string {
26
- if (IS_MYSQL) return `\`${identifier}\``;
27
- if (IS_PG) return `"${identifier}"`;
28
- return identifier; // SQLite 无需引用
29
- }
30
-
31
- /**
32
- * 处理默认值:将 'null' 字符串转换为对应类型的默认值
33
- *
34
- * @param fieldDefault - 字段默认值(可能是 'null' 字符串)
35
- * @param fieldType - 字段类型(number/string/text/array)
36
- * @returns 实际默认值
37
- *
38
- * @example
39
- * resolveDefaultValue('null', 'string') // => ''
40
- * resolveDefaultValue('null', 'number') // => 0
41
- * resolveDefaultValue('null', 'array') // => '[]'
42
- * resolveDefaultValue('null', 'text') // => 'null'
43
- * resolveDefaultValue('admin', 'string') // => 'admin'
44
- */
45
- export function resolveDefaultValue(fieldDefault: any, fieldType: 'number' | 'string' | 'text' | 'array'): any {
46
- if (fieldDefault !== 'null') {
47
- return fieldDefault;
48
- }
49
-
50
- // null 表示使用类型默认值
51
- switch (fieldType) {
52
- case 'number':
53
- return 0;
54
- case 'string':
55
- return '';
56
- case 'array':
57
- return '[]';
58
- case 'text':
59
- // text 类型不设置默认值,保持 'null'
60
- return 'null';
61
- default:
62
- return fieldDefault;
63
- }
64
- }
65
-
66
- /**
67
- * 生成 SQL DEFAULT 子句
68
- *
69
- * @param actualDefault - 实际默认值(已经过 resolveDefaultValue 处理)
70
- * @param fieldType - 字段类型
71
- * @returns SQL DEFAULT 子句字符串(包含前导空格),如果不需要则返回空字符串
72
- *
73
- * @example
74
- * generateDefaultSql(0, 'number') // => ' DEFAULT 0'
75
- * generateDefaultSql('admin', 'string') // => " DEFAULT 'admin'"
76
- * generateDefaultSql('', 'string') // => " DEFAULT ''"
77
- * generateDefaultSql('null', 'text') // => ''
78
- */
79
- export function generateDefaultSql(actualDefault: any, fieldType: 'number' | 'string' | 'text' | 'array'): string {
80
- // text 类型不设置默认值
81
- if (fieldType === 'text' || actualDefault === 'null') {
82
- return '';
83
- }
84
-
85
- // 仅 number/string/array 类型设置默认值
86
- if (fieldType === 'number' || fieldType === 'string' || fieldType === 'array') {
87
- if (typeof actualDefault === 'number' && !Number.isNaN(actualDefault)) {
88
- return ` DEFAULT ${actualDefault}`;
89
- } else {
90
- // 字符串需要转义单引号:' -> ''
91
- const escaped = String(actualDefault).replace(/'/g, "''");
92
- return ` DEFAULT '${escaped}'`;
93
- }
94
- }
95
-
96
- return '';
97
- }
98
-
99
- /**
100
- * 判断是否为字符串或数组类型(需要长度参数)
101
- *
102
- * @param fieldType - 字段类型
103
- * @returns 是否为字符串或数组类型
104
- *
105
- * @example
106
- * isStringOrArrayType('string') // => true
107
- * isStringOrArrayType('array_string') // => true
108
- * isStringOrArrayType('array_text') // => false
109
- * isStringOrArrayType('number') // => false
110
- * isStringOrArrayType('text') // => false
111
- */
112
- export function isStringOrArrayType(fieldType: string): boolean {
113
- return fieldType === 'string' || fieldType === 'array_string';
114
- }
115
-
116
- /**
117
- * 获取 SQL 数据类型
118
- *
119
- * @param fieldType - 字段类型(number/string/text/array_string/array_text)
120
- * @param fieldMax - 最大长度(string/array_string 类型需要)
121
- * @returns SQL 类型字符串
122
- *
123
- * @example
124
- * getSqlType('string', 100) // => 'VARCHAR(100)'
125
- * getSqlType('number', null) // => 'BIGINT'
126
- * getSqlType('text', null) // => 'MEDIUMTEXT'
127
- * getSqlType('array_string', 500) // => 'VARCHAR(500)'
128
- * getSqlType('array_text', null) // => 'MEDIUMTEXT'
129
- */
130
- export function getSqlType(fieldType: string, fieldMax: number | null): string {
131
- if (isStringOrArrayType(fieldType)) {
132
- return `${typeMapping[fieldType]}(${fieldMax})`;
133
- }
134
- return typeMapping[fieldType] || 'TEXT';
135
- }
136
-
137
- /**
138
- * 转义 SQL 注释中的双引号
139
- *
140
- * @param str - 注释字符串
141
- * @returns 转义后的字符串
142
- *
143
- * @example
144
- * escapeComment('用户名称') // => '用户名称'
145
- * escapeComment('用户"昵称"') // => '用户\\"昵称\\"'
146
- */
147
- export function escapeComment(str: string): string {
148
- return String(str).replace(/"/g, '\\"');
149
- }
150
-
151
- /**
152
- * 记录字段变更信息(紧凑格式)
153
- *
154
- * @param tableName - 表名
155
- * @param fieldName - 字段名
156
- * @param changeType - 变更类型(length/datatype/comment/default)
157
- * @param oldValue - 旧值
158
- * @param newValue - 新值
159
- * @param changeLabel - 变更类型的中文标签
160
- */
161
- export function logFieldChange(tableName: string, fieldName: string, changeType: string, oldValue: any, newValue: any, changeLabel: string): void {
162
- Logger.info(` ~ 修改 ${fieldName} ${changeLabel}: ${oldValue} -> ${newValue}`);
163
- }
164
-
165
- /**
166
- * 格式化字段列表为可读字符串
167
- *
168
- * @param fields - 字段名数组
169
- * @returns 格式化的字符串(逗号分隔)
170
- */
171
- export function formatFieldList(fields: string[]): string {
172
- return fields.map((f) => quoteIdentifier(f)).join(', ');
173
- }
@@ -1,217 +0,0 @@
1
- /**
2
- * syncDb 主入口文件
3
- *
4
- * 功能:
5
- * - 协调所有模块,执行数据库表结构同步
6
- * - 处理核心表、项目表、addon 表
7
- * - 提供统计信息和错误处理
8
- */
9
-
10
- import { basename } from 'pathe';
11
- import { snakeCase } from 'es-toolkit/string';
12
- import { Logger } from '../../lib/logger.js';
13
- import { Env } from '../../env.js';
14
- import { scanAddons, addonDirExists, getAddonDir } from '../../util.js';
15
- import { Database } from '../../lib/database.js';
16
- import checkTable from '../../checks/table.js';
17
- import { coreTableDir, projectTableDir } from '../../paths.js';
18
-
19
- // 导入模块化的功能
20
- import { ensureDbVersion } from './version.js';
21
- import { tableExists } from './schema.js';
22
- import { createTable, modifyTable } from './table.js';
23
- import { PerformanceTracker, ProgressLogger } from './state.js';
24
- import type { SQL } from 'bun';
25
-
26
- // 全局 SQL 客户端实例
27
- let sql: SQL | null = null;
28
-
29
- // 全局统计对象
30
- const globalCount: Record<string, number> = {
31
- processedTables: 0,
32
- createdTables: 0,
33
- modifiedTables: 0,
34
- addFields: 0,
35
- nameChanges: 0,
36
- typeChanges: 0,
37
- minChanges: 0,
38
- maxChanges: 0,
39
- defaultChanges: 0,
40
- indexCreate: 0,
41
- indexDrop: 0
42
- };
43
-
44
- // 导出统计接口
45
- export interface SyncDbStats {
46
- processedTables: number;
47
- createdTables: number;
48
- modifiedTables: number;
49
- addFields: number;
50
- nameChanges: number;
51
- typeChanges: number;
52
- minChanges: number;
53
- maxChanges: number;
54
- defaultChanges: number;
55
- indexCreate: number;
56
- indexDrop: number;
57
- }
58
-
59
- /**
60
- * 主同步函数
61
- *
62
- * 流程:
63
- * 1. 验证表定义文件
64
- * 2. 建立数据库连接并检查版本
65
- * 3. 扫描表定义文件(核心表、项目表、addon表)
66
- * 4. 对比并应用表结构变更
67
- * 5. 返回统计信息
68
- */
69
- export const SyncDb = async (): Promise<SyncDbStats> => {
70
- const perfTracker = new PerformanceTracker();
71
- const progressLogger = new ProgressLogger();
72
-
73
- try {
74
- // 重置全局统计,避免多次调用累加
75
- for (const k of Object.keys(globalCount)) {
76
- if (typeof globalCount[k] === 'number') globalCount[k] = 0;
77
- }
78
-
79
- // 阶段1:验证表定义文件
80
- perfTracker.markPhase('表定义验证');
81
- if (!(await checkTable())) {
82
- throw new Error('表定义验证失败');
83
- }
84
-
85
- // 阶段2:建立数据库连接并检查版本
86
- perfTracker.markPhase('数据库连接');
87
- sql = await Database.connectSql({ max: 1 });
88
- await ensureDbVersion(sql);
89
-
90
- // 阶段3:扫描表定义文件
91
- perfTracker.markPhase('扫描表文件');
92
- const tablesGlob = new Bun.Glob('*.json');
93
- const directories: Array<{ path: string; type: 'core' | 'app' | 'addon'; addonName?: string }> = [
94
- // 1. core 框架表(core_ 前缀)
95
- { path: coreTableDir, type: 'core' },
96
- // 2. 项目表(无前缀)
97
- { path: projectTableDir, type: 'app' }
98
- ];
99
-
100
- // 添加所有 addon 的 tables 目录(addon_{name}_ 前缀)
101
- const addons = scanAddons();
102
- for (const addon of addons) {
103
- if (addonDirExists(addon, 'tables')) {
104
- directories.push({
105
- path: getAddonDir(addon, 'tables'),
106
- type: 'addon',
107
- addonName: addon
108
- });
109
- }
110
- }
111
-
112
- // 统计表文件总数
113
- let totalTables = 0;
114
- for (const dirConfig of directories) {
115
- for await (const file of tablesGlob.scan({
116
- cwd: dirConfig.path,
117
- absolute: true,
118
- onlyFiles: true
119
- })) {
120
- const fileName = basename(file, '.json');
121
- if (!fileName.startsWith('_')) {
122
- totalTables++;
123
- }
124
- }
125
- }
126
- perfTracker.finishPhase('扫描表文件');
127
-
128
- // 阶段4:处理表文件
129
- perfTracker.markPhase('同步处理');
130
- let processedCount = 0;
131
-
132
- for (const dirConfig of directories) {
133
- const { path: dir, type, addonName } = dirConfig;
134
- const dirType = type === 'core' ? '核心' : type === 'addon' ? `组件${addonName}` : '项目';
135
-
136
- for await (const file of tablesGlob.scan({
137
- cwd: dir,
138
- absolute: true,
139
- onlyFiles: true
140
- })) {
141
- const fileName = basename(file, '.json');
142
-
143
- // 跳过以下划线开头的文件(这些是公共字段规则,不是表定义)
144
- if (fileName.startsWith('_')) {
145
- continue;
146
- }
147
-
148
- // 确定表名:
149
- // - core 表:core_{表名}
150
- // 例如:user.json → core_user
151
- // - addon 表:{addonName}_{表名}
152
- // 例如:admin addon 的 user.json → admin_user
153
- // - 项目表:{表名}
154
- // 例如:user.json → user
155
- let tableName = snakeCase(fileName);
156
- if (type === 'core') {
157
- // core 框架表,添加 core_ 前缀
158
- tableName = `core_${tableName}`;
159
- } else if (type === 'addon') {
160
- // addon 表,添加 {addonName}_ 前缀
161
- // 使用 snakeCase 统一转换(admin → admin)
162
- const addonNameSnake = snakeCase(addonName!);
163
- tableName = `${addonNameSnake}_${tableName}`;
164
- }
165
-
166
- processedCount++;
167
-
168
- const tableDefinition = await Bun.file(file).json();
169
- const existsTable = await tableExists(sql!, tableName);
170
-
171
- if (existsTable) {
172
- await modifyTable(sql!, tableName, tableDefinition, globalCount);
173
- } else {
174
- await createTable(sql!, tableName, tableDefinition);
175
- globalCount.createdTables++;
176
- }
177
- globalCount.processedTables++;
178
- }
179
- }
180
-
181
- perfTracker.finishPhase('同步处理');
182
-
183
- // 返回统计信息
184
- return {
185
- processedTables: globalCount.processedTables,
186
- createdTables: globalCount.createdTables,
187
- modifiedTables: globalCount.modifiedTables,
188
- addFields: globalCount.addFields,
189
- nameChanges: globalCount.nameChanges,
190
- typeChanges: globalCount.typeChanges,
191
- minChanges: globalCount.minChanges,
192
- maxChanges: globalCount.maxChanges,
193
- defaultChanges: globalCount.defaultChanges,
194
- indexCreate: globalCount.indexCreate,
195
- indexDrop: globalCount.indexDrop
196
- };
197
- } catch (error: any) {
198
- Logger.error(`数据库同步失败`, error);
199
- process.exit(1);
200
- } finally {
201
- if (sql) {
202
- try {
203
- await Database.disconnectSql();
204
- } catch (error: any) {
205
- Logger.warn('关闭数据库连接时出错:', error.message);
206
- }
207
- }
208
- }
209
- };
210
-
211
- // 如果直接运行此脚本(Bun 支持 import.meta.main)
212
- if (import.meta.main) {
213
- SyncDb().catch((error) => {
214
- Logger.error('数据库同步失败', error);
215
- process.exit(1);
216
- });
217
- }