digitalsee-ai-flow-cli 0.1.6 → 0.1.8
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/.github/workflows/ci.yml +24 -0
- package/.github/workflows/publish.yml +21 -0
- package/CHANGELOG.md +72 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +4 -4
- package/dist/api/client.js.map +1 -1
- package/dist/api/flow.d.ts.map +1 -1
- package/dist/api/flow.js +16 -13
- package/dist/api/flow.js.map +1 -1
- package/dist/api/knowledge.d.ts.map +1 -1
- package/dist/api/knowledge.js +3 -3
- package/dist/api/knowledge.js.map +1 -1
- package/dist/api/node.d.ts.map +1 -1
- package/dist/api/node.js +4 -4
- package/dist/api/node.js.map +1 -1
- package/dist/api/variables.d.ts.map +1 -1
- package/dist/api/variables.js +1 -1
- package/dist/api/variables.js.map +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +44 -34
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +24 -26
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/flow/account.d.ts.map +1 -1
- package/dist/commands/flow/account.js +17 -16
- package/dist/commands/flow/account.js.map +1 -1
- package/dist/commands/flow/analyze.d.ts.map +1 -1
- package/dist/commands/flow/analyze.js +42 -42
- package/dist/commands/flow/analyze.js.map +1 -1
- package/dist/commands/flow/create.d.ts.map +1 -1
- package/dist/commands/flow/create.js +7 -6
- package/dist/commands/flow/create.js.map +1 -1
- package/dist/commands/flow/edge.d.ts.map +1 -1
- package/dist/commands/flow/edge.js +36 -35
- package/dist/commands/flow/edge.js.map +1 -1
- package/dist/commands/flow/helpers.d.ts.map +1 -1
- package/dist/commands/flow/helpers.js +84 -74
- package/dist/commands/flow/helpers.js.map +1 -1
- package/dist/commands/flow/list.d.ts.map +1 -1
- package/dist/commands/flow/list.js +7 -7
- package/dist/commands/flow/list.js.map +1 -1
- package/dist/commands/flow/node.d.ts.map +1 -1
- package/dist/commands/flow/node.js +164 -134
- package/dist/commands/flow/node.js.map +1 -1
- package/dist/commands/flow/test.d.ts.map +1 -1
- package/dist/commands/flow/test.js +20 -12
- package/dist/commands/flow/test.js.map +1 -1
- package/dist/commands/flow/update.js +5 -5
- package/dist/commands/flow/update.js.map +1 -1
- package/dist/commands/flow/variables.d.ts.map +1 -1
- package/dist/commands/flow/variables.js +45 -43
- package/dist/commands/flow/variables.js.map +1 -1
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +1 -3
- package/dist/commands/flow.js.map +1 -1
- package/dist/commands/knowledge.d.ts.map +1 -1
- package/dist/commands/knowledge.js +68 -70
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/index.js +31 -20
- package/dist/index.js.map +1 -1
- package/dist/services/flowAnalyzer.d.ts.map +1 -1
- package/dist/services/flowAnalyzer.js +256 -203
- package/dist/services/flowAnalyzer.js.map +1 -1
- package/dist/services/skillGenerator.d.ts.map +1 -1
- package/dist/services/skillGenerator.js +31 -31
- package/dist/services/skillGenerator.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -10
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/encoding.d.ts.map +1 -1
- package/dist/utils/encoding.js +3 -3
- package/dist/utils/encoding.js.map +1 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +471 -126
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/oauth.d.ts.map +1 -1
- package/dist/utils/oauth.js +54 -78
- package/dist/utils/oauth.js.map +1 -1
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +20 -16
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/updateCheck.d.ts.map +1 -0
- package/dist/utils/updateCheck.js +138 -0
- package/dist/utils/updateCheck.js.map +1 -0
- package/package.json +15 -7
- package/tsconfig.json +1 -1
- package/dist/utils/auth.d.ts.map +0 -1
- package/dist/utils/auth.js +0 -213
- package/dist/utils/auth.js.map +0 -1
package/dist/utils/format.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.fillConfigSchemaDefaults = fillConfigSchemaDefaults;
|
|
|
7
7
|
exports.parseVariableRefs = parseVariableRefs;
|
|
8
8
|
exports.mergeConfigValues = mergeConfigValues;
|
|
9
9
|
exports.validateNodePropertyValues = validateNodePropertyValues;
|
|
10
|
+
exports.validateSpecialNodeType = validateSpecialNodeType;
|
|
10
11
|
exports.deepMerge = deepMerge;
|
|
11
12
|
function configSchemaToParams(subParams) {
|
|
12
13
|
if (!subParams || !Array.isArray(subParams))
|
|
@@ -15,25 +16,25 @@ function configSchemaToParams(subParams) {
|
|
|
15
16
|
name: p.name,
|
|
16
17
|
type: inferFieldType(p),
|
|
17
18
|
required: p.required ?? false,
|
|
18
|
-
description: sanitizeDescription(p.description ||
|
|
19
|
+
description: sanitizeDescription(p.description || ''),
|
|
19
20
|
display_name: p.display_name || p.name,
|
|
20
21
|
options: normalizeOptionValues(p.option_value_objects ?? p.option_values),
|
|
21
22
|
default: p.value,
|
|
22
23
|
}));
|
|
23
24
|
}
|
|
24
25
|
function sanitizeDescription(desc) {
|
|
25
|
-
return desc.replace(/\u0000/g,
|
|
26
|
+
return desc.replace(/\u0000/g, '\\u0000');
|
|
26
27
|
}
|
|
27
28
|
function normalizeOptionValues(raw) {
|
|
28
29
|
if (!raw || !Array.isArray(raw) || raw.length === 0)
|
|
29
30
|
return undefined;
|
|
30
31
|
return raw
|
|
31
32
|
.map((item) => {
|
|
32
|
-
if (typeof item ===
|
|
33
|
+
if (typeof item === 'string') {
|
|
33
34
|
const v = item.trim();
|
|
34
35
|
return v ? { key: v, label: v } : null;
|
|
35
36
|
}
|
|
36
|
-
const value = String(item?.value ?? item?.label ??
|
|
37
|
+
const value = String(item?.value ?? item?.label ?? '').trim();
|
|
37
38
|
if (!value)
|
|
38
39
|
return null;
|
|
39
40
|
const label = String(item?.label ?? value).trim() || value;
|
|
@@ -48,77 +49,79 @@ function inferFieldType(param) {
|
|
|
48
49
|
if (param.type) {
|
|
49
50
|
return param.type;
|
|
50
51
|
}
|
|
51
|
-
return
|
|
52
|
+
return 'string';
|
|
52
53
|
}
|
|
53
54
|
function inferScenarios(actionName, description) {
|
|
54
55
|
const scenarios = [];
|
|
55
|
-
const text = `${actionName} ${description ||
|
|
56
|
-
if (text.includes(
|
|
57
|
-
scenarios.push(
|
|
56
|
+
const text = `${actionName} ${description || ''}`.toLowerCase();
|
|
57
|
+
if (text.includes('trigger') || text.includes('webhook')) {
|
|
58
|
+
scenarios.push('作为流程触发器,在外部事件发生时启动工作流');
|
|
58
59
|
}
|
|
59
|
-
if (text.includes(
|
|
60
|
-
scenarios.push(
|
|
60
|
+
if (text.includes('http') || text.includes('api') || text.includes('rest')) {
|
|
61
|
+
scenarios.push('调用外部 HTTP API 接口,实现系统间的数据交互');
|
|
61
62
|
}
|
|
62
|
-
if (text.includes(
|
|
63
|
-
text.includes(
|
|
64
|
-
text.includes(
|
|
65
|
-
text.includes(
|
|
66
|
-
text.includes(
|
|
67
|
-
scenarios.push(
|
|
63
|
+
if (text.includes('database') ||
|
|
64
|
+
text.includes('sql') ||
|
|
65
|
+
text.includes('mysql') ||
|
|
66
|
+
text.includes('postgres') ||
|
|
67
|
+
text.includes('数据')) {
|
|
68
|
+
scenarios.push('连接数据库,执行数据查询或写入操作');
|
|
68
69
|
}
|
|
69
|
-
if (text.includes(
|
|
70
|
-
scenarios.push(
|
|
70
|
+
if (text.includes('email') || text.includes('mail') || text.includes('邮件')) {
|
|
71
|
+
scenarios.push('发送或接收邮件通知');
|
|
71
72
|
}
|
|
72
|
-
if (text.includes(
|
|
73
|
-
scenarios.push(
|
|
73
|
+
if (text.includes('ai') || text.includes('llm') || text.includes('model')) {
|
|
74
|
+
scenarios.push('调用 AI 大模型进行文本生成、分析或推理');
|
|
74
75
|
}
|
|
75
|
-
if (text.includes(
|
|
76
|
-
scenarios.push(
|
|
76
|
+
if (text.includes('condition') || text.includes('条件')) {
|
|
77
|
+
scenarios.push('根据条件判断结果进行流程分支控制');
|
|
77
78
|
}
|
|
78
|
-
if (text.includes(
|
|
79
|
-
scenarios.push(
|
|
79
|
+
if (text.includes('loop') || text.includes('循环')) {
|
|
80
|
+
scenarios.push('对数据集进行批量循环处理');
|
|
80
81
|
}
|
|
81
|
-
if (text.includes(
|
|
82
|
-
scenarios.push(
|
|
82
|
+
if (text.includes('transform') || text.includes('转换') || text.includes('map')) {
|
|
83
|
+
scenarios.push('对数据进行格式转换或映射处理');
|
|
83
84
|
}
|
|
84
|
-
if (text.includes(
|
|
85
|
-
scenarios.push(
|
|
85
|
+
if (text.includes('notification') || text.includes('通知') || text.includes('message')) {
|
|
86
|
+
scenarios.push('发送消息通知或告警');
|
|
86
87
|
}
|
|
87
88
|
if (scenarios.length === 0) {
|
|
88
|
-
scenarios.push(
|
|
89
|
+
scenarios.push('执行自定义业务逻辑操作');
|
|
89
90
|
}
|
|
90
91
|
return scenarios;
|
|
91
92
|
}
|
|
92
93
|
function sanitizeFileName(name) {
|
|
93
94
|
return name
|
|
94
|
-
.replace(/[<>:"/\\|?*]/g,
|
|
95
|
-
.replace(/\s+/g,
|
|
96
|
-
.replace(/^\.+/,
|
|
95
|
+
.replace(/[<>:"/\\|?*]/g, '_')
|
|
96
|
+
.replace(/\s+/g, '_')
|
|
97
|
+
.replace(/^\.+/, '')
|
|
97
98
|
.substring(0, 200);
|
|
98
99
|
}
|
|
99
100
|
function fillConfigSchemaDefaults(schema) {
|
|
100
101
|
const clone = JSON.parse(JSON.stringify(schema));
|
|
101
102
|
function walk(node) {
|
|
102
|
-
if (!node || typeof node !==
|
|
103
|
+
if (!node || typeof node !== 'object')
|
|
103
104
|
return;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const n = node;
|
|
106
|
+
if (n.sub_params && Array.isArray(n.sub_params)) {
|
|
107
|
+
for (const child of n.sub_params) {
|
|
108
|
+
const c = child;
|
|
109
|
+
if (c.value === undefined || c.value === null) {
|
|
110
|
+
c.value = inferDefaultValue(c);
|
|
108
111
|
}
|
|
109
|
-
|
|
110
|
-
walk(
|
|
112
|
+
c.update = true;
|
|
113
|
+
walk(c);
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
|
-
if (
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
+
if (n.value === undefined || n.value === null) {
|
|
117
|
+
if (n.sub_params) {
|
|
118
|
+
n.value = null;
|
|
116
119
|
}
|
|
117
120
|
else {
|
|
118
|
-
|
|
121
|
+
n.value = inferDefaultValue(n);
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
|
-
|
|
124
|
+
n.update = true;
|
|
122
125
|
}
|
|
123
126
|
walk(clone);
|
|
124
127
|
return clone;
|
|
@@ -126,43 +129,43 @@ function fillConfigSchemaDefaults(schema) {
|
|
|
126
129
|
function inferDefaultValue(param) {
|
|
127
130
|
if (param.value !== undefined && param.value !== null)
|
|
128
131
|
return param.value;
|
|
129
|
-
const type = (param.page_control || param.type ||
|
|
130
|
-
if (type ===
|
|
132
|
+
const type = (param.page_control || param.type || '').toUpperCase();
|
|
133
|
+
if (type === 'SELECT') {
|
|
131
134
|
if (param.multi_valued)
|
|
132
135
|
return [];
|
|
133
136
|
const options = param.option_value_objects ?? param.option_values;
|
|
134
137
|
if (options && Array.isArray(options) && options.length > 0) {
|
|
135
138
|
const first = options[0];
|
|
136
|
-
if (typeof first ===
|
|
139
|
+
if (typeof first === 'string')
|
|
137
140
|
return first;
|
|
138
|
-
if (typeof first ===
|
|
139
|
-
return first.value ?? first.label ??
|
|
141
|
+
if (typeof first === 'object' && first !== null) {
|
|
142
|
+
return first.value ?? first.label ?? '';
|
|
140
143
|
}
|
|
141
144
|
}
|
|
142
145
|
return null;
|
|
143
146
|
}
|
|
144
147
|
switch (type) {
|
|
145
|
-
case
|
|
148
|
+
case 'NUMBER':
|
|
146
149
|
return 0;
|
|
147
|
-
case
|
|
148
|
-
case
|
|
150
|
+
case 'BOOLEAN':
|
|
151
|
+
case 'SWITCH':
|
|
149
152
|
return false;
|
|
150
|
-
case
|
|
151
|
-
case
|
|
152
|
-
case
|
|
153
|
+
case 'DATE':
|
|
154
|
+
case 'TIME':
|
|
155
|
+
case 'DATETIME':
|
|
153
156
|
return null;
|
|
154
|
-
case
|
|
155
|
-
return
|
|
156
|
-
case
|
|
157
|
-
case
|
|
157
|
+
case 'JSON':
|
|
158
|
+
return '{}';
|
|
159
|
+
case 'EXTEND_OBJ':
|
|
160
|
+
case 'FIXED_OBJ':
|
|
158
161
|
return param.sub_params ? null : {};
|
|
159
|
-
case
|
|
160
|
-
case
|
|
161
|
-
case
|
|
162
|
-
case
|
|
163
|
-
case
|
|
162
|
+
case 'TEXTAREA':
|
|
163
|
+
case 'PASSWORD':
|
|
164
|
+
case 'HIDDEN':
|
|
165
|
+
case 'LABEL':
|
|
166
|
+
case 'TEXT':
|
|
164
167
|
default:
|
|
165
|
-
return
|
|
168
|
+
return '';
|
|
166
169
|
}
|
|
167
170
|
}
|
|
168
171
|
function parseVariableRefs(text) {
|
|
@@ -173,7 +176,7 @@ function parseVariableRefs(text) {
|
|
|
173
176
|
refs.push({
|
|
174
177
|
raw: match[0],
|
|
175
178
|
nodeId: match[1],
|
|
176
|
-
path: match[2] ||
|
|
179
|
+
path: match[2] || '',
|
|
177
180
|
});
|
|
178
181
|
}
|
|
179
182
|
return refs;
|
|
@@ -181,12 +184,14 @@ function parseVariableRefs(text) {
|
|
|
181
184
|
function mergeConfigValues(schema, overrides) {
|
|
182
185
|
const clone = JSON.parse(JSON.stringify(schema));
|
|
183
186
|
function walk(node, overrideMap) {
|
|
184
|
-
|
|
187
|
+
const n = node;
|
|
188
|
+
if (!n.sub_params)
|
|
185
189
|
return;
|
|
186
|
-
for (const child of
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
for (const child of n.sub_params) {
|
|
191
|
+
const childName = child.name;
|
|
192
|
+
if (childName && childName in overrideMap) {
|
|
193
|
+
const val = overrideMap[childName];
|
|
194
|
+
if (child.sub_params && typeof val === 'object' && !Array.isArray(val)) {
|
|
190
195
|
walk(child, val);
|
|
191
196
|
}
|
|
192
197
|
else {
|
|
@@ -207,29 +212,30 @@ function mergeConfigValues(schema, overrides) {
|
|
|
207
212
|
function validateNodePropertyValues(schema) {
|
|
208
213
|
const errors = [];
|
|
209
214
|
function walk(node, path) {
|
|
210
|
-
if (!node || typeof node !==
|
|
215
|
+
if (!node || typeof node !== 'object')
|
|
211
216
|
return;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
const n = node;
|
|
218
|
+
if (n.sub_params && Array.isArray(n.sub_params)) {
|
|
219
|
+
for (const child of n.sub_params) {
|
|
220
|
+
const c = child;
|
|
221
|
+
const cName = String(c.name || '(anonymous)');
|
|
222
|
+
const childPath = path ? `${path}.${cName}` : cName;
|
|
223
|
+
checkField(c, childPath);
|
|
224
|
+
walk(c, childPath);
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
227
|
}
|
|
222
228
|
function checkField(field, fieldPath) {
|
|
223
229
|
const val = field.value;
|
|
224
|
-
const pageControl = (field.page_control || field.type ||
|
|
225
|
-
const isEmpty = val === undefined || val === null || val ===
|
|
230
|
+
const pageControl = String(field.page_control || field.type || '').toUpperCase();
|
|
231
|
+
const isEmpty = val === undefined || val === null || val === '';
|
|
226
232
|
// 1. 检查必填字段
|
|
227
233
|
if (field.required && isEmpty) {
|
|
228
234
|
errors.push({
|
|
229
235
|
field: fieldPath,
|
|
230
|
-
rule:
|
|
236
|
+
rule: 'required',
|
|
231
237
|
message: `必填字段 "${field.display_name} : ${field.name}" 未提供值`,
|
|
232
|
-
sample:
|
|
238
|
+
sample: '',
|
|
233
239
|
});
|
|
234
240
|
}
|
|
235
241
|
// 2. 如果有值,检查值类型是否匹配 page_control
|
|
@@ -240,65 +246,65 @@ function validateNodePropertyValues(schema) {
|
|
|
240
246
|
function checkTypeConstraint(field, fieldPath, pageControl, val) {
|
|
241
247
|
const sample = JSON.stringify(val).substring(0, 80);
|
|
242
248
|
switch (pageControl) {
|
|
243
|
-
case
|
|
249
|
+
case 'SELECT':
|
|
244
250
|
if (field.multi_valued) {
|
|
245
251
|
if (!Array.isArray(val)) {
|
|
246
252
|
errors.push({
|
|
247
253
|
field: fieldPath,
|
|
248
|
-
rule:
|
|
254
|
+
rule: 'type',
|
|
249
255
|
message: `SELECT(multi) 字段 "${field.display_name || field.name}" 期望 array 类型,实际为 ${typeof val}`,
|
|
250
256
|
sample,
|
|
251
257
|
});
|
|
252
258
|
}
|
|
253
259
|
}
|
|
254
260
|
else {
|
|
255
|
-
if (typeof val !==
|
|
261
|
+
if (typeof val !== 'string') {
|
|
256
262
|
errors.push({
|
|
257
263
|
field: fieldPath,
|
|
258
|
-
rule:
|
|
264
|
+
rule: 'type',
|
|
259
265
|
message: `SELECT 字段 "${field.display_name || field.name}" 期望 string 类型,实际为 ${typeof val}`,
|
|
260
266
|
sample,
|
|
261
267
|
});
|
|
262
268
|
}
|
|
263
269
|
}
|
|
264
270
|
break;
|
|
265
|
-
case
|
|
266
|
-
if (typeof val !==
|
|
271
|
+
case 'NUMBER':
|
|
272
|
+
if (typeof val !== 'number') {
|
|
267
273
|
errors.push({
|
|
268
274
|
field: fieldPath,
|
|
269
|
-
rule:
|
|
275
|
+
rule: 'type',
|
|
270
276
|
message: `NUMBER 字段 "${field.display_name || field.name}" 期望 number 类型,实际为 ${typeof val}`,
|
|
271
277
|
sample,
|
|
272
278
|
});
|
|
273
279
|
}
|
|
274
280
|
break;
|
|
275
|
-
case
|
|
276
|
-
case
|
|
277
|
-
if (typeof val !==
|
|
281
|
+
case 'BOOLEAN':
|
|
282
|
+
case 'SWITCH':
|
|
283
|
+
if (typeof val !== 'boolean') {
|
|
278
284
|
errors.push({
|
|
279
285
|
field: fieldPath,
|
|
280
|
-
rule:
|
|
286
|
+
rule: 'type',
|
|
281
287
|
message: `${pageControl} 字段 "${field.display_name || field.name}" 期望 boolean 类型,实际为 ${typeof val}`,
|
|
282
288
|
sample,
|
|
283
289
|
});
|
|
284
290
|
}
|
|
285
291
|
break;
|
|
286
|
-
case
|
|
287
|
-
case
|
|
288
|
-
if (typeof val !==
|
|
292
|
+
case 'EXTEND_OBJ':
|
|
293
|
+
case 'FIXED_OBJ':
|
|
294
|
+
if (typeof val !== 'object' || Array.isArray(val)) {
|
|
289
295
|
errors.push({
|
|
290
296
|
field: fieldPath,
|
|
291
|
-
rule:
|
|
292
|
-
message: `${pageControl} 字段 "${field.display_name || field.name}" 期望 object 类型,实际为 ${Array.isArray(val) ?
|
|
297
|
+
rule: 'type',
|
|
298
|
+
message: `${pageControl} 字段 "${field.display_name || field.name}" 期望 object 类型,实际为 ${Array.isArray(val) ? 'array' : typeof val}`,
|
|
293
299
|
sample,
|
|
294
300
|
});
|
|
295
301
|
}
|
|
296
302
|
break;
|
|
297
|
-
case
|
|
298
|
-
if (typeof val !==
|
|
303
|
+
case 'JSON':
|
|
304
|
+
if (typeof val !== 'string') {
|
|
299
305
|
errors.push({
|
|
300
306
|
field: fieldPath,
|
|
301
|
-
rule:
|
|
307
|
+
rule: 'type',
|
|
302
308
|
message: `JSON 字段 "${field.display_name || field.name}" 期望 JSON 字符串,实际为 ${typeof val}`,
|
|
303
309
|
sample,
|
|
304
310
|
});
|
|
@@ -310,34 +316,34 @@ function validateNodePropertyValues(schema) {
|
|
|
310
316
|
catch {
|
|
311
317
|
errors.push({
|
|
312
318
|
field: fieldPath,
|
|
313
|
-
rule:
|
|
319
|
+
rule: 'format',
|
|
314
320
|
message: `JSON 字段 "${field.display_name || field.name}" 的值不是有效的 JSON`,
|
|
315
321
|
sample: val.substring(0, 80),
|
|
316
322
|
});
|
|
317
323
|
}
|
|
318
324
|
}
|
|
319
325
|
break;
|
|
320
|
-
case
|
|
321
|
-
case
|
|
322
|
-
case
|
|
323
|
-
if (typeof val !==
|
|
326
|
+
case 'DATE':
|
|
327
|
+
case 'TIME':
|
|
328
|
+
case 'DATETIME':
|
|
329
|
+
if (typeof val !== 'string') {
|
|
324
330
|
errors.push({
|
|
325
331
|
field: fieldPath,
|
|
326
|
-
rule:
|
|
332
|
+
rule: 'type',
|
|
327
333
|
message: `${pageControl} 字段 "${field.display_name || field.name}" 期望日期/时间字符串,实际为 ${typeof val}`,
|
|
328
334
|
sample,
|
|
329
335
|
});
|
|
330
336
|
}
|
|
331
337
|
break;
|
|
332
|
-
case
|
|
333
|
-
case
|
|
334
|
-
case
|
|
335
|
-
case
|
|
336
|
-
case
|
|
337
|
-
if (typeof val !==
|
|
338
|
+
case 'TEXT':
|
|
339
|
+
case 'TEXTAREA':
|
|
340
|
+
case 'PASSWORD':
|
|
341
|
+
case 'HIDDEN':
|
|
342
|
+
case 'LABEL':
|
|
343
|
+
if (typeof val !== 'string') {
|
|
338
344
|
errors.push({
|
|
339
345
|
field: fieldPath,
|
|
340
|
-
rule:
|
|
346
|
+
rule: 'type',
|
|
341
347
|
message: `${pageControl} 字段 "${field.display_name || field.name}" 期望 string 类型,实际为 ${typeof val}`,
|
|
342
348
|
sample,
|
|
343
349
|
});
|
|
@@ -347,23 +353,338 @@ function validateNodePropertyValues(schema) {
|
|
|
347
353
|
break;
|
|
348
354
|
}
|
|
349
355
|
}
|
|
350
|
-
walk(schema,
|
|
356
|
+
walk(schema, '');
|
|
357
|
+
return errors;
|
|
358
|
+
}
|
|
359
|
+
const VALID_CONDITION_OPERATORS = new Set([
|
|
360
|
+
'eq',
|
|
361
|
+
'ne',
|
|
362
|
+
'gt',
|
|
363
|
+
'ge',
|
|
364
|
+
'lt',
|
|
365
|
+
'le',
|
|
366
|
+
'sw',
|
|
367
|
+
'ew',
|
|
368
|
+
'co',
|
|
369
|
+
'nco',
|
|
370
|
+
'in',
|
|
371
|
+
'nin',
|
|
372
|
+
'is_null',
|
|
373
|
+
'is_not_null',
|
|
374
|
+
]);
|
|
375
|
+
const NULL_OPERATORS = new Set(['is_null', 'is_not_null']);
|
|
376
|
+
const LOOP_TYPES = new Set(['NUMBER', 'ARRAY', 'CONDITION']);
|
|
377
|
+
const ERROR_EXEC_OPTIONS = new Set(['END', 'CONTINUE', 'BREAK']);
|
|
378
|
+
const SPECIAL_NODE_TYPES = new Set(['ConditionNode', 'LoopNode', 'SelfLoopNode', 'SwitchNode']);
|
|
379
|
+
function safeSample(val) {
|
|
380
|
+
return (JSON.stringify(val) ?? '').substring(0, 80);
|
|
381
|
+
}
|
|
382
|
+
function validateOrList(orList, path) {
|
|
383
|
+
const errors = [];
|
|
384
|
+
if (!Array.isArray(orList)) {
|
|
385
|
+
errors.push({
|
|
386
|
+
field: path,
|
|
387
|
+
rule: 'type',
|
|
388
|
+
message: `${path} 必须是 array 类型`,
|
|
389
|
+
sample: safeSample(orList),
|
|
390
|
+
});
|
|
391
|
+
return errors;
|
|
392
|
+
}
|
|
393
|
+
if (orList.length === 0) {
|
|
394
|
+
errors.push({
|
|
395
|
+
field: path,
|
|
396
|
+
rule: 'required',
|
|
397
|
+
message: `${path} 至少需要 1 个条件组`,
|
|
398
|
+
sample: '',
|
|
399
|
+
});
|
|
400
|
+
return errors;
|
|
401
|
+
}
|
|
402
|
+
orList.forEach((orItem, i) => {
|
|
403
|
+
const orPath = `${path}[${i}]`;
|
|
404
|
+
if (!orItem || typeof orItem !== 'object') {
|
|
405
|
+
errors.push({
|
|
406
|
+
field: orPath,
|
|
407
|
+
rule: 'type',
|
|
408
|
+
message: `${orPath} 必须是 object 类型`,
|
|
409
|
+
sample: safeSample(orItem),
|
|
410
|
+
});
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const andList = orItem.and_list;
|
|
414
|
+
if (!Array.isArray(andList)) {
|
|
415
|
+
errors.push({
|
|
416
|
+
field: `${orPath}.and_list`,
|
|
417
|
+
rule: 'type',
|
|
418
|
+
message: `${orPath}.and_list 必须是 array 类型`,
|
|
419
|
+
sample: safeSample(andList),
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (andList.length === 0) {
|
|
424
|
+
errors.push({
|
|
425
|
+
field: `${orPath}.and_list`,
|
|
426
|
+
rule: 'required',
|
|
427
|
+
message: `${orPath}.and_list 至少需要 1 个条件`,
|
|
428
|
+
sample: '',
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
andList.forEach((item, j) => {
|
|
433
|
+
const itemPath = `${orPath}.and_list[${j}]`;
|
|
434
|
+
if (!item || typeof item !== 'object') {
|
|
435
|
+
errors.push({
|
|
436
|
+
field: itemPath,
|
|
437
|
+
rule: 'type',
|
|
438
|
+
message: `${itemPath} 必须是 object 类型`,
|
|
439
|
+
sample: safeSample(item),
|
|
440
|
+
});
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (!item.query || typeof item.query !== 'string' || !item.query.trim()) {
|
|
444
|
+
errors.push({
|
|
445
|
+
field: `${itemPath}.query`,
|
|
446
|
+
rule: 'required',
|
|
447
|
+
message: `${itemPath}.query 是必填字段,须为变量引用`,
|
|
448
|
+
sample: '',
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
if (!item.operator || typeof item.operator !== 'string') {
|
|
452
|
+
errors.push({
|
|
453
|
+
field: `${itemPath}.operator`,
|
|
454
|
+
rule: 'required',
|
|
455
|
+
message: `${itemPath}.operator 是必填字段`,
|
|
456
|
+
sample: '',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
else if (!VALID_CONDITION_OPERATORS.has(item.operator)) {
|
|
460
|
+
errors.push({
|
|
461
|
+
field: `${itemPath}.operator`,
|
|
462
|
+
rule: 'type',
|
|
463
|
+
message: `${itemPath}.operator 无效: "${item.operator}",有效值: ${[...VALID_CONDITION_OPERATORS].join(', ')}`,
|
|
464
|
+
sample: item.operator,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (item.operator &&
|
|
468
|
+
!NULL_OPERATORS.has(item.operator) &&
|
|
469
|
+
(item.value === undefined || item.value === null)) {
|
|
470
|
+
errors.push({
|
|
471
|
+
field: `${itemPath}.value`,
|
|
472
|
+
rule: 'required',
|
|
473
|
+
message: `${itemPath}.value 在运算符为 "${item.operator}" 时必须提供`,
|
|
474
|
+
sample: '',
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
return errors;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 校验特殊节点类型(ConditionNode / LoopNode / SelfLoopNode / SwitchNode)的业务字段。
|
|
483
|
+
* 这些字段位于 data.properties 下,与 sub_params 同级。普通 ActionNode 直接返回空数组。
|
|
484
|
+
*/
|
|
485
|
+
function validateSpecialNodeType(nodeType, data) {
|
|
486
|
+
if (!SPECIAL_NODE_TYPES.has(nodeType))
|
|
487
|
+
return [];
|
|
488
|
+
const props = data.properties || {};
|
|
489
|
+
const errors = [];
|
|
490
|
+
switch (nodeType) {
|
|
491
|
+
case 'ConditionNode': {
|
|
492
|
+
if (props.or_list === undefined || props.or_list === null) {
|
|
493
|
+
errors.push({
|
|
494
|
+
field: 'properties.or_list',
|
|
495
|
+
rule: 'required',
|
|
496
|
+
message: 'ConditionNode 缺少必填字段 properties.or_list',
|
|
497
|
+
sample: '',
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
errors.push(...validateOrList(props.or_list, 'properties.or_list'));
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
case 'LoopNode':
|
|
506
|
+
case 'SelfLoopNode': {
|
|
507
|
+
// LoopNode 字段在 properties 下双份存在:直接属性 + sub_params[].value
|
|
508
|
+
// CLI --data 只填充 sub_params,API 返回时两者都有,需要从两处查找
|
|
509
|
+
const loopField = (name) => {
|
|
510
|
+
if (props[name] !== undefined && props[name] !== null)
|
|
511
|
+
return props[name];
|
|
512
|
+
const subParams = props.sub_params;
|
|
513
|
+
if (Array.isArray(subParams)) {
|
|
514
|
+
const entry = subParams.find((s) => s.name === name);
|
|
515
|
+
if (entry && entry.value !== undefined && entry.value !== null)
|
|
516
|
+
return entry.value;
|
|
517
|
+
}
|
|
518
|
+
return undefined;
|
|
519
|
+
};
|
|
520
|
+
const loopType = loopField('type');
|
|
521
|
+
if (!loopType || !LOOP_TYPES.has(loopType)) {
|
|
522
|
+
errors.push({
|
|
523
|
+
field: 'properties.type',
|
|
524
|
+
rule: 'required',
|
|
525
|
+
message: `循环节点 properties.type 必须是 NUMBER / ARRAY / CONDITION 之一,当前为 "${loopType}"`,
|
|
526
|
+
sample: loopType || '',
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
if (loopType === 'NUMBER') {
|
|
530
|
+
const startVal = loopField('start');
|
|
531
|
+
const endVal = loopField('end');
|
|
532
|
+
if (typeof startVal !== 'number') {
|
|
533
|
+
errors.push({
|
|
534
|
+
field: 'properties.start',
|
|
535
|
+
rule: 'type',
|
|
536
|
+
message: `NUMBER 循环的 properties.start 必须是 number 类型,当前为 ${typeof startVal}`,
|
|
537
|
+
sample: String(startVal),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
else if (startVal < 1) {
|
|
541
|
+
errors.push({
|
|
542
|
+
field: 'properties.start',
|
|
543
|
+
rule: 'type',
|
|
544
|
+
message: `NUMBER 循环的 properties.start 必须 >= 1,当前为 ${startVal}`,
|
|
545
|
+
sample: String(startVal),
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
if (typeof endVal !== 'number') {
|
|
549
|
+
errors.push({
|
|
550
|
+
field: 'properties.end',
|
|
551
|
+
rule: 'type',
|
|
552
|
+
message: `NUMBER 循环的 properties.end 必须是 number 类型,当前为 ${typeof endVal}`,
|
|
553
|
+
sample: String(endVal),
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
else if (typeof startVal === 'number' && endVal < startVal) {
|
|
557
|
+
errors.push({
|
|
558
|
+
field: 'properties.end',
|
|
559
|
+
rule: 'type',
|
|
560
|
+
message: `NUMBER 循环的 properties.end (${endVal}) 必须 >= properties.start (${startVal})`,
|
|
561
|
+
sample: String(endVal),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (loopType === 'ARRAY' || loopType === 'CONDITION') {
|
|
566
|
+
const exprVal = loopField('expression');
|
|
567
|
+
if (!exprVal || typeof exprVal !== 'string' || !exprVal.trim()) {
|
|
568
|
+
errors.push({
|
|
569
|
+
field: 'properties.expression',
|
|
570
|
+
rule: 'required',
|
|
571
|
+
message: `${loopType} 循环的 properties.expression 是必填字段`,
|
|
572
|
+
sample: exprVal ? String(exprVal).substring(0, 80) : '',
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const ascVal = loopField('asc');
|
|
577
|
+
if (typeof ascVal !== 'boolean') {
|
|
578
|
+
errors.push({
|
|
579
|
+
field: 'properties.asc',
|
|
580
|
+
rule: 'type',
|
|
581
|
+
message: `properties.asc 必须是 boolean 类型,当前为 ${typeof ascVal}`,
|
|
582
|
+
sample: String(ascVal),
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
const errExecVal = loopField('errorExec');
|
|
586
|
+
if (!errExecVal || !ERROR_EXEC_OPTIONS.has(errExecVal)) {
|
|
587
|
+
errors.push({
|
|
588
|
+
field: 'properties.errorExec',
|
|
589
|
+
rule: 'required',
|
|
590
|
+
message: `properties.errorExec 必须是 END / CONTINUE / BREAK 之一,当前为 "${errExecVal}"`,
|
|
591
|
+
sample: errExecVal ? String(errExecVal) : '',
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
case 'SwitchNode': {
|
|
597
|
+
const branches = props.branches;
|
|
598
|
+
if (!Array.isArray(branches)) {
|
|
599
|
+
errors.push({
|
|
600
|
+
field: 'properties.branches',
|
|
601
|
+
rule: 'type',
|
|
602
|
+
message: `properties.branches 必须是 array 类型,当前为 ${typeof branches}`,
|
|
603
|
+
sample: safeSample(branches),
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
else if (branches.length < 2 || branches.length > 10) {
|
|
607
|
+
errors.push({
|
|
608
|
+
field: 'properties.branches',
|
|
609
|
+
rule: 'type',
|
|
610
|
+
message: `properties.branches 需要 2-10 个分支,当前为 ${branches.length} 个`,
|
|
611
|
+
sample: '',
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
const seenIds = new Set();
|
|
616
|
+
branches.forEach((branch, i) => {
|
|
617
|
+
const brPath = `properties.branches[${i}]`;
|
|
618
|
+
if (!branch || typeof branch !== 'object') {
|
|
619
|
+
errors.push({
|
|
620
|
+
field: brPath,
|
|
621
|
+
rule: 'type',
|
|
622
|
+
message: `${brPath} 必须是 object 类型`,
|
|
623
|
+
sample: safeSample(branch),
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (!branch.id || typeof branch.id !== 'string' || !branch.id.trim()) {
|
|
628
|
+
errors.push({
|
|
629
|
+
field: `${brPath}.id`,
|
|
630
|
+
rule: 'required',
|
|
631
|
+
message: `${brPath}.id 是必填字段`,
|
|
632
|
+
sample: '',
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
else if (seenIds.has(branch.id)) {
|
|
636
|
+
errors.push({
|
|
637
|
+
field: `${brPath}.id`,
|
|
638
|
+
rule: 'type',
|
|
639
|
+
message: `分支 id "${branch.id}" 重复`,
|
|
640
|
+
sample: branch.id,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
seenIds.add(branch.id);
|
|
645
|
+
}
|
|
646
|
+
if (!branch.name || typeof branch.name !== 'string' || !branch.name.trim()) {
|
|
647
|
+
errors.push({
|
|
648
|
+
field: `${brPath}.name`,
|
|
649
|
+
rule: 'required',
|
|
650
|
+
message: `${brPath}.name 是必填字段`,
|
|
651
|
+
sample: '',
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
if (!branch.or_list) {
|
|
655
|
+
errors.push({
|
|
656
|
+
field: `${brPath}.or_list`,
|
|
657
|
+
rule: 'required',
|
|
658
|
+
message: `${brPath}.or_list 是必填字段`,
|
|
659
|
+
sample: '',
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
errors.push(...validateOrList(branch.or_list, `${brPath}.or_list`));
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
351
670
|
return errors;
|
|
352
671
|
}
|
|
353
672
|
function isPlainObject(value) {
|
|
354
673
|
return (value !== null &&
|
|
355
|
-
typeof value ===
|
|
674
|
+
typeof value === 'object' &&
|
|
356
675
|
!Array.isArray(value) &&
|
|
357
|
-
Object.prototype.toString.call(value) ===
|
|
676
|
+
Object.prototype.toString.call(value) === '[object Object]');
|
|
358
677
|
}
|
|
359
678
|
function deepMerge(target, ...sources) {
|
|
679
|
+
const tgt = target;
|
|
360
680
|
for (const source of sources) {
|
|
361
|
-
if (source == null || typeof source !==
|
|
681
|
+
if (source == null || typeof source !== 'object' || Array.isArray(source)) {
|
|
362
682
|
continue;
|
|
363
683
|
}
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
684
|
+
const src = source;
|
|
685
|
+
const subParams = tgt.sub_params;
|
|
686
|
+
for (const key of Object.keys(src)) {
|
|
687
|
+
const srcVal = src[key];
|
|
367
688
|
// sub_params-aware merge: 匹配 name 并原地修改 target 的 sub_params 中的 value
|
|
368
689
|
if (Array.isArray(subParams)) {
|
|
369
690
|
const matched = subParams.find((item) => item.name === key);
|
|
@@ -377,12 +698,36 @@ function deepMerge(target, ...sources) {
|
|
|
377
698
|
continue;
|
|
378
699
|
}
|
|
379
700
|
}
|
|
380
|
-
const tgtVal =
|
|
381
|
-
|
|
382
|
-
|
|
701
|
+
const tgtVal = tgt[key];
|
|
702
|
+
// sub_params 数组按 name 合并,避免整体替换导致数据丢失
|
|
703
|
+
if (key === 'sub_params' && Array.isArray(tgtVal) && Array.isArray(srcVal)) {
|
|
704
|
+
const tgtArr = tgtVal;
|
|
705
|
+
for (const srcItem of srcVal) {
|
|
706
|
+
if (!srcItem || typeof srcItem.name !== 'string') {
|
|
707
|
+
tgtArr.push(srcItem);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
const matched = tgtArr.find((t) => t.name === srcItem.name);
|
|
711
|
+
if (matched) {
|
|
712
|
+
if (matched.sub_params && isPlainObject(srcItem.value)) {
|
|
713
|
+
deepMerge(matched, srcItem.value);
|
|
714
|
+
}
|
|
715
|
+
else if (srcItem.value !== undefined) {
|
|
716
|
+
matched.value = srcItem.value;
|
|
717
|
+
}
|
|
718
|
+
if (srcItem.update !== undefined)
|
|
719
|
+
matched.update = srcItem.update;
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
tgtArr.push(srcItem);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
else if (isPlainObject(tgtVal) && isPlainObject(srcVal)) {
|
|
727
|
+
tgt[key] = deepMerge({ ...tgtVal }, srcVal);
|
|
383
728
|
}
|
|
384
729
|
else {
|
|
385
|
-
|
|
730
|
+
tgt[key] = srcVal;
|
|
386
731
|
}
|
|
387
732
|
}
|
|
388
733
|
}
|