digitalsee-ai-flow-cli 0.9.15 → 0.9.24-beta
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/README.md +3 -0
- package/dist/api/client.js +36 -10
- package/dist/api/flowVersion.js +45 -0
- package/dist/api/knowledge.js +58 -17
- package/dist/api/node.js +76 -38
- package/dist/commands/flow/edge.js +71 -12
- package/dist/commands/flow/list.js +2 -1
- package/dist/commands/flow/node.js +147 -45
- package/dist/commands/flow/offline.js +37 -10
- package/dist/commands/flow/online.js +4 -4
- package/dist/commands/flow/publish.js +122 -17
- package/dist/commands/flow/validate.js +12 -0
- package/dist/commands/flow/version.js +303 -0
- package/dist/commands/flow.js +2 -0
- package/dist/commands/knowledge.js +101 -27
- package/dist/services/classicNodePorts.js +413 -0
- package/dist/services/portValidator.js +304 -0
- package/dist/services/skillGenerator.js +215 -29
- package/dist/utils/connectable.js +221 -0
- package/dist/utils/nodePackage.js +23 -1
- package/dist/utils/nodeSchemaCache.js +87 -0
- package/package.json +1 -1
|
@@ -33,15 +33,105 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseActionIdRef = parseActionIdRef;
|
|
37
|
+
exports.resolveAction = resolveAction;
|
|
36
38
|
exports.registerFlowNode = registerFlowNode;
|
|
37
39
|
const fs = __importStar(require("fs"));
|
|
38
40
|
const flow_1 = require("@/api/flow");
|
|
39
41
|
const node_1 = require("@/api/node");
|
|
42
|
+
const knowledge_1 = require("@/api/knowledge");
|
|
40
43
|
const flowAnalyzer_1 = require("@/services/flowAnalyzer");
|
|
41
44
|
const format_1 = require("@/utils/format");
|
|
42
45
|
const helpers_1 = require("@/commands/flow/helpers");
|
|
43
46
|
const output_1 = require("@/utils/output");
|
|
44
47
|
const exit_code_1 = require("@/constants/exit-code");
|
|
48
|
+
const ACTION_ID_HELP = '动作 ID:纯数字(legacy 动作 id)、action_key(NodePackage 最新版)或 action_key@version(精确版本)';
|
|
49
|
+
/**
|
|
50
|
+
* 解析 --action-id 的三种形式:
|
|
51
|
+
* - `action_key@version` → NodePackage 精确版本
|
|
52
|
+
* - 纯数字 → legacy 动作 id
|
|
53
|
+
* - `action_key` → NodePackage 最新版(version 需经列表解析)
|
|
54
|
+
*/
|
|
55
|
+
function parseActionIdRef(actionId) {
|
|
56
|
+
const atIndex = actionId.indexOf('@');
|
|
57
|
+
if (atIndex > 0) {
|
|
58
|
+
const key = actionId.slice(0, atIndex).trim();
|
|
59
|
+
const version = Number(actionId.slice(atIndex + 1).trim());
|
|
60
|
+
if (key && Number.isFinite(version)) {
|
|
61
|
+
return { key, version };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (/^\d+$/.test(actionId)) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
return { key: actionId };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 按解析结果获取动作(以 RCS 为标准):
|
|
71
|
+
* a. `key@version` → getActionNodeSchema(NodePackage 权威 schema)
|
|
72
|
+
* b. 纯数字 → getActionDetail(legacy /acm/actions/:id,Long id 直接作创建身份)
|
|
73
|
+
* c. `key`(需 linkId)→ connectable 按 link_id 解析(RCS loadConnectableNodeActions 语义),
|
|
74
|
+
* 创建身份遵循 RCS resolveClassicCreateActionId:Long id 直接用,否则 '0' 哨兵
|
|
75
|
+
* (真实身份是 action_key + action_ver)
|
|
76
|
+
*/
|
|
77
|
+
async function resolveAction(actionId, ctx) {
|
|
78
|
+
const ref = parseActionIdRef(actionId);
|
|
79
|
+
if (ref.key && ref.version !== undefined) {
|
|
80
|
+
const nodeSchema = await (0, node_1.getActionNodeSchema)(ref.key, ref.version);
|
|
81
|
+
return nodeSchemaToAction(nodeSchema);
|
|
82
|
+
}
|
|
83
|
+
if (!ref.key) {
|
|
84
|
+
try {
|
|
85
|
+
return await (0, node_1.getActionDetail)(actionId);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!ctx.linkId) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const result = await (0, knowledge_1.searchNodesByCategory)({
|
|
95
|
+
mode: 'independent',
|
|
96
|
+
scene: '',
|
|
97
|
+
linkId: ctx.linkId,
|
|
98
|
+
flowId: ctx.flowId,
|
|
99
|
+
trigger: ctx.trigger || false,
|
|
100
|
+
});
|
|
101
|
+
const exact = result.items.find((candidate) => candidate.connector.id === ctx.linkId) ?? result.items[0];
|
|
102
|
+
const matched = (exact?.matched_actions ?? []).find((a) => a.action_key === ref.key);
|
|
103
|
+
if (!matched) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
matched.id = toClassicCreateActionId(matched);
|
|
107
|
+
if (matched.node_schema && matched.action_ver === undefined) {
|
|
108
|
+
matched.action_ver = matched.node_schema.version;
|
|
109
|
+
}
|
|
110
|
+
return matched;
|
|
111
|
+
}
|
|
112
|
+
/** RCS classicNodeActionIdentity:创建接口需要 Long action_id;非 Long(字符串 key 等)用 '0' 哨兵 */
|
|
113
|
+
function toClassicCreateActionId(action) {
|
|
114
|
+
const raw = action.id;
|
|
115
|
+
if (raw !== undefined && raw !== null && /^\d+$/.test(String(raw))) {
|
|
116
|
+
return String(raw);
|
|
117
|
+
}
|
|
118
|
+
return '0';
|
|
119
|
+
}
|
|
120
|
+
function nodeSchemaToAction(nodeSchema) {
|
|
121
|
+
return {
|
|
122
|
+
// legacy 哨兵:仅创建接口需要 action_id,不能作为动作持久身份
|
|
123
|
+
id: '0',
|
|
124
|
+
backend_action_id: '0',
|
|
125
|
+
action_key: nodeSchema.key,
|
|
126
|
+
action_ver: nodeSchema.version,
|
|
127
|
+
is_trigger: false,
|
|
128
|
+
name: nodeSchema.name,
|
|
129
|
+
connector_id: '',
|
|
130
|
+
status: 'active',
|
|
131
|
+
config_schema: {},
|
|
132
|
+
node_schema: nodeSchema,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
45
135
|
function registerFlowNode(flowCmd) {
|
|
46
136
|
const nodeCmd = flowCmd.command('node').description('节点操作');
|
|
47
137
|
nodeCmd
|
|
@@ -179,7 +269,7 @@ function registerFlowNode(flowCmd) {
|
|
|
179
269
|
.description('在连接流中创建新节点')
|
|
180
270
|
.requiredOption('--flow-id <flowId>', '连接流 ID')
|
|
181
271
|
.requiredOption('--connector-id <connectorId>', '连接器 ID')
|
|
182
|
-
.requiredOption('--action-id <actionId>',
|
|
272
|
+
.requiredOption('--action-id <actionId>', ACTION_ID_HELP)
|
|
183
273
|
.option('--trigger', '作为触发器节点(流中第一个节点建议启用)', false)
|
|
184
274
|
.option('--node-type <type>', '节点类型或动作类型')
|
|
185
275
|
.option('--account-id <id>', '账号实例 ID(需要账号鉴权时指定)')
|
|
@@ -218,27 +308,16 @@ function registerFlowNode(flowCmd) {
|
|
|
218
308
|
let posX = options.positionX !== undefined ? Number(options.positionX) : defaultPos.x;
|
|
219
309
|
let posY = options.positionY !== undefined ? Number(options.positionY) : defaultPos.y;
|
|
220
310
|
const trigger = options.trigger || options.node_type === 'StartEventNode' || false;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
action.id = '0';
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
(0, output_1.printInfo)(`获取连接器 ${connectorId} 的动作列表...`);
|
|
236
|
-
const actions = await (0, node_1.getActions)({
|
|
237
|
-
linkId: connectorId,
|
|
238
|
-
flowId,
|
|
239
|
-
trigger,
|
|
240
|
-
});
|
|
241
|
-
action = actions.find((a) => a.id === actionId);
|
|
311
|
+
(0, output_1.printInfo)(`解析动作 ${actionId}...`);
|
|
312
|
+
const action = await resolveAction(actionId, {
|
|
313
|
+
linkId: connectorId,
|
|
314
|
+
flowId,
|
|
315
|
+
trigger,
|
|
316
|
+
});
|
|
317
|
+
if (action && !action.id) {
|
|
318
|
+
// legacy 哨兵:创建接口要求必传 action_id,NodePackage 动作数字 id <=0,
|
|
319
|
+
// 此处 '0' 仅作占位,动作真实身份是 action_key + action_ver
|
|
320
|
+
action.id = '0';
|
|
242
321
|
}
|
|
243
322
|
if (!action) {
|
|
244
323
|
const msg = `未找到动作 ${actionId}`;
|
|
@@ -358,9 +437,11 @@ function registerFlowNode(flowCmd) {
|
|
|
358
437
|
if (accountId) {
|
|
359
438
|
payload.account_id = accountId;
|
|
360
439
|
}
|
|
361
|
-
if (hasNodeSchema
|
|
362
|
-
|
|
363
|
-
|
|
440
|
+
if (hasNodeSchema) {
|
|
441
|
+
// node_schema.key/version 是权威身份;action_ver 直接字段兜底
|
|
442
|
+
// (node_schema 缺失但 action_ver 存在的场景)
|
|
443
|
+
payload.action_key = action.node_schema?.key ?? action.action_key;
|
|
444
|
+
payload.action_ver = action.node_schema?.version ?? action.action_ver;
|
|
364
445
|
}
|
|
365
446
|
if (options.name) {
|
|
366
447
|
payload.action_name = options.name;
|
|
@@ -391,6 +472,10 @@ function registerFlowNode(flowCmd) {
|
|
|
391
472
|
(0, output_1.printInfo)('正在创建节点...');
|
|
392
473
|
const result = await (0, node_1.createNode)(payload);
|
|
393
474
|
const resultObj = result;
|
|
475
|
+
// Loop/SelfLoop 需要自环边(loop → input),提示补连而不自动写流(CLI 无状态,避免隐式副作用)
|
|
476
|
+
const selfLoopHint = payload.node_type === 'LoopNode' || payload.node_type === 'SelfLoopNode'
|
|
477
|
+
? `该节点需要自环边(loop → input),请执行: ai-flow flow edge connect --flow-id ${flowId} --source ${String(resultObj.id || '')} --target ${String(resultObj.id || '')} --source-handle loop --target-handle input`
|
|
478
|
+
: undefined;
|
|
394
479
|
if (options.json) {
|
|
395
480
|
const jsonResult = {
|
|
396
481
|
ok: true,
|
|
@@ -402,6 +487,8 @@ function registerFlowNode(flowCmd) {
|
|
|
402
487
|
trigger,
|
|
403
488
|
position: { x: posX, y: posY },
|
|
404
489
|
};
|
|
490
|
+
if (selfLoopHint)
|
|
491
|
+
jsonResult.hint = selfLoopHint;
|
|
405
492
|
if (options.name)
|
|
406
493
|
jsonResult.customName = options.name;
|
|
407
494
|
if (options.note !== undefined)
|
|
@@ -437,6 +524,8 @@ function registerFlowNode(flowCmd) {
|
|
|
437
524
|
else if (options.data)
|
|
438
525
|
pairs.push(['配置数据', '--data']);
|
|
439
526
|
(0, output_1.printKeyValue)(pairs);
|
|
527
|
+
if (selfLoopHint)
|
|
528
|
+
(0, output_1.printInfo)(selfLoopHint);
|
|
440
529
|
}
|
|
441
530
|
}
|
|
442
531
|
catch (error) {
|
|
@@ -461,7 +550,7 @@ function registerFlowNode(flowCmd) {
|
|
|
461
550
|
.option('--position-y <number>', '节点 Y 坐标')
|
|
462
551
|
.option('--note <text>', '节点备注')
|
|
463
552
|
.option('--connector-id <connectorId>', '更换连接器 ID')
|
|
464
|
-
.option('--action-id <actionId>',
|
|
553
|
+
.option('--action-id <actionId>', `更换动作 ID(${ACTION_ID_HELP})`)
|
|
465
554
|
.option('--account-id <id>', '更换账号实例 ID')
|
|
466
555
|
.option('--error-handler-file <path>', '错误处理配置 JSON 文件')
|
|
467
556
|
.option('--sample-output <json>', '设置节点样本输出,供下游节点测试时引用')
|
|
@@ -530,28 +619,38 @@ function registerFlowNode(flowCmd) {
|
|
|
530
619
|
return;
|
|
531
620
|
const nd = nodeDetail;
|
|
532
621
|
const hasNodeSchema = !!(nd.action_key && nd.action_ver);
|
|
533
|
-
// node_schema
|
|
534
|
-
let
|
|
622
|
+
// node_schema 模式:优先 getActionNodeSchema 拿权威 schema(action_key@action_ver)
|
|
623
|
+
let nodeSchema;
|
|
535
624
|
if (hasNodeSchema) {
|
|
536
625
|
try {
|
|
537
|
-
|
|
538
|
-
name: nd.action_key,
|
|
539
|
-
flow_id: flowId,
|
|
540
|
-
trigger: nd.node_type === 'StartEventNode',
|
|
541
|
-
page: 1,
|
|
542
|
-
size: 20,
|
|
543
|
-
};
|
|
544
|
-
const actions = await (0, node_1.searchActions)(params);
|
|
545
|
-
nodeSchemaAction = actions.find((a) => a.action_key === nd.action_key);
|
|
626
|
+
nodeSchema = await (0, node_1.getActionNodeSchema)(nd.action_key, Number(nd.action_ver));
|
|
546
627
|
}
|
|
547
|
-
catch
|
|
548
|
-
(0, output_1.printError)(`获取 node_schema action 失败: ${e.message}`);
|
|
628
|
+
catch {
|
|
549
629
|
// 获取失败则回退到旧模式
|
|
550
630
|
}
|
|
551
631
|
}
|
|
632
|
+
// --action-id 指定时按新身份解析(三种形式)
|
|
633
|
+
let replacedAction;
|
|
634
|
+
if (options.actionId) {
|
|
635
|
+
replacedAction = await resolveAction(options.actionId, {
|
|
636
|
+
linkId: options.connectorId || nd.connector_id || undefined,
|
|
637
|
+
flowId,
|
|
638
|
+
trigger: nd.node_type === 'StartEventNode',
|
|
639
|
+
});
|
|
640
|
+
if (!replacedAction) {
|
|
641
|
+
const msg = `未找到动作 ${options.actionId}`;
|
|
642
|
+
if (options.json)
|
|
643
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
644
|
+
else
|
|
645
|
+
(0, output_1.printError)(msg);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (replacedAction.node_schema) {
|
|
649
|
+
nodeSchema = replacedAction.node_schema;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
552
652
|
let updateData;
|
|
553
|
-
if (
|
|
554
|
-
const ns = nodeSchemaAction.node_schema;
|
|
653
|
+
if (nodeSchema) {
|
|
555
654
|
const existingProps = nd.properties ?? {};
|
|
556
655
|
const pos = nd.position ?? { x: 0, y: 0 };
|
|
557
656
|
updateData = {
|
|
@@ -560,8 +659,12 @@ function registerFlowNode(flowCmd) {
|
|
|
560
659
|
position: { ...pos },
|
|
561
660
|
action_id: options.actionId || nd.action_id,
|
|
562
661
|
account_id: options.accountId ?? nd.account_id ?? null,
|
|
563
|
-
action_key:
|
|
564
|
-
|
|
662
|
+
action_key: replacedAction
|
|
663
|
+
? (replacedAction.node_schema?.key ?? replacedAction.action_key)
|
|
664
|
+
: nodeSchema.key,
|
|
665
|
+
action_ver: replacedAction
|
|
666
|
+
? (replacedAction.node_schema?.version ?? replacedAction.action_ver)
|
|
667
|
+
: nodeSchema.version,
|
|
565
668
|
};
|
|
566
669
|
}
|
|
567
670
|
else {
|
|
@@ -631,8 +734,7 @@ function registerFlowNode(flowCmd) {
|
|
|
631
734
|
}
|
|
632
735
|
if (updateData.properties) {
|
|
633
736
|
if (hasNodeSchema) {
|
|
634
|
-
const inputFields = (
|
|
635
|
-
[]);
|
|
737
|
+
const inputFields = (nodeSchema?.input_fields ?? []);
|
|
636
738
|
if (inputFields.length > 0) {
|
|
637
739
|
if ((0, helpers_1.checkPropertyValidation)((0, helpers_1.validateNodeSchemaProperties)(updateData.properties, inputFields), !!options.json))
|
|
638
740
|
return;
|
|
@@ -2,12 +2,31 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerFlowOffline = registerFlowOffline;
|
|
4
4
|
const flow_1 = require("@/api/flow");
|
|
5
|
+
const flowVersion_1 = require("@/api/flowVersion");
|
|
5
6
|
const helpers_1 = require("@/commands/flow/helpers");
|
|
6
7
|
const output_1 = require("@/utils/output");
|
|
8
|
+
function isNotFound(error) {
|
|
9
|
+
return error.code === 'NOT_FOUND';
|
|
10
|
+
}
|
|
11
|
+
/** 版本化下线优先,旧后端(404)回退到 op/v2/:id/OFFLINE */
|
|
12
|
+
async function offlineWithFallback(flowId) {
|
|
13
|
+
try {
|
|
14
|
+
const result = await (0, flowVersion_1.offlineFlowVersion)(flowId);
|
|
15
|
+
const opStatus = result?.op_status;
|
|
16
|
+
return typeof opStatus === 'string' && opStatus ? opStatus : 'OFFLINE';
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (!isNotFound(error))
|
|
20
|
+
throw error;
|
|
21
|
+
(0, output_1.printError)('版本化下线接口不可用(404),已回退到旧版下线接口');
|
|
22
|
+
await (0, flow_1.updateFlowOpStatus)(flowId, 'OFFLINE');
|
|
23
|
+
return 'OFFLINE';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
7
26
|
function registerFlowOffline(flowCmd) {
|
|
8
27
|
flowCmd
|
|
9
28
|
.command('offline <flowId>')
|
|
10
|
-
.description('下线连接流(ONLINE → OFFLINE)')
|
|
29
|
+
.description('下线连接流(ONLINE/ON_WARN → OFFLINE)')
|
|
11
30
|
.option('--json', '以 JSON 格式输出')
|
|
12
31
|
.action(async (flowId, options) => {
|
|
13
32
|
try {
|
|
@@ -16,30 +35,38 @@ function registerFlowOffline(flowCmd) {
|
|
|
16
35
|
const flow = await (0, flow_1.getFlowDetail)(flowId);
|
|
17
36
|
const currentStatus = flow.op_status;
|
|
18
37
|
if (currentStatus === 'OFFLINE' || currentStatus === 'UNPUBLISHED') {
|
|
38
|
+
const msg = `流程已处于 ${(0, helpers_1.formatOpStatus)(currentStatus)} 状态,无需重复下线`;
|
|
39
|
+
if (options.json) {
|
|
40
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
(0, output_1.printWarning)(msg);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (currentStatus === 'ONLINE_ING') {
|
|
48
|
+
const msg = `流程正在升级中(${(0, helpers_1.formatOpStatus)(currentStatus)}),请等待升级完成后再执行下线`;
|
|
19
49
|
if (options.json) {
|
|
20
|
-
(0, output_1.printStructuredResult)({
|
|
21
|
-
ok: false,
|
|
22
|
-
message: `流程已处于 ${(0, helpers_1.formatOpStatus)(currentStatus)} 状态,无需重复下线`,
|
|
23
|
-
});
|
|
50
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
24
51
|
}
|
|
25
52
|
else {
|
|
26
|
-
(0, output_1.printWarning)(
|
|
53
|
+
(0, output_1.printWarning)(msg);
|
|
27
54
|
}
|
|
28
55
|
return;
|
|
29
56
|
}
|
|
30
57
|
if (options.json) {
|
|
31
|
-
await (
|
|
58
|
+
const opStatus = await offlineWithFallback(flowId);
|
|
32
59
|
(0, output_1.printStructuredResult)({
|
|
33
60
|
ok: true,
|
|
34
61
|
id: flowId,
|
|
35
|
-
op_status:
|
|
62
|
+
op_status: opStatus,
|
|
36
63
|
message: '连接流下线成功',
|
|
37
64
|
});
|
|
38
65
|
}
|
|
39
66
|
else {
|
|
40
67
|
(0, output_1.printInfo)(`正在下线连接流 ${flow.name || flowId}...`);
|
|
41
|
-
await (
|
|
42
|
-
(0, output_1.printSuccess)(
|
|
68
|
+
const opStatus = await offlineWithFallback(flowId);
|
|
69
|
+
(0, output_1.printSuccess)(`连接流下线成功,当前状态: ${(0, helpers_1.formatOpStatus)(opStatus)}`);
|
|
43
70
|
}
|
|
44
71
|
}
|
|
45
72
|
catch (error) {
|
|
@@ -6,7 +6,7 @@ const output_1 = require("@/utils/output");
|
|
|
6
6
|
function registerFlowOnline(flowCmd) {
|
|
7
7
|
flowCmd
|
|
8
8
|
.command('online <flowId>')
|
|
9
|
-
.description('
|
|
9
|
+
.description('强制上线连接流(特殊恢复手段,跳过流程检查;常规上/下线请使用 flow publish / flow offline)')
|
|
10
10
|
.option('--force', '跳过确认提示')
|
|
11
11
|
.option('--json', '以 JSON 格式输出')
|
|
12
12
|
.action(async (flowId, options) => {
|
|
@@ -14,8 +14,8 @@ function registerFlowOnline(flowCmd) {
|
|
|
14
14
|
if (options.json)
|
|
15
15
|
(0, output_1.setJsonMode)(true);
|
|
16
16
|
if (!options.force && !options.json) {
|
|
17
|
-
(0, output_1.printWarning)('
|
|
18
|
-
(0, output_1.printInfo)('
|
|
17
|
+
(0, output_1.printWarning)('强制上线为特殊恢复手段,将跳过流程检查,可能影响正在运行的任务');
|
|
18
|
+
(0, output_1.printInfo)('常规上/下线请使用 flow publish / flow offline;如确需强制上线,请使用 --force 参数');
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
if (options.json) {
|
|
@@ -27,7 +27,7 @@ function registerFlowOnline(flowCmd) {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
|
-
(0, output_1.printInfo)(`正在强制上线连接流 ${flowId}
|
|
30
|
+
(0, output_1.printInfo)(`正在强制上线连接流 ${flowId}(特殊恢复手段,常规操作请使用 flow publish)...`);
|
|
31
31
|
await (0, flow_1.onlineFlow)(flowId);
|
|
32
32
|
(0, output_1.printSuccess)('连接流强制上线成功');
|
|
33
33
|
}
|
|
@@ -2,45 +2,149 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerFlowPublish = registerFlowPublish;
|
|
4
4
|
const flow_1 = require("@/api/flow");
|
|
5
|
+
const flowVersion_1 = require("@/api/flowVersion");
|
|
6
|
+
const exit_code_1 = require("@/constants/exit-code");
|
|
5
7
|
const helpers_1 = require("@/commands/flow/helpers");
|
|
8
|
+
const version_1 = require("@/commands/flow/version");
|
|
6
9
|
const output_1 = require("@/utils/output");
|
|
10
|
+
const VERSION_NAME_MAX = 100;
|
|
11
|
+
const REMARK_MAX = 500;
|
|
12
|
+
function isNotFound(error) {
|
|
13
|
+
return error.code === 'NOT_FOUND';
|
|
14
|
+
}
|
|
15
|
+
function isForbidden(error) {
|
|
16
|
+
return error.code === 'FORBIDDEN';
|
|
17
|
+
}
|
|
18
|
+
/** 版本化发布优先,旧后端(404)回退到 op/v2/:id/ONLINE。返回 null 表示已走旧接口 */
|
|
19
|
+
async function publishWithFallback(flowId, request) {
|
|
20
|
+
try {
|
|
21
|
+
return await (0, flowVersion_1.publishFlowVersion)(flowId, request);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (!isNotFound(error))
|
|
25
|
+
throw error;
|
|
26
|
+
(0, output_1.printError)('服务端不支持版本发布,已回退旧接口');
|
|
27
|
+
await (0, flow_1.updateFlowOpStatus)(flowId, 'ONLINE');
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
7
31
|
function registerFlowPublish(flowCmd) {
|
|
8
32
|
flowCmd
|
|
9
33
|
.command('publish <flowId>')
|
|
10
|
-
.description('
|
|
34
|
+
.description('发布草稿为新版本并上线(UNPUBLISHED/OFFLINE → ONLINE)')
|
|
35
|
+
.option('--name <versionName>', '版本命名(≤100 字符)')
|
|
36
|
+
.option('--remark <remark>', '发布备注(≤500 字符)')
|
|
37
|
+
.option('--force', '跳过发布校验强制发布(可能影响运行中的任务)')
|
|
11
38
|
.option('--json', '以 JSON 格式输出')
|
|
12
39
|
.action(async (flowId, options) => {
|
|
13
40
|
try {
|
|
14
41
|
if (options.json)
|
|
15
42
|
(0, output_1.setJsonMode)(true);
|
|
43
|
+
if (options.name && options.name.length > VERSION_NAME_MAX) {
|
|
44
|
+
const msg = `版本命名不能超过 ${VERSION_NAME_MAX} 字符`;
|
|
45
|
+
if (options.json)
|
|
46
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
47
|
+
else
|
|
48
|
+
(0, output_1.printError)(msg);
|
|
49
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (options.remark && options.remark.length > REMARK_MAX) {
|
|
53
|
+
const msg = `发布备注不能超过 ${REMARK_MAX} 字符`;
|
|
54
|
+
if (options.json)
|
|
55
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
56
|
+
else
|
|
57
|
+
(0, output_1.printError)(msg);
|
|
58
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
16
61
|
const flow = await (0, flow_1.getFlowDetail)(flowId);
|
|
17
62
|
const currentStatus = flow.op_status;
|
|
18
|
-
if (currentStatus === '
|
|
63
|
+
if (currentStatus === 'ONLINE_ING') {
|
|
64
|
+
// message 需保持纯文本(--json 模式禁止 ANSI 颜色码),着色交给 printWarning/printError
|
|
65
|
+
const msg = `流程正在升级中(${helpers_1.OP_STATUS_MAP[currentStatus] || currentStatus}),请等待升级完成后再发布`;
|
|
66
|
+
if (options.json)
|
|
67
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
68
|
+
else
|
|
69
|
+
(0, output_1.printWarning)(msg);
|
|
70
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (currentStatus === 'ONLINE' || currentStatus === 'ON_WARN') {
|
|
74
|
+
// 版本化语义:已发布且草稿无未发布变更时无需重复发布;
|
|
75
|
+
// 旧后端(版本状态接口 404)维持旧的拦截语义
|
|
76
|
+
let alreadyLatest = true;
|
|
77
|
+
try {
|
|
78
|
+
const status = await (0, flowVersion_1.getFlowVersionStatus)(flowId);
|
|
79
|
+
alreadyLatest = status.published && !status.has_unpublished_changes;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (!isNotFound(error))
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
if (alreadyLatest) {
|
|
86
|
+
const msg = `流程已处于 ${helpers_1.OP_STATUS_MAP[currentStatus] || currentStatus} 状态且无未发布变更,无需重复发布`;
|
|
87
|
+
if (options.json)
|
|
88
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
89
|
+
else
|
|
90
|
+
(0, output_1.printWarning)(msg);
|
|
91
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const request = {
|
|
96
|
+
version_name: options.name,
|
|
97
|
+
remark: options.remark,
|
|
98
|
+
force: options.force === true,
|
|
99
|
+
};
|
|
100
|
+
let version;
|
|
101
|
+
try {
|
|
102
|
+
version = await publishWithFallback(flowId, request);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (!options.force && !isForbidden(error)) {
|
|
106
|
+
const hint = '发布校验未通过,可加 --force 强制发布(可能影响运行中的任务)';
|
|
107
|
+
if (options.json) {
|
|
108
|
+
(0, output_1.printStructuredResult)({
|
|
109
|
+
ok: false,
|
|
110
|
+
message: `${error.message};${hint}`,
|
|
111
|
+
forceAvailable: true,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
(0, output_1.printError)(`发布失败: ${error.message}`);
|
|
116
|
+
(0, output_1.printWarning)(hint);
|
|
117
|
+
}
|
|
118
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
if (version === null) {
|
|
19
124
|
if (options.json) {
|
|
20
125
|
(0, output_1.printStructuredResult)({
|
|
21
|
-
ok:
|
|
22
|
-
|
|
126
|
+
ok: true,
|
|
127
|
+
id: flowId,
|
|
128
|
+
op_status: 'ONLINE',
|
|
129
|
+
message: '连接流发布成功',
|
|
23
130
|
});
|
|
24
131
|
}
|
|
25
132
|
else {
|
|
26
|
-
(0, output_1.
|
|
133
|
+
(0, output_1.printSuccess)('连接流发布成功(旧接口)');
|
|
27
134
|
}
|
|
28
135
|
return;
|
|
29
136
|
}
|
|
30
137
|
if (options.json) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
ok: true,
|
|
34
|
-
id: flowId,
|
|
35
|
-
op_status: 'ONLINE',
|
|
36
|
-
message: '连接流发布成功',
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
(0, output_1.printInfo)(`正在发布连接流 ${flow.name || flowId}...`);
|
|
41
|
-
await (0, flow_1.updateFlowOpStatus)(flowId, 'ONLINE');
|
|
42
|
-
(0, output_1.printSuccess)('连接流发布成功');
|
|
138
|
+
(0, output_1.printStructuredResult)({ ok: true, data: version });
|
|
139
|
+
return;
|
|
43
140
|
}
|
|
141
|
+
(0, output_1.printInfo)(`正在发布连接流 ${flow.name || flowId}...`);
|
|
142
|
+
(0, output_1.printSuccess)(`连接流发布成功: v${version.version_no} 已上线`);
|
|
143
|
+
(0, output_1.printKeyValue)([
|
|
144
|
+
['版本', `v${version.version_no}`],
|
|
145
|
+
['命名', version.version_name || '-'],
|
|
146
|
+
['发布时间', (0, version_1.formatPublishTime)(version.publish_time)],
|
|
147
|
+
]);
|
|
44
148
|
}
|
|
45
149
|
catch (error) {
|
|
46
150
|
if (options.json) {
|
|
@@ -49,6 +153,7 @@ function registerFlowPublish(flowCmd) {
|
|
|
49
153
|
else {
|
|
50
154
|
(0, output_1.printError)(`发布连接流失败: ${error.message}`);
|
|
51
155
|
}
|
|
156
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
52
157
|
}
|
|
53
158
|
});
|
|
54
159
|
}
|
|
@@ -4,6 +4,7 @@ exports.registerFlowValidate = registerFlowValidate;
|
|
|
4
4
|
const flow_1 = require("@/api/flow");
|
|
5
5
|
const output_1 = require("@/utils/output");
|
|
6
6
|
const format_1 = require("@/utils/format");
|
|
7
|
+
const portValidator_1 = require("@/services/portValidator");
|
|
7
8
|
function registerFlowValidate(flowCmd) {
|
|
8
9
|
flowCmd
|
|
9
10
|
.command('validate <flowId>')
|
|
@@ -41,6 +42,16 @@ function registerFlowValidate(flowCmd) {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
}
|
|
45
|
+
// node_schema 端口级校验(legacy 节点自动跳过)
|
|
46
|
+
(0, output_1.printInfo)('正在校验节点端口连线...');
|
|
47
|
+
const portResult = await (0, portValidator_1.validateFlowPorts)({ nodes, edges });
|
|
48
|
+
for (const v of portResult.violations) {
|
|
49
|
+
const label = `边 ${v.edgeId || `${v.sourceNodeId}→${v.targetNodeId}`}`;
|
|
50
|
+
errors.push(`[端口/${v.code}] ${label}: ${v.message}`);
|
|
51
|
+
}
|
|
52
|
+
for (const u of portResult.unresolvedNodes) {
|
|
53
|
+
warnings.push(`节点 ${u.nodeId} 的 node_schema (${u.actionKey}@${u.actionVer}) 获取失败,已跳过端口校验`);
|
|
54
|
+
}
|
|
44
55
|
if (options.json) {
|
|
45
56
|
(0, output_1.printStructuredResult)({
|
|
46
57
|
ok: errors.length === 0,
|
|
@@ -50,6 +61,7 @@ function registerFlowValidate(flowCmd) {
|
|
|
50
61
|
edgeCount: edges.length,
|
|
51
62
|
errors,
|
|
52
63
|
warnings,
|
|
64
|
+
portViolations: portResult.violations,
|
|
53
65
|
});
|
|
54
66
|
return;
|
|
55
67
|
}
|