lan-low-code-cli 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -0
- package/dist/api/flow.js +119 -0
- package/dist/api/formDefine.js +31 -1
- package/dist/api/operation.js +27 -0
- package/dist/api/template.js +76 -0
- package/dist/api/view.js +23 -0
- package/dist/commands/flow.js +755 -0
- package/dist/commands/form.js +181 -1
- package/dist/commands/operation.js +730 -0
- package/dist/commands/template.js +541 -0
- package/dist/generator/flow/compiler.js +397 -0
- package/dist/generator/flow/index.js +22 -0
- package/dist/generator/flow/layout.js +136 -0
- package/dist/generator/flow/schema-resolver.js +133 -0
- package/dist/generator/flow/schema.js +96 -0
- package/dist/generator/flow/types.js +10 -0
- package/dist/generator/flow/validate.js +129 -0
- package/dist/index.js +20 -1
- package/package.json +49 -49
- package/skills/lan-low-code-cli/SKILL.md +20 -5
- package/skills/lan-low-code-cli/SKILL.zh-CN.md +19 -4
- package/skills/lan-low-code-cli/references/cli-contract.md +167 -8
- package/skills/lan-low-code-cli/references/cli-contract.zh-CN.md +162 -7
- package/skills/lan-low-code-cli/references/mkxform-api.md +2652 -2652
- package/skills/lan-low-code-cli/references/operation-script-context.md +220 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerFlowCommand = registerFlowCommand;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const flow_1 = require("../api/flow");
|
|
7
|
+
const client_1 = require("../client");
|
|
8
|
+
const config_1 = require("../config");
|
|
9
|
+
const flow_2 = require("../generator/flow");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
/** 读取 --spec / --design:支持 @文件 或内联 JSON 字符串 */
|
|
12
|
+
function readJsonInput(value, label) {
|
|
13
|
+
let text = value;
|
|
14
|
+
if (value.startsWith('@')) {
|
|
15
|
+
const file = (0, path_1.resolve)(process.cwd(), value.slice(1));
|
|
16
|
+
if (!(0, fs_1.existsSync)(file))
|
|
17
|
+
throw new Error(`${label} 文件不存在:${file}`);
|
|
18
|
+
text = (0, fs_1.readFileSync)(file, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(text);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
throw new Error(`${label} 不是合法 JSON:${err.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function writeJsonFile(file, data) {
|
|
28
|
+
const target = (0, path_1.resolve)(process.cwd(), file.startsWith('@') ? file.slice(1) : file);
|
|
29
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(target), { recursive: true });
|
|
30
|
+
(0, fs_1.writeFileSync)(target, `${JSON.stringify(data, null, 2)}\n`, 'utf-8');
|
|
31
|
+
logger_1.logger.success(`已写出:${target}`);
|
|
32
|
+
}
|
|
33
|
+
function parseNumber(value, label) {
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const parsed = Number(value);
|
|
37
|
+
if (!Number.isFinite(parsed))
|
|
38
|
+
throw new Error(`${label} 必须是数字:${value}`);
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
function buildOptions(opts) {
|
|
42
|
+
const mode = (opts.layout ?? 'ide');
|
|
43
|
+
if (!['ide', 'layered', 'passthrough'].includes(mode)) {
|
|
44
|
+
throw new Error(`--layout 只支持 ide | layered | passthrough,收到:${mode}`);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
mode,
|
|
48
|
+
...(opts.centerX !== undefined ? { centerX: parseNumber(opts.centerX, '--center-x') } : {}),
|
|
49
|
+
...(opts.rankPadding !== undefined ? { rankPadding: parseNumber(opts.rankPadding, '--rank-padding') } : {}),
|
|
50
|
+
...(opts.laneGap !== undefined ? { laneGap: parseNumber(opts.laneGap, '--lane-gap') } : {}),
|
|
51
|
+
...(opts.minGap !== undefined ? { minGap: parseNumber(opts.minGap, '--min-gap') } : {}),
|
|
52
|
+
...(opts.subCenterX !== undefined ? { subCenterX: parseNumber(opts.subCenterX, '--sub-center-x') } : {}),
|
|
53
|
+
emitWayPoints: Boolean(opts.emitWayPoints),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function printDiagnostics(result, force) {
|
|
57
|
+
const errors = result.diagnostics.filter((d) => d.level === 'error');
|
|
58
|
+
const warnings = result.diagnostics.filter((d) => d.level === 'warn');
|
|
59
|
+
warnings.forEach((d) => logger_1.logger.warn(`[${d.code}] ${d.message}`));
|
|
60
|
+
if (errors.length === 0) {
|
|
61
|
+
logger_1.logger.success(`离线校验通过(${result.diagnostics.length - errors.length} 条提示)`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
errors.forEach((d) => logger_1.logger.error(`[${d.code}] ${d.message}`));
|
|
65
|
+
if (!force) {
|
|
66
|
+
throw new Error(`离线校验未通过(${errors.length} 项错误);修正 spec 后重试,或追加 --force 仅用于查看输出`);
|
|
67
|
+
}
|
|
68
|
+
logger_1.logger.warn('已按 --force 输出,但存在错误,禁止直接发布到环境');
|
|
69
|
+
}
|
|
70
|
+
function printSummary(result) {
|
|
71
|
+
logger_1.logger.title('动作流编译结果');
|
|
72
|
+
logger_1.logger.info(`节点 ${result.design.nodes.length} · 连线 ${result.design.lines.length} · 启动方式 ${result.fdStartWay}`);
|
|
73
|
+
logger_1.logger.info(logger_1.color.gray(`fdDesign 长度 ${result.fdDesign.length} 字符`));
|
|
74
|
+
logger_1.logger.info('');
|
|
75
|
+
logger_1.logger.table(result.bounds.map((item) => ({
|
|
76
|
+
id: item.id,
|
|
77
|
+
nodeType: item.type,
|
|
78
|
+
rank: item.rank,
|
|
79
|
+
lane: item.lane,
|
|
80
|
+
x: item.bounds.x,
|
|
81
|
+
y: item.bounds.y,
|
|
82
|
+
size: `${item.bounds.width}×${item.bounds.height}`,
|
|
83
|
+
})));
|
|
84
|
+
}
|
|
85
|
+
function buildTemplateSpec(opts) {
|
|
86
|
+
return {
|
|
87
|
+
appCode: opts.appCode ?? '<appCode>',
|
|
88
|
+
name: opts.name ?? '新建动作流',
|
|
89
|
+
code: opts.code ?? 'flow_new',
|
|
90
|
+
startWay: (opts.startWay ?? 'StartActiveNode'),
|
|
91
|
+
trigger: { fdRunType: ['operate'], fdFormRelation: '0', dataType: 1 },
|
|
92
|
+
nodes: [
|
|
93
|
+
{
|
|
94
|
+
id: 'N2',
|
|
95
|
+
type: 'QueryDataNode',
|
|
96
|
+
name: '查询数据',
|
|
97
|
+
config: { target: { entityId: '<entityId>' }, resultType: 'single' },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'N3',
|
|
101
|
+
type: 'AddDataNode',
|
|
102
|
+
name: '写入数据',
|
|
103
|
+
config: {
|
|
104
|
+
dataSourceVO: { entityId: '<entityId>' },
|
|
105
|
+
flow: '<flowId>',
|
|
106
|
+
fields: {},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
edges: [{ from: 'N2', to: 'N3' }],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function addCommonOptions(cmd) {
|
|
114
|
+
return cmd
|
|
115
|
+
.option('--base-url <url>', '服务端基址(覆盖配置)')
|
|
116
|
+
.option('--token <token>', '鉴权 Token(覆盖配置)');
|
|
117
|
+
}
|
|
118
|
+
function createClient(opts) {
|
|
119
|
+
return new client_1.LcodeClient((0, config_1.resolveRuntime)({ baseUrl: opts.baseUrl, token: opts.token }));
|
|
120
|
+
}
|
|
121
|
+
/** 后端有的接口返回数组,有的返回 { content: [] } */
|
|
122
|
+
function toRows(value) {
|
|
123
|
+
if (Array.isArray(value))
|
|
124
|
+
return value;
|
|
125
|
+
if (value && typeof value === 'object') {
|
|
126
|
+
const content = value.content;
|
|
127
|
+
if (Array.isArray(content))
|
|
128
|
+
return content;
|
|
129
|
+
}
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
const STATUS_LABEL = { '-1': '草稿', '0': '停用', '1': '启用' };
|
|
133
|
+
const STATUS_VALUE = { draft: -1, stopped: 0, enabled: 1 };
|
|
134
|
+
/** --from 支持:get 响应(含 fdDesign)、编译结果(含 design)、或直接的设计对象 */
|
|
135
|
+
function resolveDesign(input) {
|
|
136
|
+
let value = input;
|
|
137
|
+
if (typeof value === 'string') {
|
|
138
|
+
try {
|
|
139
|
+
value = JSON.parse(value);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
throw new Error('--from 传入的字符串不是合法 JSON');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!value || typeof value !== 'object')
|
|
146
|
+
throw new Error('--from 必须是对象或 JSON 字符串');
|
|
147
|
+
const obj = value;
|
|
148
|
+
if (Array.isArray(obj.nodes)) {
|
|
149
|
+
return {
|
|
150
|
+
nodes: obj.nodes,
|
|
151
|
+
lines: (Array.isArray(obj.lines) ? obj.lines : []),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (typeof obj.fdDesign === 'string')
|
|
155
|
+
return resolveDesign(obj.fdDesign);
|
|
156
|
+
if (obj.design && typeof obj.design === 'object')
|
|
157
|
+
return resolveDesign(obj.design);
|
|
158
|
+
throw new Error('--from 需为 flow get 响应(含 fdDesign)、flow compile 结果(含 design)或设计对象(含 nodes)');
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* golden 模板抽取:剥离 drawAttr(坐标)与实例字段,只保留合法结构骨架。
|
|
162
|
+
* 坐标由布局引擎统一生成,不把某次手工拖拽的偶然位置固化进模板。
|
|
163
|
+
*/
|
|
164
|
+
function stripInstanceFields(node) {
|
|
165
|
+
const out = {};
|
|
166
|
+
for (const [key, value] of Object.entries(node)) {
|
|
167
|
+
if (key === 'drawAttr' || key === 'id' || key === 'code' || key === 'name')
|
|
168
|
+
continue;
|
|
169
|
+
if (key === 'subProcess' && value && typeof value === 'object') {
|
|
170
|
+
const sub = value;
|
|
171
|
+
out.subProcess = {
|
|
172
|
+
nodes: (Array.isArray(sub.nodes) ? sub.nodes : []).map((item) => stripInstanceFields(item)),
|
|
173
|
+
lines: [],
|
|
174
|
+
};
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
out[key] = value;
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
function writeTextFile(file, text) {
|
|
182
|
+
const target = (0, path_1.resolve)(process.cwd(), file.startsWith('@') ? file.slice(1) : file);
|
|
183
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(target), { recursive: true });
|
|
184
|
+
(0, fs_1.writeFileSync)(target, text, 'utf-8');
|
|
185
|
+
logger_1.logger.success(`已写出:${target}`);
|
|
186
|
+
}
|
|
187
|
+
/** 与 UI 一致的提交体:{ ...basicInfo, fdDesign: JSON.stringify(design) } */
|
|
188
|
+
function buildPublishPayload(result) {
|
|
189
|
+
return { ...result.basicInfo, fdDesign: result.fdDesign };
|
|
190
|
+
}
|
|
191
|
+
/** diff 时默认忽略 drawAttr.bounds(坐标由布局引擎生成,不参与语义比较) */
|
|
192
|
+
function normalizeForDiff(value, ignoreBounds) {
|
|
193
|
+
const copy = JSON.parse(JSON.stringify(value));
|
|
194
|
+
if (ignoreBounds && copy.drawAttr && typeof copy.drawAttr === 'object') {
|
|
195
|
+
delete copy.drawAttr.bounds;
|
|
196
|
+
}
|
|
197
|
+
return JSON.stringify(copy);
|
|
198
|
+
}
|
|
199
|
+
function diffDesigns(local, remote, ignoreBounds) {
|
|
200
|
+
const keyOfNode = (n) => String(n.id ?? '');
|
|
201
|
+
const keyOfLine = (l) => `${String(l.sourceRef ?? '')}->${String(l.targetRef ?? '')}`;
|
|
202
|
+
const index = (rows, key) => new Map(rows.map((row) => [key(row), normalizeForDiff(row, ignoreBounds)]));
|
|
203
|
+
const localNodes = index(local.nodes, keyOfNode);
|
|
204
|
+
const remoteNodes = index(remote.nodes, keyOfNode);
|
|
205
|
+
const localLines = index(local.lines, keyOfLine);
|
|
206
|
+
const remoteLines = index(remote.lines, keyOfLine);
|
|
207
|
+
const compare = (a, b) => ({
|
|
208
|
+
added: [...a.keys()].filter((k) => !b.has(k)),
|
|
209
|
+
removed: [...b.keys()].filter((k) => !a.has(k)),
|
|
210
|
+
changed: [...a.keys()].filter((k) => b.has(k) && a.get(k) !== b.get(k)),
|
|
211
|
+
});
|
|
212
|
+
const nodes = compare(localNodes, remoteNodes);
|
|
213
|
+
const lines = compare(localLines, remoteLines);
|
|
214
|
+
return {
|
|
215
|
+
ignoreBounds,
|
|
216
|
+
equal: Object.values(nodes).every((v) => v.length === 0) && Object.values(lines).every((v) => v.length === 0),
|
|
217
|
+
nodes,
|
|
218
|
+
lines,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/** 生成调用 MKXFORM.flow_{fdCode} 的按钮脚本(mkxform-api.md:2558-2574 / 190-229) */
|
|
222
|
+
function buildFlowScript(detail) {
|
|
223
|
+
const code = String(detail.fdCode ?? '');
|
|
224
|
+
const fdId = String(detail.fdId ?? '');
|
|
225
|
+
const name = String(detail.fdName ?? code);
|
|
226
|
+
return `// 由 llc flow script 生成 · 动作流:${name}(${code})
|
|
227
|
+
(function () {
|
|
228
|
+
var MKXFORM = window.MKXFORM || {};
|
|
229
|
+
var fn = MKXFORM['flow_${code}'];
|
|
230
|
+
if (typeof fn !== 'function') {
|
|
231
|
+
console.warn('动作流函数未注册:flow_${code}(需已发布且启用、fdStartWay=StartActiveNode)');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
fn({
|
|
235
|
+
context: typeof context !== 'undefined' ? context : {},
|
|
236
|
+
fdProcessId: '${fdId}',
|
|
237
|
+
incParams: {},
|
|
238
|
+
fdProcessCode: '${code}'
|
|
239
|
+
}, function (error, res, ctx) {
|
|
240
|
+
if (error) { console.error('动作流执行失败', error); return; }
|
|
241
|
+
console.log('动作流返回', res);
|
|
242
|
+
if (ctx && typeof ctx.refresh === 'function') ctx.refresh();
|
|
243
|
+
});
|
|
244
|
+
})();
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
function registerFlowCommand(program) {
|
|
248
|
+
const flow = program
|
|
249
|
+
.command('flow')
|
|
250
|
+
.description('动作流建模:Flow Spec 编译 / 只读查询 / golden 模板抽取');
|
|
251
|
+
addCommonOptions(flow
|
|
252
|
+
.command('spec')
|
|
253
|
+
.description('生成一份 Flow Spec 模板(开始/结束节点由编译器自动补齐)')
|
|
254
|
+
.option('--app-code <code>', '应用编码 fdAppCode')
|
|
255
|
+
.option('--name <name>', '流程名称')
|
|
256
|
+
.option('--code <code>', '流程编码(字母数字下划线,≤30)')
|
|
257
|
+
.option('--start-way <way>', '启动方式:StartActiveNode | StartScheduledNode | StartDataNode | StartEventNode')
|
|
258
|
+
.option('--out <file>', '写出到文件(缺省输出到终端)')
|
|
259
|
+
.option('--json', '以 JSON 输出')).action((opts) => {
|
|
260
|
+
const spec = buildTemplateSpec(opts);
|
|
261
|
+
if (opts.out)
|
|
262
|
+
writeJsonFile(opts.out, spec);
|
|
263
|
+
else
|
|
264
|
+
logger_1.logger.json(spec);
|
|
265
|
+
logger_1.logger.info(logger_1.color.gray('提示:把 <entityId> / <flowId> 等占位符替换为环境真实值后再执行 llc flow compile'));
|
|
266
|
+
});
|
|
267
|
+
addCommonOptions(flow
|
|
268
|
+
.command('compile')
|
|
269
|
+
.description('把 Flow Spec 编译为可直接提交的 fdDesign(含编排布局与离线校验)')
|
|
270
|
+
.requiredOption('--spec <@file|json>', 'Flow Spec:@文件路径 或内联 JSON 字符串')
|
|
271
|
+
.option('--layout <mode>', '布局模式:ide(默认,分支同层左右对称)| layered | passthrough(沿用已有坐标)')
|
|
272
|
+
.option('--center-x <n>', '主链中心 x,默认 679(对应 1920 宽屏)')
|
|
273
|
+
.option('--rank-padding <n>', '层间距增量,默认 52(层距 = 该层最高节点 + 52)')
|
|
274
|
+
.option('--lane-gap <n>', '泳道间距覆盖值,默认 2 × 层距')
|
|
275
|
+
.option('--min-gap <n>', '节点最小边缘间隙,默认 40(IDE 常规编排最小约 43px)')
|
|
276
|
+
.option('--sub-center-x <n>', '循环子画布中心线,默认与主画布一致')
|
|
277
|
+
.option('--emit-waypoints', '输出 drawAttr.wayPoints(IDE 自身保存时不写)')
|
|
278
|
+
.option('--out <file>', '写出完整编译结果到文件')
|
|
279
|
+
.option('--force', '有校验错误时仍输出(仅用于查看,禁止发布)')
|
|
280
|
+
.option('--json', '以 JSON 输出完整编译结果')).action((opts) => {
|
|
281
|
+
const spec = readJsonInput(String(opts.spec), '--spec');
|
|
282
|
+
const result = (0, flow_2.compileFlow)(spec, buildOptions(opts));
|
|
283
|
+
if (opts.json) {
|
|
284
|
+
logger_1.logger.json(result);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
printSummary(result);
|
|
288
|
+
logger_1.logger.info('');
|
|
289
|
+
}
|
|
290
|
+
printDiagnostics(result, Boolean(opts.force));
|
|
291
|
+
if (opts.out)
|
|
292
|
+
writeJsonFile(opts.out, result);
|
|
293
|
+
if (!opts.json)
|
|
294
|
+
logger_1.logger.info(logger_1.color.gray('追加 --json 或 --out <file> 可取得完整 fdDesign'));
|
|
295
|
+
});
|
|
296
|
+
addCommonOptions(flow
|
|
297
|
+
.command('validate')
|
|
298
|
+
.description('只做离线校验(图结构 / 必填 / 编码 / 布局重叠),不输出设计')
|
|
299
|
+
.requiredOption('--spec <@file|json>', 'Flow Spec:@文件路径 或内联 JSON 字符串')
|
|
300
|
+
.option('--layout <mode>', '布局模式:ide | layered | passthrough')
|
|
301
|
+
.option('--center-x <n>', '主链中心 x')
|
|
302
|
+
.option('--rank-padding <n>', '层间距增量')
|
|
303
|
+
.option('--lane-gap <n>', '泳道间距')
|
|
304
|
+
.option('--min-gap <n>', '节点最小边缘间隙')
|
|
305
|
+
.option('--json', '以 JSON 输出诊断结果')).action((opts) => {
|
|
306
|
+
const spec = readJsonInput(String(opts.spec), '--spec');
|
|
307
|
+
const result = (0, flow_2.compileFlow)(spec, buildOptions(opts));
|
|
308
|
+
if (opts.json) {
|
|
309
|
+
logger_1.logger.json(result.diagnostics);
|
|
310
|
+
if ((0, flow_2.hasErrors)(result.diagnostics)) {
|
|
311
|
+
result.diagnostics
|
|
312
|
+
.filter((d) => d.level === 'error')
|
|
313
|
+
.forEach((d) => logger_1.logger.error(`[${d.code}] ${d.message}`));
|
|
314
|
+
throw new Error('离线校验未通过');
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
printDiagnostics(result, false);
|
|
319
|
+
logger_1.logger.info(logger_1.color.gray(`共 ${result.design.nodes.length} 个节点、${result.design.lines.length} 条连线`));
|
|
320
|
+
});
|
|
321
|
+
// ---------------------------------------------------------------- 阶段 2:只读查询
|
|
322
|
+
addCommonOptions(flow
|
|
323
|
+
.command('list')
|
|
324
|
+
.description('列出应用下的动作流(只读)')
|
|
325
|
+
.requiredOption('--app-code <code>', '应用编码 fdAppCode(可由 llc app get --id 取得)')
|
|
326
|
+
.option('--keyword <kw>', '按名称模糊过滤')
|
|
327
|
+
.option('--status <status>', '状态过滤:draft | stopped | enabled 或 -1/0/1')
|
|
328
|
+
.option('--page-no <n>', '页码,默认 1')
|
|
329
|
+
.option('--page-size <n>', '每页条数,默认 100')
|
|
330
|
+
.option('--json', '以 JSON 输出')).action(async (opts) => {
|
|
331
|
+
const client = createClient(opts);
|
|
332
|
+
const conditions = { fdAppCode: opts.appCode };
|
|
333
|
+
if (opts.keyword)
|
|
334
|
+
conditions.fdName = { $contains: String(opts.keyword) };
|
|
335
|
+
if (opts.status !== undefined) {
|
|
336
|
+
const value = STATUS_VALUE[String(opts.status)] ?? Number(opts.status);
|
|
337
|
+
if (!Number.isFinite(value))
|
|
338
|
+
throw new Error(`--status 只支持 draft|stopped|enabled 或 -1/0/1:${opts.status}`);
|
|
339
|
+
conditions.fdStatus = value;
|
|
340
|
+
}
|
|
341
|
+
const result = await flow_1.flowApi.list(client, {
|
|
342
|
+
conditions,
|
|
343
|
+
pageNo: parseNumber(opts.pageNo, '--page-no') ?? 1,
|
|
344
|
+
pageSize: parseNumber(opts.pageSize, '--page-size') ?? 100,
|
|
345
|
+
});
|
|
346
|
+
const rows = toRows(result);
|
|
347
|
+
if (opts.json) {
|
|
348
|
+
logger_1.logger.json(rows);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
logger_1.logger.table(rows.map((row) => ({
|
|
352
|
+
fdId: row.fdId ?? '',
|
|
353
|
+
fdName: row.fdName ?? '',
|
|
354
|
+
fdCode: row.fdCode ?? '',
|
|
355
|
+
启动方式: row.fdStartWay ?? '',
|
|
356
|
+
状态: STATUS_LABEL[String(row.fdStatus)] ?? row.fdStatus ?? '',
|
|
357
|
+
分类: row.fdCategory?.fdName ?? '',
|
|
358
|
+
})));
|
|
359
|
+
logger_1.logger.info(logger_1.color.gray(`共 ${rows.length} 条动作流`));
|
|
360
|
+
});
|
|
361
|
+
addCommonOptions(flow
|
|
362
|
+
.command('get')
|
|
363
|
+
.description('查看动作流详情(只读;--with-design 解析 fdDesign)')
|
|
364
|
+
.requiredOption('--id <fdId>', '动作流 fdId')
|
|
365
|
+
.option('--with-design', '把 fdDesign 字符串解析为对象后以 JSON 输出')
|
|
366
|
+
.option('--raw', '原样输出 fdDesign 字符串')
|
|
367
|
+
.option('--json', '以 JSON 输出详情')).action(async (opts) => {
|
|
368
|
+
const client = createClient(opts);
|
|
369
|
+
const detail = await flow_1.flowApi.get(client, String(opts.id));
|
|
370
|
+
if (opts.raw) {
|
|
371
|
+
process.stdout.write(`${detail.fdDesign ?? ''}\n`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (opts.withDesign) {
|
|
375
|
+
let design = detail.fdDesign;
|
|
376
|
+
if (typeof design === 'string') {
|
|
377
|
+
try {
|
|
378
|
+
design = JSON.parse(design);
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
throw new Error(`fdDesign 不是合法 JSON:${err.message}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
logger_1.logger.json({ ...detail, fdDesign: design });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (opts.json) {
|
|
388
|
+
logger_1.logger.json(detail);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
logger_1.logger.table([
|
|
392
|
+
{
|
|
393
|
+
fdId: detail.fdId ?? '',
|
|
394
|
+
fdName: detail.fdName ?? '',
|
|
395
|
+
fdCode: detail.fdCode ?? '',
|
|
396
|
+
启动方式: detail.fdStartWay ?? '',
|
|
397
|
+
状态: STATUS_LABEL[String(detail.fdStatus)] ?? detail.fdStatus ?? '',
|
|
398
|
+
fdAppCode: detail.fdAppCode ?? '',
|
|
399
|
+
fdDesign长度: (detail.fdDesign ?? '').length,
|
|
400
|
+
},
|
|
401
|
+
]);
|
|
402
|
+
});
|
|
403
|
+
const category = flow.command('category').description('动作流分类(只读)');
|
|
404
|
+
addCommonOptions(category
|
|
405
|
+
.command('list')
|
|
406
|
+
.description('列出应用的动作流分类')
|
|
407
|
+
.requiredOption('--app-code <code>', '应用编码 fdAppCode')
|
|
408
|
+
.option('--keyword <kw>', '按名称模糊过滤')
|
|
409
|
+
.option('--json', '以 JSON 输出')).action(async (opts) => {
|
|
410
|
+
const client = createClient(opts);
|
|
411
|
+
const result = await flow_1.flowApi.categoryList(client, {
|
|
412
|
+
conditions: {
|
|
413
|
+
fdAppCode: opts.appCode,
|
|
414
|
+
...(opts.keyword ? { fdName: { $contains: String(opts.keyword) } } : {}),
|
|
415
|
+
},
|
|
416
|
+
sorts: { fdCreateTime: 'desc' },
|
|
417
|
+
pageNo: 1,
|
|
418
|
+
pageSize: 1000,
|
|
419
|
+
});
|
|
420
|
+
const rows = toRows(result);
|
|
421
|
+
if (opts.json) {
|
|
422
|
+
logger_1.logger.json(rows);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
logger_1.logger.table(rows.map((row) => ({ fdId: row.fdId ?? '', fdName: row.fdName ?? '' })));
|
|
426
|
+
});
|
|
427
|
+
addCommonOptions(flow
|
|
428
|
+
.command('logs')
|
|
429
|
+
.description('查询动作流运行日志(只读)')
|
|
430
|
+
.requiredOption('--id <fdId>', '动作流 fdId')
|
|
431
|
+
.option('--limit <n>', '条数,默认 20')
|
|
432
|
+
.option('--endpoint <path>', '覆盖日志端点相对路径(环境差异时用)')
|
|
433
|
+
.option('--json', '以 JSON 输出')).action(async (opts) => {
|
|
434
|
+
const client = createClient(opts);
|
|
435
|
+
const result = await flow_1.flowApi.logFindAll(client, {
|
|
436
|
+
conditions: { 'fdEntity.fdId': String(opts.id) },
|
|
437
|
+
fdGroup: 'modeling.process.log',
|
|
438
|
+
sorts: { fdBeginTime: 'desc' },
|
|
439
|
+
offset: 0,
|
|
440
|
+
pageSize: parseNumber(opts.limit, '--limit') ?? 20,
|
|
441
|
+
}, opts.endpoint);
|
|
442
|
+
const rows = toRows(result);
|
|
443
|
+
if (opts.json) {
|
|
444
|
+
logger_1.logger.json(rows);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
logger_1.logger.table(rows.map((row) => ({
|
|
448
|
+
fdId: row.fdId ?? '',
|
|
449
|
+
开始时间: row.fdBeginTime ?? '',
|
|
450
|
+
状态: row.fdStatus ?? '',
|
|
451
|
+
消息: String(row.fdMessage ?? '').slice(0, 60),
|
|
452
|
+
})));
|
|
453
|
+
logger_1.logger.info(logger_1.color.gray(`共 ${rows.length} 条日志`));
|
|
454
|
+
});
|
|
455
|
+
addCommonOptions(flow
|
|
456
|
+
.command('schema')
|
|
457
|
+
.description('查询动作流建模所需的后端 Schema / 元数据(只读,始终以 JSON 输出)')
|
|
458
|
+
.requiredOption('--kind <kind>', 'businessNodes | events | source | jsonSchema | condition | out | active | event | interface | apiMethod | flowBots | meta | dataModel | recycle')
|
|
459
|
+
.option('--entity-id <id>', '数据模型 / 表单实体 id')
|
|
460
|
+
.option('--code <code>', '动作流、事件或接口编码')
|
|
461
|
+
.option('--app-code <code>', '应用编码(--kind active 必填)')
|
|
462
|
+
.option('--api-type <type>', '内部接口类型(--kind apiMethod)')
|
|
463
|
+
.option('--api-method-path <path>', '内部接口方法路径(--kind apiMethod)')).action(async (opts) => {
|
|
464
|
+
const client = createClient(opts);
|
|
465
|
+
const kind = String(opts.kind);
|
|
466
|
+
const requireOpt = (value, name) => {
|
|
467
|
+
if (!value)
|
|
468
|
+
throw new Error(`--kind ${kind} 需要 ${name}`);
|
|
469
|
+
return value;
|
|
470
|
+
};
|
|
471
|
+
let result;
|
|
472
|
+
switch (kind) {
|
|
473
|
+
case 'businessNodes':
|
|
474
|
+
result = await flow_1.flowApi.businessNodes(client);
|
|
475
|
+
break;
|
|
476
|
+
case 'events':
|
|
477
|
+
result = await flow_1.flowApi.getProcessDataEventInfo(client);
|
|
478
|
+
break;
|
|
479
|
+
case 'source':
|
|
480
|
+
result = await flow_1.flowApi.getSourceFields(client, requireOpt(opts.entityId, '--entity-id'));
|
|
481
|
+
break;
|
|
482
|
+
case 'jsonSchema':
|
|
483
|
+
result = await flow_1.flowApi.getJsonSchema(client, requireOpt(opts.entityId, '--entity-id'));
|
|
484
|
+
break;
|
|
485
|
+
case 'condition':
|
|
486
|
+
result = await flow_1.flowApi.getConditionFields(client, { entityId: requireOpt(opts.entityId, '--entity-id') });
|
|
487
|
+
break;
|
|
488
|
+
case 'out':
|
|
489
|
+
result = await flow_1.flowApi.getDataOutJsonSchema(client, requireOpt(opts.entityId, '--entity-id'));
|
|
490
|
+
break;
|
|
491
|
+
case 'active':
|
|
492
|
+
result = await flow_1.flowApi.getActionFlowSchema(client, requireOpt(opts.code, '--code'), requireOpt(opts.appCode, '--app-code'));
|
|
493
|
+
break;
|
|
494
|
+
case 'event':
|
|
495
|
+
result = await flow_1.flowApi.getEventSchema(client, requireOpt(opts.code, '--code'));
|
|
496
|
+
break;
|
|
497
|
+
case 'interface':
|
|
498
|
+
result = await flow_1.flowApi.getInterfaceSchema(client, requireOpt(opts.code, '--code'));
|
|
499
|
+
break;
|
|
500
|
+
case 'apiMethod':
|
|
501
|
+
result = await flow_1.flowApi.getApiMethodSchema(client, { apiType: requireOpt(opts.apiType, '--api-type'), apiMethodPath: requireOpt(opts.apiMethodPath, '--api-method-path') });
|
|
502
|
+
break;
|
|
503
|
+
case 'flowBots':
|
|
504
|
+
result = await flow_1.flowApi.getFlowBotsSchema(client, requireOpt(opts.code, '--code'));
|
|
505
|
+
break;
|
|
506
|
+
case 'meta':
|
|
507
|
+
result = await flow_1.flowApi.getMetaJsonSchema(client, { entityId: requireOpt(opts.entityId, '--entity-id') });
|
|
508
|
+
break;
|
|
509
|
+
case 'dataModel':
|
|
510
|
+
result = await flow_1.flowApi.loadDataModel(client, { entityId: requireOpt(opts.entityId, '--entity-id') });
|
|
511
|
+
break;
|
|
512
|
+
case 'recycle':
|
|
513
|
+
result = await flow_1.flowApi.getRecycleState(client, requireOpt(opts.entityId, '--entity-id'));
|
|
514
|
+
break;
|
|
515
|
+
default: throw new Error(`未知 --kind:${kind}(可执行 llc help flow schema 查看支持项)`);
|
|
516
|
+
}
|
|
517
|
+
logger_1.logger.json(result);
|
|
518
|
+
});
|
|
519
|
+
// golden 模板抽取:纯离线,输入来自 flow get / flow compile 的产物
|
|
520
|
+
flow
|
|
521
|
+
.command('templates')
|
|
522
|
+
.description('从已有动作流抽取 golden 节点模板(剥离坐标,纯离线)')
|
|
523
|
+
.requiredOption('--from <@file|json>', 'flow get 响应 / flow compile 结果 / 设计对象')
|
|
524
|
+
.option('--out <file>', '写出模板文件')
|
|
525
|
+
.option('--json', '以 JSON 输出')
|
|
526
|
+
.action((opts) => {
|
|
527
|
+
const input = readJsonInput(String(opts.from), '--from');
|
|
528
|
+
const design = resolveDesign(input);
|
|
529
|
+
const templates = {};
|
|
530
|
+
for (const node of design.nodes) {
|
|
531
|
+
const nodeType = String(node.nodeType ?? 'unknown');
|
|
532
|
+
const existing = templates[nodeType];
|
|
533
|
+
if (existing)
|
|
534
|
+
existing.count += 1;
|
|
535
|
+
else
|
|
536
|
+
templates[nodeType] = { count: 1, skeleton: stripInstanceFields(node) };
|
|
537
|
+
}
|
|
538
|
+
const output = {
|
|
539
|
+
version: 1,
|
|
540
|
+
nodeTypes: Object.keys(templates),
|
|
541
|
+
lineSkeleton: { type: 'line', drawAttr: {} },
|
|
542
|
+
templates,
|
|
543
|
+
};
|
|
544
|
+
if (opts.out)
|
|
545
|
+
writeJsonFile(opts.out, output);
|
|
546
|
+
else
|
|
547
|
+
logger_1.logger.json(output);
|
|
548
|
+
logger_1.logger.info(logger_1.color.gray('模板已剥离 drawAttr.bounds;坐标由 llc flow compile 的布局引擎统一生成'));
|
|
549
|
+
});
|
|
550
|
+
// ---------------------------------------------------------------- 阶段 3:写入与闭环
|
|
551
|
+
addCommonOptions(flow
|
|
552
|
+
.command('apply')
|
|
553
|
+
.description('把 Flow Spec 写入环境(默认预览;--yes 才提交;--draft 只存草稿)')
|
|
554
|
+
.requiredOption('--spec <@file|json>', 'Flow Spec:@文件路径 或内联 JSON')
|
|
555
|
+
.option('--app-code <code>', '应用编码 fdAppCode(覆盖 spec.appCode,写入时建议显式指定)')
|
|
556
|
+
.option('--draft', '只保存草稿(addDraft/updateDraft),不发布')
|
|
557
|
+
.option('--enable', '成功后调用 startup 启用')
|
|
558
|
+
.option('--resolve-schema', '联网补齐 extraAttr.context_*(SchemaResolver)')
|
|
559
|
+
.option('--strict-schema', 'Schema 拉取失败时升级为错误')
|
|
560
|
+
.option('--layout <mode>', '布局模式:ide | layered | passthrough')
|
|
561
|
+
.option('--center-x <n>', '主链中心 x')
|
|
562
|
+
.option('--lane-gap <n>', '泳道间距')
|
|
563
|
+
.option('--min-gap <n>', '最小边缘间隙')
|
|
564
|
+
.option('--emit-waypoints', '输出 wayPoints')
|
|
565
|
+
.option('--force', '有校验错误时仍继续(仅调试)')
|
|
566
|
+
.option('--yes', '确认写入(不加则只打印 payload)')
|
|
567
|
+
.option('--json', '以 JSON 输出结果')).action(async (opts) => {
|
|
568
|
+
const spec = readJsonInput(String(opts.spec), '--spec');
|
|
569
|
+
if (opts.appCode)
|
|
570
|
+
spec.appCode = opts.appCode;
|
|
571
|
+
if (!spec.appCode) {
|
|
572
|
+
throw new Error('写入动作流必须指定应用编码:在 spec 里写 appCode,或追加 --app-code <code>');
|
|
573
|
+
}
|
|
574
|
+
let result = (0, flow_2.compileFlow)(spec, buildOptions(opts));
|
|
575
|
+
printDiagnostics(result, Boolean(opts.force));
|
|
576
|
+
const client = createClient(opts);
|
|
577
|
+
if (opts.resolveSchema) {
|
|
578
|
+
logger_1.logger.step(`补齐运行时上下文 extraAttr.context_*(应用 ${spec.appCode})`);
|
|
579
|
+
result = await (0, flow_2.resolveFlowSchema)(client, spec, result, { strict: Boolean(opts.strictSchema) });
|
|
580
|
+
printDiagnostics(result, Boolean(opts.force));
|
|
581
|
+
}
|
|
582
|
+
const isUpdate = Boolean(spec.fdId);
|
|
583
|
+
if (!isUpdate) {
|
|
584
|
+
let unique;
|
|
585
|
+
try {
|
|
586
|
+
unique = await flow_1.flowApi.checkCodeUniqueness(client, spec.code, spec.appCode);
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
logger_1.logger.warn(`编码唯一性检查失败,继续提交:${err.message.split('\n')[0]}`);
|
|
590
|
+
}
|
|
591
|
+
if (unique === false) {
|
|
592
|
+
throw new Error(`流程编码 ${spec.code} 在应用 ${spec.appCode} 中已存在;更换 code,或更新已有流程时在 spec 中写 fdId`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const payload = buildPublishPayload(result);
|
|
596
|
+
if (!opts.yes) {
|
|
597
|
+
logger_1.logger.warn(`预览模式:未写入。目标应用 ${spec.appCode};确认 payload 后追加 --yes`);
|
|
598
|
+
logger_1.logger.json(payload);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
let created;
|
|
602
|
+
if (opts.draft) {
|
|
603
|
+
created = isUpdate ? await flow_1.flowApi.updateDraft(client, payload) : await flow_1.flowApi.addDraft(client, payload);
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
created = isUpdate ? await flow_1.flowApi.updatePublish(client, payload) : await flow_1.flowApi.addPublish(client, payload);
|
|
607
|
+
}
|
|
608
|
+
const returned = typeof created === 'string'
|
|
609
|
+
? created
|
|
610
|
+
: created?.fdId ?? '';
|
|
611
|
+
const fdId = isUpdate ? String(spec.fdId) : returned;
|
|
612
|
+
if (!fdId) {
|
|
613
|
+
throw new Error(`提交成功但未取得 fdId;请用 llc flow list --app-code ${spec.appCode} 核对后再操作`);
|
|
614
|
+
}
|
|
615
|
+
logger_1.logger.success(`动作流已${opts.draft ? '保存草稿' : '发布'}:${fdId}`);
|
|
616
|
+
let readback;
|
|
617
|
+
try {
|
|
618
|
+
readback = await flow_1.flowApi.get(client, fdId);
|
|
619
|
+
}
|
|
620
|
+
catch (err) {
|
|
621
|
+
logger_1.logger.warn(`回读失败(不影响已提交结果):${err.message.split('\n')[0]}`);
|
|
622
|
+
}
|
|
623
|
+
if (opts.enable) {
|
|
624
|
+
await flow_1.flowApi.startup(client, fdId);
|
|
625
|
+
logger_1.logger.success(`已启用:${fdId}`);
|
|
626
|
+
}
|
|
627
|
+
if (opts.json) {
|
|
628
|
+
logger_1.logger.json({
|
|
629
|
+
appCode: spec.appCode,
|
|
630
|
+
fdId,
|
|
631
|
+
fdCode: spec.code,
|
|
632
|
+
published: !opts.draft,
|
|
633
|
+
enabled: Boolean(opts.enable),
|
|
634
|
+
diagnostics: result.diagnostics,
|
|
635
|
+
readback: readback
|
|
636
|
+
? {
|
|
637
|
+
fdStatus: readback.fdStatus,
|
|
638
|
+
fdStartWay: readback.fdStartWay,
|
|
639
|
+
fdProcessActive: readback.fdProcessActive,
|
|
640
|
+
designLength: (readback.fdDesign ?? '').length,
|
|
641
|
+
}
|
|
642
|
+
: null,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
addCommonOptions(flow
|
|
647
|
+
.command('diff')
|
|
648
|
+
.description('对比本地 Flow Spec 与远端动作流设计(默认忽略坐标差异)')
|
|
649
|
+
.requiredOption('--id <fdId>', '远端动作流 fdId')
|
|
650
|
+
.requiredOption('--spec <@file|json>', '本地 Flow Spec')
|
|
651
|
+
.option('--no-ignore-bounds', '比较时也包含 drawAttr.bounds')
|
|
652
|
+
.option('--resolve-schema', '对比前联网补齐 extraAttr.context_*(与发布时的选项保持一致)')
|
|
653
|
+
.option('--strict-schema', 'Schema 拉取失败时升级为错误')
|
|
654
|
+
.option('--layout <mode>', '布局模式:ide | layered | passthrough')
|
|
655
|
+
.option('--center-x <n>', '主链中心 x')
|
|
656
|
+
.option('--json', '以 JSON 输出差异')).action(async (opts) => {
|
|
657
|
+
const spec = readJsonInput(String(opts.spec), '--spec');
|
|
658
|
+
let result = (0, flow_2.compileFlow)(spec, buildOptions(opts));
|
|
659
|
+
printDiagnostics(result, false);
|
|
660
|
+
const client = createClient(opts);
|
|
661
|
+
if (opts.resolveSchema) {
|
|
662
|
+
result = await (0, flow_2.resolveFlowSchema)(client, spec, result, { strict: Boolean(opts.strictSchema) });
|
|
663
|
+
}
|
|
664
|
+
const remote = await flow_1.flowApi.get(client, String(opts.id));
|
|
665
|
+
let remoteDesign;
|
|
666
|
+
try {
|
|
667
|
+
remoteDesign = JSON.parse(remote.fdDesign ?? '{}');
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
throw new Error(`远端 fdDesign 不是合法 JSON:${err.message}`);
|
|
671
|
+
}
|
|
672
|
+
const diff = diffDesigns(result.design, remoteDesign, opts.ignoreBounds !== false);
|
|
673
|
+
logger_1.logger.json(diff);
|
|
674
|
+
if (diff.equal)
|
|
675
|
+
logger_1.logger.success('结构与远端一致');
|
|
676
|
+
else
|
|
677
|
+
logger_1.logger.warn('设计与远端存在差异(见 nodes / lines 的 added / removed / changed)');
|
|
678
|
+
});
|
|
679
|
+
addCommonOptions(flow
|
|
680
|
+
.command('script')
|
|
681
|
+
.description('生成调用 MKXFORM.flow_{fdCode} 的按钮脚本')
|
|
682
|
+
.requiredOption('--id <fdId>', '动作流 fdId')
|
|
683
|
+
.option('--out <file>', '写出脚本文件')
|
|
684
|
+
.option('--json', '以 JSON 输出')).action(async (opts) => {
|
|
685
|
+
const client = createClient(opts);
|
|
686
|
+
const detail = await flow_1.flowApi.get(client, String(opts.id));
|
|
687
|
+
if (!detail.fdCode)
|
|
688
|
+
throw new Error('该动作流缺少 fdCode,无法生成 flow_{fdCode} 调用');
|
|
689
|
+
const script = buildFlowScript(detail);
|
|
690
|
+
if (opts.out)
|
|
691
|
+
writeTextFile(opts.out, script);
|
|
692
|
+
else if (opts.json)
|
|
693
|
+
logger_1.logger.json({ fdId: detail.fdId, fdCode: detail.fdCode, script });
|
|
694
|
+
else
|
|
695
|
+
process.stdout.write(`${script}\n`);
|
|
696
|
+
logger_1.logger.info(logger_1.color.gray('用法:llc operation create --form-id <id> --type script --script @<file>,再用 llc operation attach 挂载'));
|
|
697
|
+
});
|
|
698
|
+
addCommonOptions(flow
|
|
699
|
+
.command('enable')
|
|
700
|
+
.description('启用动作流(startup)')
|
|
701
|
+
.requiredOption('--id <fdId>', '动作流 fdId')
|
|
702
|
+
.option('--json', '以 JSON 输出')).action(async (opts) => {
|
|
703
|
+
const client = createClient(opts);
|
|
704
|
+
await flow_1.flowApi.startup(client, String(opts.id));
|
|
705
|
+
logger_1.logger.success(`已启用:${opts.id}`);
|
|
706
|
+
if (opts.json)
|
|
707
|
+
logger_1.logger.json({ fdId: opts.id, enabled: true });
|
|
708
|
+
});
|
|
709
|
+
addCommonOptions(flow
|
|
710
|
+
.command('disable')
|
|
711
|
+
.description('停用动作流(stop;会影响调用该动作流的业务模块)')
|
|
712
|
+
.requiredOption('--id <fdId>', '动作流 fdId')
|
|
713
|
+
.option('--yes', '确认停用')
|
|
714
|
+
.option('--json', '以 JSON 输出')).action(async (opts) => {
|
|
715
|
+
if (!opts.yes) {
|
|
716
|
+
logger_1.logger.warn('已取消:停用可能影响业务模块,请追加 --yes 确认');
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const client = createClient(opts);
|
|
720
|
+
await flow_1.flowApi.stop(client, String(opts.id));
|
|
721
|
+
logger_1.logger.success(`已停用:${opts.id}`);
|
|
722
|
+
if (opts.json)
|
|
723
|
+
logger_1.logger.json({ fdId: opts.id, enabled: false });
|
|
724
|
+
});
|
|
725
|
+
addCommonOptions(flow
|
|
726
|
+
.command('delete')
|
|
727
|
+
.description('删除动作流(先查引用,被引用时拒绝)')
|
|
728
|
+
.requiredOption('--id <fdId>', '动作流 fdId')
|
|
729
|
+
.option('--yes', '确认删除')
|
|
730
|
+
.option('--json', '以 JSON 输出')).action(async (opts) => {
|
|
731
|
+
if (!opts.yes) {
|
|
732
|
+
logger_1.logger.warn('已取消:删除动作流不可恢复,请追加 --yes 确认');
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const client = createClient(opts);
|
|
736
|
+
logger_1.logger.step('检查引用关系');
|
|
737
|
+
let refs = [];
|
|
738
|
+
try {
|
|
739
|
+
refs = toRows(await flow_1.flowApi.listByRefer(client, String(opts.id)));
|
|
740
|
+
}
|
|
741
|
+
catch (err) {
|
|
742
|
+
logger_1.logger.warn(`引用检查失败,继续删除:${err.message.split('\n')[0]}`);
|
|
743
|
+
}
|
|
744
|
+
if (refs.length > 0) {
|
|
745
|
+
refs.forEach((item, index) => {
|
|
746
|
+
logger_1.logger.info(` ${index + 1}. ${String(item.fdEntityLabel ?? '')} -- ${String(item.fdEntitySubject ?? '')}`);
|
|
747
|
+
});
|
|
748
|
+
throw new Error('该动作流已被引用,请先在对应业务中移除后再删除');
|
|
749
|
+
}
|
|
750
|
+
await flow_1.flowApi.remove(client, String(opts.id));
|
|
751
|
+
logger_1.logger.success(`已删除:${opts.id}`);
|
|
752
|
+
if (opts.json)
|
|
753
|
+
logger_1.logger.json({ fdId: opts.id, deleted: true });
|
|
754
|
+
});
|
|
755
|
+
}
|