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.
@@ -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>', '动作 ID')
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
- let action;
222
- try {
223
- action = await (0, node_1.getActionDetail)(actionId);
224
- }
225
- catch {
226
- // 获取失败则回退到列表查找
227
- }
228
- if (action) {
229
- if (!action.id && action.node_schema) {
230
- // 兼容旧 API:非 node package 节点要求必传 action_id,此处设为 '0' 占位
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 && action.node_schema) {
362
- payload.action_key = action.node_schema.key;
363
- payload.action_ver = action.node_schema.version;
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>', '更换动作 ID')
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 模式:获取 action 以拿到 action_key / action_ver
534
- let nodeSchemaAction;
622
+ // node_schema 模式:优先 getActionNodeSchema 拿权威 schema(action_key@action_ver)
623
+ let nodeSchema;
535
624
  if (hasNodeSchema) {
536
625
  try {
537
- const params = {
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 (e) {
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 (hasNodeSchema && nodeSchemaAction?.node_schema) {
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: ns.key,
564
- action_ver: ns.version,
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 = (nodeSchemaAction?.node_schema?.input_fields ??
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)(`流程已处于 ${(0, helpers_1.formatOpStatus)(currentStatus)} 状态,无需重复下线`);
53
+ (0, output_1.printWarning)(msg);
27
54
  }
28
55
  return;
29
56
  }
30
57
  if (options.json) {
31
- await (0, flow_1.updateFlowOpStatus)(flowId, 'OFFLINE');
58
+ const opStatus = await offlineWithFallback(flowId);
32
59
  (0, output_1.printStructuredResult)({
33
60
  ok: true,
34
61
  id: flowId,
35
- op_status: 'OFFLINE',
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 (0, flow_1.updateFlowOpStatus)(flowId, 'OFFLINE');
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)('如需继续,请使用 --force 参数');
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('发布连接流(UNPUBLISHED → ONLINE)')
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 === 'ONLINE' || currentStatus === 'ONLINE_ING') {
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: false,
22
- message: `流程已处于 ${(0, helpers_1.formatOpStatus)(currentStatus)} 状态,无需重复发布`,
126
+ ok: true,
127
+ id: flowId,
128
+ op_status: 'ONLINE',
129
+ message: '连接流发布成功',
23
130
  });
24
131
  }
25
132
  else {
26
- (0, output_1.printWarning)(`流程已处于 ${(0, helpers_1.formatOpStatus)(currentStatus)} 状态,无需重复发布`);
133
+ (0, output_1.printSuccess)('连接流发布成功(旧接口)');
27
134
  }
28
135
  return;
29
136
  }
30
137
  if (options.json) {
31
- await (0, flow_1.updateFlowOpStatus)(flowId, 'ONLINE');
32
- (0, output_1.printStructuredResult)({
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
  }