@waron97/prbot 3.2.0 → 3.3.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 +26 -18
- package/agrippa-pb.md +172 -74
- package/package.json +1 -1
- package/src/agrippa/commands/clone.js +7 -1
- package/src/agrippa/commands/cloneLrp.js +114 -0
- package/src/agrippa/commands/diff.js +23 -10
- package/src/agrippa/commands/initPhase.js +44 -34
- package/src/agrippa/commands/pb.js +61 -16
- package/src/agrippa/commands/pull.js +93 -17
- package/src/agrippa/commands/pullLrp.js +55 -0
- package/src/agrippa/commands/push.js +88 -19
- package/src/agrippa/commands/pushLrp.js +56 -0
- package/src/agrippa/index.js +32 -17
- package/src/agrippa/lib/lrpApi.js +153 -0
- package/src/agrippa/lib/pbEdit.js +103 -6
- package/src/agrippa/lib/pbLayout.js +23 -2
- package/src/agrippa/lib/pbModel.js +324 -90
- package/src/agrippa/lib/pbPreview.js +4 -2
- package/src/agrippa/lib/pbProject.js +34 -5
- package/src/agrippa/lib/pbScriptTemplate.js +29 -0
- package/src/commands/autopr.js +1 -1
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { stringify as yamlStringify } from 'yaml';
|
|
16
16
|
import { pad, toSlug } from './pbProject.js';
|
|
17
|
+
import { SCRIPT_TEMPLATE } from './pbScriptTemplate.js';
|
|
17
18
|
|
|
18
19
|
// Fixed element sizes (measured across all fixtures). Containers are sized by
|
|
19
20
|
// the layout engine; we seed a small default so a stubbed clone stays valid.
|
|
@@ -27,6 +28,12 @@ const SIZE = {
|
|
|
27
28
|
userTask: [84, 84],
|
|
28
29
|
subProcess: [350, 200],
|
|
29
30
|
transaction: [350, 200],
|
|
31
|
+
// LRP-only (measured across the real LRP corpus):
|
|
32
|
+
intermediateCatchEvent: [36, 36],
|
|
33
|
+
intermediateThrowEvent: [36, 36],
|
|
34
|
+
callActivity: [84, 84],
|
|
35
|
+
parallelGateway: [50, 50],
|
|
36
|
+
eventBasedGateway: [50, 50],
|
|
30
37
|
};
|
|
31
38
|
|
|
32
39
|
// BPMN id prefixes by node type (mirrors the upstream naming convention).
|
|
@@ -40,10 +47,29 @@ const PREFIX = {
|
|
|
40
47
|
userTask: 'UserTask',
|
|
41
48
|
subProcess: 'SubProcess',
|
|
42
49
|
transaction: 'Transaction',
|
|
50
|
+
intermediateCatchEvent: 'IntermediateCatchEvent',
|
|
51
|
+
intermediateThrowEvent: 'IntermediateThrowEvent',
|
|
52
|
+
callActivity: 'CallActivity',
|
|
53
|
+
parallelGateway: 'ParallelGateway',
|
|
54
|
+
eventBasedGateway: 'EventBasedGateway',
|
|
43
55
|
};
|
|
44
56
|
|
|
45
57
|
const CONTAINER = new Set(['subProcess', 'transaction']);
|
|
46
58
|
|
|
59
|
+
// Default attrs injected at node creation so the editor accepts them without manual edits.
|
|
60
|
+
const DEFAULT_ATTRS = {
|
|
61
|
+
scriptTask: {
|
|
62
|
+
scriptFormat: 'javascript',
|
|
63
|
+
'activiti:async': 'false',
|
|
64
|
+
'activiti:exclusive': 'false',
|
|
65
|
+
'activiti:autoStoreVariables': 'false',
|
|
66
|
+
},
|
|
67
|
+
serviceTask: {
|
|
68
|
+
'activiti:async': 'false',
|
|
69
|
+
'activiti:exclusive': 'false',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
47
73
|
// Depth-first visit of every node in the nested graph, with its parent node.
|
|
48
74
|
function eachNode(nodes, parent, fn) {
|
|
49
75
|
for (const n of nodes || []) {
|
|
@@ -112,7 +138,7 @@ function nextScriptSeq(existingScriptFiles) {
|
|
|
112
138
|
|
|
113
139
|
// ---------- add ----------
|
|
114
140
|
|
|
115
|
-
// Add a node. For scriptTask, scaffolds
|
|
141
|
+
// Add a node. For scriptTask, scaffolds a templated script file and refs it. For
|
|
116
142
|
// userTask, scaffolds a minimal page file + a manifest page entry (guid=null →
|
|
117
143
|
// push will create it upstream). Container nodes start empty + expanded.
|
|
118
144
|
function addNode(structure, manifest, opts, ctx = {}) {
|
|
@@ -130,7 +156,7 @@ function addNode(structure, manifest, opts, ctx = {}) {
|
|
|
130
156
|
if (type === 'scriptTask') {
|
|
131
157
|
const seq = nextScriptSeq(ctx.existingScripts);
|
|
132
158
|
const file = `scripts/${pad(seq)}_${toSlug(name || id)}.js`;
|
|
133
|
-
writes[file] =
|
|
159
|
+
writes[file] = SCRIPT_TEMPLATE;
|
|
134
160
|
node.script = file;
|
|
135
161
|
} else if (type === 'userTask') {
|
|
136
162
|
const formKey = toSlug(name || id);
|
|
@@ -161,6 +187,8 @@ function addNode(structure, manifest, opts, ctx = {}) {
|
|
|
161
187
|
node.nodes = [];
|
|
162
188
|
}
|
|
163
189
|
|
|
190
|
+
if (DEFAULT_ATTRS[type]) node.attrs = { ...DEFAULT_ATTRS[type] };
|
|
191
|
+
|
|
164
192
|
if (parentId) {
|
|
165
193
|
const f = findNode(structure, parentId);
|
|
166
194
|
if (!f) throw new Error(`parent not found: ${parentId}`);
|
|
@@ -334,10 +362,9 @@ function connect(structure, { from, to, name, condition, conditionType, makeDefa
|
|
|
334
362
|
warnings.push(`replaced previous default flow ${prev} on ${from}.`);
|
|
335
363
|
}
|
|
336
364
|
}
|
|
337
|
-
// Re-check
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
365
|
+
// Re-check all structural invariants after the edit, scoped to affected nodes.
|
|
366
|
+
const allIssues = lintAll(structure);
|
|
367
|
+
warnings.push(...allIssues.filter((w) => w.startsWith(from) || w.startsWith(to)));
|
|
341
368
|
|
|
342
369
|
return { writes: {}, deletes: [], result: { id, from, to, warnings } };
|
|
343
370
|
}
|
|
@@ -407,6 +434,73 @@ function lintGateways(structure) {
|
|
|
407
434
|
return issues;
|
|
408
435
|
}
|
|
409
436
|
|
|
437
|
+
// Flag non-default outgoing edges that are missing a name when a node has 2+
|
|
438
|
+
// outgoing flows. In Activiti diagrams these labels are mandatory so operators
|
|
439
|
+
// can distinguish branches at runtime.
|
|
440
|
+
function lintEdgeNames(structure) {
|
|
441
|
+
const issues = [];
|
|
442
|
+
eachNode(structure.nodes, null, (n) => {
|
|
443
|
+
const outs = n.edges || [];
|
|
444
|
+
if (outs.length < 2) return;
|
|
445
|
+
const def = n.attrs?.default;
|
|
446
|
+
for (const e of outs) {
|
|
447
|
+
if (e.id === def) continue;
|
|
448
|
+
if (!e.name) {
|
|
449
|
+
issues.push(`${n.id} → ${e.target} (${e.id}): non-default flow has no name.`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
return issues;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Gateway types that legitimately fan multiple flows into one node (a "join").
|
|
457
|
+
// parallelGateway/eventBasedGateway joins are common in the real LRP corpus
|
|
458
|
+
// (61 occurrences) — only exclusiveGateway's merge semantics need a lint rule.
|
|
459
|
+
const JOIN_CAPABLE_GATEWAYS = new Set(['exclusiveGateway', 'parallelGateway', 'eventBasedGateway']);
|
|
460
|
+
|
|
461
|
+
// Flag incoming-edge violations:
|
|
462
|
+
// - Only a gateway may have >1 incoming flows (plain tasks/events are 1-in).
|
|
463
|
+
// - An exclusiveGateway may not have both >1 incoming AND >1 outgoing (pick one
|
|
464
|
+
// direction for merging or splitting, not both at once) — parallel/event-based
|
|
465
|
+
// gateways may legitimately fork-then-join across the diagram, so this
|
|
466
|
+
// specific rule stays scoped to exclusiveGateway.
|
|
467
|
+
function lintIncomingEdges(structure) {
|
|
468
|
+
const inCount = {};
|
|
469
|
+
const outCount = {};
|
|
470
|
+
eachNode(structure.nodes, null, (n) => {
|
|
471
|
+
for (const e of n.edges || []) {
|
|
472
|
+
inCount[e.target] = (inCount[e.target] || 0) + 1;
|
|
473
|
+
outCount[n.id] = (outCount[n.id] || 0) + 1;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const issues = [];
|
|
478
|
+
eachNode(structure.nodes, null, (n) => {
|
|
479
|
+
const inc = inCount[n.id] || 0;
|
|
480
|
+
const out = outCount[n.id] || 0;
|
|
481
|
+
if (inc > 1 && !JOIN_CAPABLE_GATEWAYS.has(n.type)) {
|
|
482
|
+
issues.push(
|
|
483
|
+
`${n.id} (${n.name || n.type}): only a gateway may have multiple incoming flows (has ${inc}).`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
if (n.type === 'exclusiveGateway' && inc > 1 && out > 1) {
|
|
487
|
+
issues.push(
|
|
488
|
+
`${n.id} (${n.name || 'gateway'}): exclusiveGateway may not have both multiple incoming (${inc}) and multiple outgoing (${out}) flows.`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
return issues;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Run all lint rules and return combined issues.
|
|
496
|
+
function lintAll(structure) {
|
|
497
|
+
return [
|
|
498
|
+
...lintGateways(structure),
|
|
499
|
+
...lintEdgeNames(structure),
|
|
500
|
+
...lintIncomingEdges(structure),
|
|
501
|
+
];
|
|
502
|
+
}
|
|
503
|
+
|
|
410
504
|
// Remove an edge by id, or by --from/--to pair.
|
|
411
505
|
function disconnect(structure, { id, from, to }) {
|
|
412
506
|
let removed = 0;
|
|
@@ -464,4 +558,7 @@ export {
|
|
|
464
558
|
setDefault,
|
|
465
559
|
listGraph,
|
|
466
560
|
lintGateways,
|
|
561
|
+
lintEdgeNames,
|
|
562
|
+
lintIncomingEdges,
|
|
563
|
+
lintAll,
|
|
467
564
|
};
|
|
@@ -37,7 +37,13 @@ const ROOT_OPTS = {
|
|
|
37
37
|
'elk.layered.spacing.edgeEdgeBetweenLayers': '20',
|
|
38
38
|
'elk.layered.spacing.edgeNodeBetweenLayers': '20',
|
|
39
39
|
};
|
|
40
|
-
const CONTAINER_OPTS = {
|
|
40
|
+
const CONTAINER_OPTS = {
|
|
41
|
+
'elk.padding': '[top=50.0,left=30.0,bottom=50.0,right=30.0]',
|
|
42
|
+
'elk.layered.spacing.nodeNodeBetweenLayers': '60',
|
|
43
|
+
'elk.spacing.nodeNode': '80',
|
|
44
|
+
'elk.spacing.edgeNode': '20',
|
|
45
|
+
'elk.layered.spacing.edgeNodeBetweenLayers': '20',
|
|
46
|
+
};
|
|
41
47
|
|
|
42
48
|
function sizeOf(n) {
|
|
43
49
|
if (SIZE[n.type]) return SIZE[n.type];
|
|
@@ -147,6 +153,9 @@ async function autoLayout(structure) {
|
|
|
147
153
|
sources: [source],
|
|
148
154
|
targets: [e.target],
|
|
149
155
|
sourcePort,
|
|
156
|
+
labels: e.name
|
|
157
|
+
? [{ id: `${e.id}__lbl`, text: e.name, width: Math.min(e.name.length * 6, 140), height: 14 }]
|
|
158
|
+
: [],
|
|
150
159
|
layoutOptions: {
|
|
151
160
|
'elk.layered.priority.straightness': isHappy ? '10' : 1,
|
|
152
161
|
'elk.layered.priority.shortness': isHappy ? '10' : 1,
|
|
@@ -237,8 +246,20 @@ async function autoLayout(structure) {
|
|
|
237
246
|
height: round(p.height),
|
|
238
247
|
};
|
|
239
248
|
for (const e of n.edges || []) {
|
|
240
|
-
const
|
|
249
|
+
const off = lcaOffset(n.id, e.target);
|
|
250
|
+
const wp = waypointsFor(e.id, off);
|
|
241
251
|
if (wp) e.waypoints = wp;
|
|
252
|
+
if (e.name) {
|
|
253
|
+
const lbl = elkEdges[e.id]?.labels?.[0];
|
|
254
|
+
if (lbl) {
|
|
255
|
+
e.labelPos = {
|
|
256
|
+
x: round(lbl.x + off.x),
|
|
257
|
+
y: round(lbl.y + off.y),
|
|
258
|
+
width: round(lbl.width),
|
|
259
|
+
height: round(lbl.height),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
242
263
|
}
|
|
243
264
|
});
|
|
244
265
|
|