befly 3.76.4 → 3.76.6
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/package.json +1 -1
- package/scripts/syncDb/index.js +1 -1
- package/scripts/syncDb/model.js +1 -1
- package/scripts/syncDb/plan.js +17 -11
- package/scripts/syncDb/report.js +62 -7
package/package.json
CHANGED
package/scripts/syncDb/index.js
CHANGED
|
@@ -30,7 +30,7 @@ async function buildPlan(mysql, targetTables, tableNames) {
|
|
|
30
30
|
export async function syncDb(mysqlConfig, options = {}) {
|
|
31
31
|
const apply = options.apply === true;
|
|
32
32
|
const list = options.list === true;
|
|
33
|
-
const tableNames = Array.isArray(options.tableNames) ? options.tableNames
|
|
33
|
+
const tableNames = [...new Set((Array.isArray(options.tableNames) ? options.tableNames : options.tableName ? [options.tableName] : []).filter(Boolean))];
|
|
34
34
|
const tableScope = tableNames.length > 0 ? tableNames.join(", ") : "";
|
|
35
35
|
const startedAt = nowText();
|
|
36
36
|
let client;
|
package/scripts/syncDb/model.js
CHANGED
|
@@ -33,7 +33,7 @@ export function selectTables(tables, tableNames = []) {
|
|
|
33
33
|
|
|
34
34
|
const selectedNameSet = new Set(selectedNames);
|
|
35
35
|
const selectedTables = Object.fromEntries(Object.entries(tables).filter(([key, table]) => selectedNameSet.has(key) || selectedNameSet.has(table.codeName) || selectedNameSet.has(table.fileName)));
|
|
36
|
-
const matchedNames = new Set(Object.
|
|
36
|
+
const matchedNames = new Set(Object.entries(selectedTables).flatMap(([key, table]) => [key, table.codeName, table.fileName]));
|
|
37
37
|
const missingName = selectedNames.find((name) => !matchedNames.has(name));
|
|
38
38
|
if (missingName) throw new TypeError(`找不到 Table 定义:${missingName}`);
|
|
39
39
|
return selectedTables;
|
package/scripts/syncDb/plan.js
CHANGED
|
@@ -15,21 +15,28 @@ function targetDbType(fieldType) {
|
|
|
15
15
|
return { integer: "bigint", number: "decimal", varchar: "varchar", text: "mediumtext" }[fieldType];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
const compatibleFieldTypeSources = {
|
|
19
|
+
integer: new Set(["integer", "tinyint", "smallint", "mediumint", "int"]),
|
|
20
|
+
number: new Set(["number"]),
|
|
21
|
+
varchar: new Set(["varchar", "char"]),
|
|
22
|
+
text: new Set(["text", "tinytext", "longtext", "json", "varchar", "char"])
|
|
23
|
+
};
|
|
24
|
+
|
|
18
25
|
function compatibleFieldType(target, actual) {
|
|
19
|
-
|
|
20
|
-
integer: new Set(["integer", "tinyint", "smallint", "mediumint", "int"]),
|
|
21
|
-
number: new Set(["number"]),
|
|
22
|
-
varchar: new Set(["varchar", "char"]),
|
|
23
|
-
text: new Set(["text", "tinytext", "longtext", "json", "varchar", "char"])
|
|
24
|
-
};
|
|
25
|
-
return sources[target]?.has(actual) === true;
|
|
26
|
+
return compatibleFieldTypeSources[target]?.has(actual) === true;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
function columnChanges(target, actual) {
|
|
29
30
|
const changes = [];
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
if (target.fieldType !== actual.fieldType) changes.push({ property: "fieldType", before: actual.fieldType, after: target.fieldType });
|
|
32
|
+
if (target.fieldType === "varchar" && target.maxValue !== actual.maxValue) changes.push({ property: "maxValue", before: actual.maxValue, after: target.maxValue });
|
|
33
|
+
if (target.fieldType === "number") {
|
|
34
|
+
if (target.precision !== actual.precision) changes.push({ property: "precision", before: actual.precision, after: target.precision });
|
|
35
|
+
if (target.scale !== actual.scale) changes.push({ property: "scale", before: actual.scale, after: target.scale });
|
|
32
36
|
}
|
|
37
|
+
if (target.nullable !== actual.nullable) changes.push({ property: "nullable", before: actual.nullable, after: target.nullable });
|
|
38
|
+
if (target.name !== actual.name) changes.push({ property: "name", before: actual.name, after: target.name });
|
|
39
|
+
if (target.unsigned !== actual.unsigned) changes.push({ property: "unsigned", before: actual.unsigned, after: target.unsigned });
|
|
33
40
|
if (!sameDefault(target.default, actual.default)) changes.push({ property: "default", before: actual.default, after: target.default });
|
|
34
41
|
return changes;
|
|
35
42
|
}
|
|
@@ -68,8 +75,7 @@ export function updatePlanHash(plan) {
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
export function buildSchemaPlan(targetTables, actualTables, { tableNames = [], singleTable = false } = {}) {
|
|
71
|
-
const
|
|
72
|
-
const restrictUnmanagedTables = selectedTableNames.length > 0 || singleTable;
|
|
78
|
+
const restrictUnmanagedTables = singleTable || (Array.isArray(tableNames) ? tableNames.length > 0 : Boolean(tableNames));
|
|
73
79
|
const plan = { operations: [], notices: [], blocked: [], counts: {} };
|
|
74
80
|
|
|
75
81
|
for (const target of Object.values(targetTables)) {
|
package/scripts/syncDb/report.js
CHANGED
|
@@ -2,6 +2,8 @@ import { mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
function formatValue(value) {
|
|
5
|
+
if (value === true) return "是";
|
|
6
|
+
if (value === false) return "否";
|
|
5
7
|
if (value === "") return '""';
|
|
6
8
|
if (value === null || value === undefined) return "无";
|
|
7
9
|
if (value && typeof value === "object") {
|
|
@@ -39,18 +41,55 @@ function colorText(text, color, enabled) {
|
|
|
39
41
|
return ansi ? `${ansi}${text}\x1b[0m` : text;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
const fieldTypeProperties = new Set(["fieldType", "maxValue", "precision", "scale"]);
|
|
45
|
+
|
|
46
|
+
const propertyNames = {
|
|
47
|
+
nullable: "是否可空",
|
|
48
|
+
name: "注释",
|
|
49
|
+
default: "默认值",
|
|
50
|
+
unsigned: "无符号",
|
|
51
|
+
type: "索引类型",
|
|
52
|
+
fields: "索引字段"
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const indexTypeNames = { index: "普通", unique: "唯一" };
|
|
56
|
+
|
|
42
57
|
function targetFieldTypeText(field) {
|
|
43
58
|
if (!field || typeof field !== "object") return "-";
|
|
44
59
|
return field.fieldType === "integer" ? "BIGINT" : field.fieldType === "number" ? `DECIMAL(${field.precision},${field.scale})` : field.fieldType === "varchar" ? `VARCHAR(${field.maxValue})` : field.fieldType === "text" ? "MEDIUMTEXT" : "-";
|
|
45
60
|
}
|
|
46
61
|
|
|
62
|
+
function actualFieldTypeText(field) {
|
|
63
|
+
if (!field || typeof field !== "object") return "-";
|
|
64
|
+
const dbType = String(field.dbType || "").toLowerCase();
|
|
65
|
+
if (["tinyint", "smallint", "mediumint", "int", "bigint"].includes(dbType)) return dbType.toUpperCase();
|
|
66
|
+
if (["decimal", "numeric"].includes(dbType)) return `${dbType.toUpperCase()}(${field.precision},${field.scale})`;
|
|
67
|
+
if (["varchar", "char"].includes(dbType)) return `${dbType.toUpperCase()}(${field.maxValue})`;
|
|
68
|
+
if (dbType) return dbType.toUpperCase();
|
|
69
|
+
return targetFieldTypeText(field);
|
|
70
|
+
}
|
|
71
|
+
|
|
47
72
|
function fieldTypeText(operation) {
|
|
48
73
|
if (!["addColumn", "alterColumn"].includes(operation.kind)) return "-";
|
|
49
|
-
|
|
74
|
+
const after = targetFieldTypeText(operation.after);
|
|
75
|
+
if (operation.kind === "addColumn" || !operation.before) return after;
|
|
76
|
+
const before = actualFieldTypeText(operation.before);
|
|
77
|
+
return before === "-" || before === after ? after : `${before} -> ${after}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function blockedFieldTypeText(blocked) {
|
|
81
|
+
const target = blocked.target;
|
|
82
|
+
if (target && typeof target === "object" && target.fieldType && typeof blocked.before === "string") {
|
|
83
|
+
const before = blocked.before.toUpperCase();
|
|
84
|
+
const after = targetFieldTypeText(target);
|
|
85
|
+
return before === after ? after : `${before} -> ${after}`;
|
|
86
|
+
}
|
|
87
|
+
return targetFieldTypeText(target || blocked.after);
|
|
50
88
|
}
|
|
51
89
|
|
|
52
90
|
function preflightResult(operation, colors) {
|
|
53
|
-
|
|
91
|
+
if (!operation.preflight) return "-";
|
|
92
|
+
return colorText(operation.preflight.blocked ? "否" : "是", operation.preflight.blocked ? "red" : "limegreen", colors);
|
|
54
93
|
}
|
|
55
94
|
|
|
56
95
|
function buildSyncRows(plan, colors) {
|
|
@@ -70,13 +109,14 @@ function buildSyncRows(plan, colors) {
|
|
|
70
109
|
}));
|
|
71
110
|
for (const notice of plan.notices.filter((item) => !createdTables.has(item.table))) rows.push({ 类型: colorText("保留", "deepskyblue", colors), 风险: "-", 表: notice.table, 对象: notice.object || "-", 字段类型: "-", 变更: notice.message, 预检结果: "-" });
|
|
72
111
|
for (const blocked of plan.blocked.filter((item) => !createdTables.has(item.table))) {
|
|
73
|
-
const
|
|
112
|
+
const hasBooleanCompare = typeof blocked.before === "boolean" || typeof blocked.after === "boolean";
|
|
113
|
+
const change = blocked.kind === "preflight" || hasBooleanCompare ? blocked.reason : blocked.before !== undefined || blocked.after !== undefined ? `${formatValue(blocked.before)} -> ${formatValue(blocked.after)}` : blocked.reason;
|
|
74
114
|
rows.push({
|
|
75
115
|
类型: colorText("禁止同步", "orangered", colors),
|
|
76
116
|
风险: colorText("阻塞", "orangered", colors),
|
|
77
117
|
表: blocked.table,
|
|
78
118
|
对象: blocked.object || "-",
|
|
79
|
-
字段类型:
|
|
119
|
+
字段类型: blockedFieldTypeText(blocked),
|
|
80
120
|
变更: change,
|
|
81
121
|
预检结果: blocked.kind === "preflight" ? colorText("否", "red", colors) : "-"
|
|
82
122
|
});
|
|
@@ -139,11 +179,26 @@ function escapeTableValue(value) {
|
|
|
139
179
|
|
|
140
180
|
function archiveChangeText(operation) {
|
|
141
181
|
if (operation.kind === "createTable") return "创建全新表";
|
|
142
|
-
if (operation.kind === "addColumn") return
|
|
182
|
+
if (operation.kind === "addColumn") return `是否可空=${operation.after.nullable ? "是" : "否"},默认值=${formatValue(operation.after.default)}`;
|
|
143
183
|
if (operation.kind === "addIndex") return `新增${operation.after.type === "unique" ? "唯一" : "普通"}索引:${operation.after.fields.join(", ")}`;
|
|
144
|
-
if (operation.kind === "replaceIndex")
|
|
184
|
+
if (operation.kind === "replaceIndex") {
|
|
185
|
+
return operation.changes
|
|
186
|
+
.map((change) => {
|
|
187
|
+
const label = propertyNames[change.property] || change.property;
|
|
188
|
+
if (change.property === "type") return `${label}: ${indexTypeNames[change.before] || change.before} -> ${indexTypeNames[change.after] || change.after}`;
|
|
189
|
+
const before = Array.isArray(change.before) ? change.before.join(", ") : formatValue(change.before);
|
|
190
|
+
const after = Array.isArray(change.after) ? change.after.join(", ") : formatValue(change.after);
|
|
191
|
+
return `${label}: ${before} -> ${after}`;
|
|
192
|
+
})
|
|
193
|
+
.join(";");
|
|
194
|
+
}
|
|
145
195
|
if (operation.kind === "dropIndex") return `删除未在 Tables 中声明的索引:${operation.object}`;
|
|
146
|
-
return
|
|
196
|
+
return (
|
|
197
|
+
operation.changes
|
|
198
|
+
.filter((change) => !fieldTypeProperties.has(change.property))
|
|
199
|
+
.map((change) => `${propertyNames[change.property] || change.property}: ${formatValue(change.before)} -> ${formatValue(change.after)}`)
|
|
200
|
+
.join(";") || "-"
|
|
201
|
+
);
|
|
147
202
|
}
|
|
148
203
|
|
|
149
204
|
function renderArchive(context) {
|