@flow-scanner/lightning-flow-scanner-core 6.10.6 → 6.11.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 +19 -16
- package/index.d.ts +5 -2
- package/index.js +19 -0
- package/main/config/NodeIcons.d.ts +24 -0
- package/main/config/NodeIcons.js +122 -0
- package/main/config/VariableIcons.d.ts +25 -0
- package/main/config/VariableIcons.js +53 -0
- package/main/libs/Compiler.d.ts +1 -2
- package/main/libs/Compiler.js +10 -16
- package/main/libs/ExportDiagram.d.ts +41 -0
- package/main/libs/ExportDiagram.js +40 -0
- package/main/models/Flow.d.ts +8 -0
- package/main/models/Flow.js +57 -2
- package/main/models/FlowGraph.d.ts +85 -0
- package/main/models/FlowGraph.js +532 -0
- package/main/models/FlowNode.d.ts +58 -2
- package/main/models/FlowNode.js +161 -3
- package/main/models/FlowVariable.d.ts +59 -1
- package/main/models/FlowVariable.js +118 -1
- package/main/models/LoopRuleCommon.js +11 -12
- package/main/rules/DuplicateDMLOperation.d.ts +1 -1
- package/main/rules/DuplicateDMLOperation.js +36 -67
- package/main/rules/MissingFaultPath.js +4 -14
- package/main/rules/UnconnectedElement.js +2 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://github.com/Flow-Scanner/lightning-flow-scanner/
|
|
3
|
-
<img src="https://img.shields.io/github/
|
|
4
|
-
</a>
|
|
5
|
-
<a href="https://github.com/Flow-Scanner/lightning-flow-scanner/graphs/contributors">
|
|
6
|
-
<img src="https://img.shields.io/github/contributors/Flow-Scanner/lightning-flow-scanner.svg?style=flat-square" alt="Contributors">
|
|
2
|
+
<a href="https://github.com/Flow-Scanner/lightning-flow-scanner/stargazers">
|
|
3
|
+
<img src="https://img.shields.io/github/stars/Flow-Scanner/lightning-flow-scanner?label=Stargazers&style=flat-square" alt="GitHub stars">
|
|
7
4
|
</a>
|
|
8
|
-
|
|
9
|
-
<img src="https://img.shields.io/npm/v/@flow-scanner/lightning-flow-scanner-core?label=
|
|
5
|
+
<a href="https://www.npmjs.com/package/@flow-scanner/lightning-flow-scanner-core">
|
|
6
|
+
<img src="https://img.shields.io/npm/v/@flow-scanner/lightning-flow-scanner-core?label=Core&style=flat-square" alt="Core version">
|
|
10
7
|
</a>
|
|
11
8
|
<a href="https://www.npmjs.com/package/lightning-flow-scanner">
|
|
12
|
-
<img src="https://img.shields.io/npm/v/lightning-flow-scanner?label=
|
|
9
|
+
<img src="https://img.shields.io/npm/v/lightning-flow-scanner?label=CLI&style=flat-square" alt="CLI version">
|
|
13
10
|
</a>
|
|
14
11
|
<a href="https://open-vsx.org/extension/ForceConfigControl/lightning-flow-scanner-vsx">
|
|
15
|
-
<img src="https://img.shields.io/open-vsx/v/ForceConfigControl/lightning-flow-scanner-vsx?label=
|
|
16
|
-
</a>
|
|
17
|
-
<a href="https://github.com/Flow-Scanner/lightning-flow-scanner/stargazers">
|
|
18
|
-
<img src="https://img.shields.io/github/stars/Flow-Scanner/lightning-flow-scanner?style=flat-square" alt="GitHub stars">
|
|
12
|
+
<img src="https://img.shields.io/open-vsx/v/ForceConfigControl/lightning-flow-scanner-vsx?label=VS%20Code&style=flat-square" alt="VS Code version">
|
|
19
13
|
</a>
|
|
20
14
|
<a href="https://www.npmjs.com/package/lightning-flow-scanner-core">
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
<img src="https://img.shields.io/npm/dt/lightning-flow-scanner-core?label=Downloads%3Cv6&style=flat-square" alt="Downloads <v6">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/@flow-scanner/lightning-flow-scanner-core">
|
|
18
|
+
<img src="https://img.shields.io/npm/dt/@flow-scanner/lightning-flow-scanner-core?label=Downloads%3Ev6&style=flat-square" alt="Downloads >v6">
|
|
19
|
+
</a>
|
|
23
20
|
</p>
|
|
24
21
|
|
|
25
22
|
<p align="center">
|
|
@@ -355,9 +352,15 @@ Use `lightning-flow-scanner-core` as a Node.js/browser dependency:
|
|
|
355
352
|
import { parse, scan } from "@flow-scanner/lightning-flow-scanner-core";
|
|
356
353
|
parse("flows/*.xml").then(scan);
|
|
357
354
|
|
|
358
|
-
// Get SARIF output
|
|
355
|
+
// Get SARIF output (e.g. for GitHub Code Scanning)
|
|
359
356
|
import { parse, scan, exportSarif } from "@flow-scanner/lightning-flow-scanner-core";
|
|
360
|
-
parse("flows
|
|
357
|
+
parse("flows/**/*.flow-meta.xml").then(scan).then(exportSarif)
|
|
358
|
+
// .then(sarif => fs.writeFile("results.sarif", sarif))
|
|
359
|
+
|
|
360
|
+
// Generate Markdown documentation with Mermaid flow diagrams
|
|
361
|
+
import { parse, exportDiagram } from "@flow-scanner/lightning-flow-scanner-core";
|
|
362
|
+
parse("flows/**/*.flow-meta.xml").then(exportDiagram)
|
|
363
|
+
// .then(md => fs.writeFile("FLOW_DOCUMENTATION.md", md))
|
|
361
364
|
|
|
362
365
|
// Browser Usage (Tooling API)
|
|
363
366
|
const { Flow, scan } = window.lightningflowscanner;
|
package/index.d.ts
CHANGED
|
@@ -19,5 +19,8 @@ import { ParsedFlow } from "./main/models/ParsedFlow";
|
|
|
19
19
|
import { RuleResult } from "./main/models/RuleResult";
|
|
20
20
|
import { ScanResult } from "./main/models/ScanResult";
|
|
21
21
|
import { Violation } from "./main/models/Violation";
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
import { DEFAULT_ICONS, ASCII_ICONS, type NodeIconConfig } from "./main/config/NodeIcons";
|
|
23
|
+
import { DEFAULT_VARIABLE_ICONS, ASCII_VARIABLE_ICONS, type VariableIconConfig } from "./main/config/VariableIcons";
|
|
24
|
+
import { exportDiagram, type DiagramOptions } from "./main/libs/ExportDiagram";
|
|
25
|
+
export { Compiler, exportDetails, exportDiagram, exportSarif, fix, Flow, FlowAttribute, FlowElement, FlowNode, FlowResource, FlowType, FlowVariable, getRules, parse, ParsedFlow, Violation, RuleResult, scan, ScanResult, DEFAULT_ICONS, ASCII_ICONS, DEFAULT_VARIABLE_ICONS, ASCII_VARIABLE_ICONS, };
|
|
26
|
+
export type { FlatViolation, IRuleDefinition, IRulesConfig, NodeIconConfig, DiagramOptions, VariableIconConfig };
|
package/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Updated exports in index.ts
|
|
1
2
|
"use strict";
|
|
2
3
|
Object.defineProperty(exports, "__esModule", {
|
|
3
4
|
value: true
|
|
@@ -9,9 +10,21 @@ function _export(target, all) {
|
|
|
9
10
|
});
|
|
10
11
|
}
|
|
11
12
|
_export(exports, {
|
|
13
|
+
get ASCII_ICONS () {
|
|
14
|
+
return _NodeIcons.ASCII_ICONS;
|
|
15
|
+
},
|
|
16
|
+
get ASCII_VARIABLE_ICONS () {
|
|
17
|
+
return _VariableIcons.ASCII_VARIABLE_ICONS;
|
|
18
|
+
},
|
|
12
19
|
get Compiler () {
|
|
13
20
|
return _Compiler.Compiler;
|
|
14
21
|
},
|
|
22
|
+
get DEFAULT_ICONS () {
|
|
23
|
+
return _NodeIcons.DEFAULT_ICONS;
|
|
24
|
+
},
|
|
25
|
+
get DEFAULT_VARIABLE_ICONS () {
|
|
26
|
+
return _VariableIcons.DEFAULT_VARIABLE_ICONS;
|
|
27
|
+
},
|
|
15
28
|
get Flow () {
|
|
16
29
|
return _Flow.Flow;
|
|
17
30
|
},
|
|
@@ -48,6 +61,9 @@ _export(exports, {
|
|
|
48
61
|
get exportDetails () {
|
|
49
62
|
return _ExportDetails.exportDetails;
|
|
50
63
|
},
|
|
64
|
+
get exportDiagram () {
|
|
65
|
+
return _ExportDiagram.exportDiagram;
|
|
66
|
+
},
|
|
51
67
|
get exportSarif () {
|
|
52
68
|
return _ExportSarif.exportSarif;
|
|
53
69
|
},
|
|
@@ -82,3 +98,6 @@ const _ParsedFlow = require("./main/models/ParsedFlow");
|
|
|
82
98
|
const _RuleResult = require("./main/models/RuleResult");
|
|
83
99
|
const _ScanResult = require("./main/models/ScanResult");
|
|
84
100
|
const _Violation = require("./main/models/Violation");
|
|
101
|
+
const _NodeIcons = require("./main/config/NodeIcons");
|
|
102
|
+
const _VariableIcons = require("./main/config/VariableIcons");
|
|
103
|
+
const _ExportDiagram = require("./main/libs/ExportDiagram");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon configuration for flow node types.
|
|
3
|
+
* Emoji are safe in UTF-8 encoded source files and will work in all modern builds.
|
|
4
|
+
* If you need ASCII fallback, use FlowNode.setIconConfig(ASCII_ICONS)
|
|
5
|
+
*/
|
|
6
|
+
export interface NodeIconConfig {
|
|
7
|
+
[nodeType: string]: {
|
|
8
|
+
[subtype: string]: string;
|
|
9
|
+
} | {
|
|
10
|
+
default: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Default icons using emoji (recommended for modern environments)
|
|
15
|
+
*/
|
|
16
|
+
export declare const DEFAULT_ICONS: NodeIconConfig;
|
|
17
|
+
/**
|
|
18
|
+
* ASCII fallback icons (for environments without emoji support)
|
|
19
|
+
*/
|
|
20
|
+
export declare const ASCII_ICONS: NodeIconConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Get default icon config (can be overridden at runtime)
|
|
23
|
+
*/
|
|
24
|
+
export declare function getDefaultIconConfig(): NodeIconConfig;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon configuration for flow node types.
|
|
3
|
+
* Emoji are safe in UTF-8 encoded source files and will work in all modern builds.
|
|
4
|
+
* If you need ASCII fallback, use FlowNode.setIconConfig(ASCII_ICONS)
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
function _export(target, all) {
|
|
10
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
_export(exports, {
|
|
16
|
+
get ASCII_ICONS () {
|
|
17
|
+
return ASCII_ICONS;
|
|
18
|
+
},
|
|
19
|
+
get DEFAULT_ICONS () {
|
|
20
|
+
return DEFAULT_ICONS;
|
|
21
|
+
},
|
|
22
|
+
get getDefaultIconConfig () {
|
|
23
|
+
return getDefaultIconConfig;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const DEFAULT_ICONS = {
|
|
27
|
+
actionCalls: {
|
|
28
|
+
apex: '\u2699\uFE0F',
|
|
29
|
+
emailAlert: '\uD83D\uDCE7',
|
|
30
|
+
emailSimple: '\uD83D\uDCE7',
|
|
31
|
+
submit: '\u26A1',
|
|
32
|
+
default: '\u26A1' // HIGH VOLTAGE
|
|
33
|
+
},
|
|
34
|
+
assignments: {
|
|
35
|
+
default: '\uD83D\uDFF0' // 🟰 HEAVY EQUALS SIGN
|
|
36
|
+
},
|
|
37
|
+
collectionProcessors: {
|
|
38
|
+
FilterCollectionProcessor: '\uD83D\uDD3D',
|
|
39
|
+
SortCollectionProcessor: '\uD83D\uDD03',
|
|
40
|
+
default: '\uD83D\uDCE6' // PACKAGE
|
|
41
|
+
},
|
|
42
|
+
customErrors: {
|
|
43
|
+
default: '\uD83D\uDEAB' // PROHIBITED
|
|
44
|
+
},
|
|
45
|
+
decisions: {
|
|
46
|
+
default: '\uD83D\uDD00' // TWISTED ARROWS
|
|
47
|
+
},
|
|
48
|
+
loops: {
|
|
49
|
+
default: '\uD83D\uDD01' // REPEAT BUTTON
|
|
50
|
+
},
|
|
51
|
+
recordCreates: {
|
|
52
|
+
default: '\u2795' // PLUS
|
|
53
|
+
},
|
|
54
|
+
recordDeletes: {
|
|
55
|
+
default: '\uD83D\uDDD1\uFE0F' // WASTEBASKET
|
|
56
|
+
},
|
|
57
|
+
recordLookups: {
|
|
58
|
+
default: '\uD83D\uDD0D' // MAGNIFYING GLASS
|
|
59
|
+
},
|
|
60
|
+
recordUpdates: {
|
|
61
|
+
default: '\uD83D\uDEE0\uFE0F' // HAMMER AND WRENCH
|
|
62
|
+
},
|
|
63
|
+
screens: {
|
|
64
|
+
default: '\uD83D\uDCBB' // LAPTOP
|
|
65
|
+
},
|
|
66
|
+
subflows: {
|
|
67
|
+
default: '\uD83D\uDD17' // LINK
|
|
68
|
+
},
|
|
69
|
+
transforms: {
|
|
70
|
+
default: '\u267B\uFE0F'
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const ASCII_ICONS = {
|
|
74
|
+
actionCalls: {
|
|
75
|
+
apex: '[A]',
|
|
76
|
+
emailAlert: '[E]',
|
|
77
|
+
emailSimple: '[E]',
|
|
78
|
+
submit: '[!]',
|
|
79
|
+
default: '[!]'
|
|
80
|
+
},
|
|
81
|
+
assignments: {
|
|
82
|
+
default: '[=]'
|
|
83
|
+
},
|
|
84
|
+
collectionProcessors: {
|
|
85
|
+
FilterCollectionProcessor: '[F]',
|
|
86
|
+
SortCollectionProcessor: '[S]',
|
|
87
|
+
default: '[C]'
|
|
88
|
+
},
|
|
89
|
+
customErrors: {
|
|
90
|
+
default: '[X]'
|
|
91
|
+
},
|
|
92
|
+
decisions: {
|
|
93
|
+
default: '[?]'
|
|
94
|
+
},
|
|
95
|
+
loops: {
|
|
96
|
+
default: '[L]'
|
|
97
|
+
},
|
|
98
|
+
recordCreates: {
|
|
99
|
+
default: '[+]'
|
|
100
|
+
},
|
|
101
|
+
recordDeletes: {
|
|
102
|
+
default: '[-]'
|
|
103
|
+
},
|
|
104
|
+
recordLookups: {
|
|
105
|
+
default: '[S]'
|
|
106
|
+
},
|
|
107
|
+
recordUpdates: {
|
|
108
|
+
default: '[U]'
|
|
109
|
+
},
|
|
110
|
+
screens: {
|
|
111
|
+
default: '[#]'
|
|
112
|
+
},
|
|
113
|
+
subflows: {
|
|
114
|
+
default: '[>]'
|
|
115
|
+
},
|
|
116
|
+
transforms: {
|
|
117
|
+
default: '[T]'
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
function getDefaultIconConfig() {
|
|
121
|
+
return DEFAULT_ICONS;
|
|
122
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon configuration for flow variable types.
|
|
3
|
+
* Includes icons for variable subtypes and boolean states.
|
|
4
|
+
*/
|
|
5
|
+
export interface VariableIconConfig {
|
|
6
|
+
subtypes: {
|
|
7
|
+
[subtype: string]: string;
|
|
8
|
+
};
|
|
9
|
+
boolean: {
|
|
10
|
+
true: string;
|
|
11
|
+
false: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Default icons using emoji (recommended for modern environments)
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_VARIABLE_ICONS: VariableIconConfig;
|
|
18
|
+
/**
|
|
19
|
+
* ASCII fallback icons (for environments without emoji support)
|
|
20
|
+
*/
|
|
21
|
+
export declare const ASCII_VARIABLE_ICONS: VariableIconConfig;
|
|
22
|
+
/**
|
|
23
|
+
* Get default variable icon config
|
|
24
|
+
*/
|
|
25
|
+
export declare function getDefaultVariableIconConfig(): VariableIconConfig;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon configuration for flow variable types.
|
|
3
|
+
* Includes icons for variable subtypes and boolean states.
|
|
4
|
+
*/ "use strict";
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
function _export(target, all) {
|
|
9
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
_export(exports, {
|
|
15
|
+
get ASCII_VARIABLE_ICONS () {
|
|
16
|
+
return ASCII_VARIABLE_ICONS;
|
|
17
|
+
},
|
|
18
|
+
get DEFAULT_VARIABLE_ICONS () {
|
|
19
|
+
return DEFAULT_VARIABLE_ICONS;
|
|
20
|
+
},
|
|
21
|
+
get getDefaultVariableIconConfig () {
|
|
22
|
+
return getDefaultVariableIconConfig;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const DEFAULT_VARIABLE_ICONS = {
|
|
26
|
+
subtypes: {
|
|
27
|
+
variables: '\uD83D\uDCCA',
|
|
28
|
+
constants: '\uD83D\uDD12',
|
|
29
|
+
formulas: '\uD83E\uDDEE',
|
|
30
|
+
choices: '\uD83D\uDCCB',
|
|
31
|
+
dynamicChoiceSets: '\uD83D\uDD04'
|
|
32
|
+
},
|
|
33
|
+
boolean: {
|
|
34
|
+
true: '\u2705',
|
|
35
|
+
false: '\u2B1C'
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const ASCII_VARIABLE_ICONS = {
|
|
39
|
+
subtypes: {
|
|
40
|
+
variables: '[V]',
|
|
41
|
+
constants: '[C]',
|
|
42
|
+
formulas: '[F]',
|
|
43
|
+
choices: '[CH]',
|
|
44
|
+
dynamicChoiceSets: '[D]'
|
|
45
|
+
},
|
|
46
|
+
boolean: {
|
|
47
|
+
true: '[X]',
|
|
48
|
+
false: '[ ]'
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function getDefaultVariableIconConfig() {
|
|
52
|
+
return DEFAULT_VARIABLE_ICONS;
|
|
53
|
+
}
|
package/main/libs/Compiler.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { Flow } from "../models/Flow";
|
|
2
1
|
import { FlowNode } from "../models/FlowNode";
|
|
3
2
|
export declare class Compiler {
|
|
4
3
|
visitedElements: Set<string>;
|
|
5
4
|
constructor();
|
|
6
|
-
traverseFlow(
|
|
5
|
+
traverseFlow(startElementName: string, visitCallback: (element: FlowNode) => void, nodeMap: Map<string, FlowNode>, allConnectors: Map<string, Set<string>>, endElementName?: string): void;
|
|
7
6
|
private findNextElements;
|
|
8
7
|
}
|
package/main/libs/Compiler.js
CHANGED
|
@@ -22,7 +22,7 @@ function _define_property(obj, key, value) {
|
|
|
22
22
|
return obj;
|
|
23
23
|
}
|
|
24
24
|
let Compiler = class Compiler {
|
|
25
|
-
traverseFlow(
|
|
25
|
+
traverseFlow(startElementName, visitCallback, nodeMap, allConnectors, endElementName) {
|
|
26
26
|
// Iterative Deepening Depth-First Search (IDDFS)
|
|
27
27
|
let elementsToVisit = [
|
|
28
28
|
startElementName
|
|
@@ -31,12 +31,11 @@ let Compiler = class Compiler {
|
|
|
31
31
|
const nextElements = [];
|
|
32
32
|
for (const elementName of elementsToVisit){
|
|
33
33
|
if (!this.visitedElements.has(elementName)) {
|
|
34
|
-
|
|
35
|
-
const currentElement = (_flow_elements = flow.elements) === null || _flow_elements === void 0 ? void 0 : _flow_elements.find((element)=>element.name === elementName);
|
|
34
|
+
const currentElement = nodeMap.get(elementName);
|
|
36
35
|
if (currentElement) {
|
|
37
36
|
visitCallback(currentElement);
|
|
38
37
|
this.visitedElements.add(elementName);
|
|
39
|
-
nextElements.push(...this.findNextElements(
|
|
38
|
+
nextElements.push(...this.findNextElements(elementName, allConnectors, nodeMap, endElementName));
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
}
|
|
@@ -46,19 +45,14 @@ let Compiler = class Compiler {
|
|
|
46
45
|
elementsToVisit = nextElements;
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
|
-
findNextElements(
|
|
48
|
+
findNextElements(elementName, allConnectors, nodeMap, endElementName) {
|
|
50
49
|
const nextElements = [];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const targetReference = (_connector_connectorTargetReference_targetReference = connector === null || connector === void 0 ? void 0 : (_connector_connectorTargetReference = connector.connectorTargetReference) === null || _connector_connectorTargetReference === void 0 ? void 0 : _connector_connectorTargetReference.targetReference) !== null && _connector_connectorTargetReference_targetReference !== void 0 ? _connector_connectorTargetReference_targetReference : connector.reference;
|
|
58
|
-
// Check if the reference exists in the flow elements
|
|
59
|
-
const nextElement = (_flow_elements = flow.elements) === null || _flow_elements === void 0 ? void 0 : _flow_elements.find((element)=>element.metaType === "node" && element.name === targetReference);
|
|
60
|
-
if (nextElement && nextElement.metaType === "node" && nextElement.name !== endElementName) {
|
|
61
|
-
nextElements.push(nextElement.name);
|
|
50
|
+
const targets = allConnectors.get(elementName);
|
|
51
|
+
if (targets) {
|
|
52
|
+
for (const targetReference of targets){
|
|
53
|
+
if (targetReference !== endElementName && nodeMap.has(targetReference)) {
|
|
54
|
+
nextElements.push(targetReference);
|
|
55
|
+
}
|
|
62
56
|
}
|
|
63
57
|
}
|
|
64
58
|
return nextElements;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ParsedFlow } from "../models/ParsedFlow";
|
|
2
|
+
export interface DiagramOptions {
|
|
3
|
+
includeDetails?: boolean;
|
|
4
|
+
includeMarkdownDocs?: boolean;
|
|
5
|
+
collapsedDetails?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generate Markdown documentation for parsed flows, including Mermaid diagrams for valid flows.
|
|
9
|
+
*
|
|
10
|
+
* This function can be chained after parse() to generate documentation.
|
|
11
|
+
* It filters out errored parses and only documents valid flows.
|
|
12
|
+
*
|
|
13
|
+
* @param parsedFlows Array of ParsedFlow objects from parse()
|
|
14
|
+
* @param options Visualization options for Mermaid diagrams
|
|
15
|
+
*
|
|
16
|
+
* @returns Markdown string with documentation
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Node.js usage
|
|
21
|
+
* import { parse, exportFlowMarkdown } from "@flow-scanner/lightning-flow-scanner-core";
|
|
22
|
+
* import fs from "fs/promises";
|
|
23
|
+
*
|
|
24
|
+
* const parsed = await parse(["flows/*.xml"]);
|
|
25
|
+
* const md = exportFlowMarkdown(parsed, {
|
|
26
|
+
* includeDetails: true,
|
|
27
|
+
* includeMarkdownDocs: true,
|
|
28
|
+
* collapsedDetails: true
|
|
29
|
+
* });
|
|
30
|
+
* await fs.writeFile("flow-doc.md", md);
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // Chained (async/await)
|
|
36
|
+
* const md = await parse(["flows/*.xml"]).then(parsed =>
|
|
37
|
+
* exportFlowMarkdown(parsed, { includeDetails: true })
|
|
38
|
+
* );
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function exportDiagram(parsedFlows: ParsedFlow[], options?: DiagramOptions): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "exportDiagram", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return exportDiagram;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
function exportDiagram(parsedFlows, options = {
|
|
12
|
+
includeDetails: true,
|
|
13
|
+
includeMarkdownDocs: true,
|
|
14
|
+
collapsedDetails: true
|
|
15
|
+
}) {
|
|
16
|
+
const validFlows = parsedFlows.filter((p)=>p.flow).map((p)=>p.flow);
|
|
17
|
+
let markdown = "# Flow Documentation\n\n";
|
|
18
|
+
if (validFlows.length === 0) {
|
|
19
|
+
markdown += "No valid flows found.\n\n";
|
|
20
|
+
}
|
|
21
|
+
for (const flow of validFlows){
|
|
22
|
+
markdown += `## ${flow.name}\n\n`;
|
|
23
|
+
const vizOptions = {
|
|
24
|
+
includeDetails: options.includeDetails,
|
|
25
|
+
includeMarkdownDocs: options.includeMarkdownDocs,
|
|
26
|
+
collapsedDetails: options.collapsedDetails
|
|
27
|
+
};
|
|
28
|
+
markdown += flow.visualize("mermaid", vizOptions) + "\n\n";
|
|
29
|
+
}
|
|
30
|
+
// Optionally add errors section if any
|
|
31
|
+
const errors = parsedFlows.filter((p)=>p.errorMessage);
|
|
32
|
+
if (errors.length > 0) {
|
|
33
|
+
markdown += "## Parse Errors\n\n";
|
|
34
|
+
for (const err of errors){
|
|
35
|
+
markdown += `- ${err.uri}: ${err.errorMessage}\n`;
|
|
36
|
+
}
|
|
37
|
+
markdown += "\n";
|
|
38
|
+
}
|
|
39
|
+
return markdown;
|
|
40
|
+
}
|
package/main/models/Flow.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FlowElement } from "./FlowElement";
|
|
2
2
|
import { FlowNode } from "./FlowNode";
|
|
3
|
+
import { FlowGraph } from "./FlowGraph";
|
|
3
4
|
export declare class Flow {
|
|
4
5
|
/**
|
|
5
6
|
* Metadata Tags of Salesforce Flow Attributes
|
|
@@ -42,11 +43,18 @@ export declare class Flow {
|
|
|
42
43
|
* Access start element data via startNode.element
|
|
43
44
|
*/
|
|
44
45
|
startNode?: FlowNode;
|
|
46
|
+
private _graph?;
|
|
47
|
+
get graph(): FlowGraph;
|
|
45
48
|
root?: any;
|
|
46
49
|
xmldata: any;
|
|
47
50
|
constructor(path?: string, data?: unknown);
|
|
48
51
|
static from(obj: Partial<Flow>): Flow;
|
|
49
52
|
preProcessNodes(): void;
|
|
53
|
+
visualize(format?: 'mermaid' | 'plantuml', options?: {
|
|
54
|
+
includeDetails?: boolean;
|
|
55
|
+
includeMarkdownDocs?: boolean;
|
|
56
|
+
collapsedDetails?: boolean;
|
|
57
|
+
}): string;
|
|
50
58
|
private processNodeType;
|
|
51
59
|
/**
|
|
52
60
|
* Find the name of the first element to execute.
|
package/main/models/Flow.js
CHANGED
|
@@ -14,6 +14,7 @@ const _FlowMetadata = require("./FlowMetadata");
|
|
|
14
14
|
const _FlowNode = require("./FlowNode");
|
|
15
15
|
const _FlowResource = require("./FlowResource");
|
|
16
16
|
const _FlowVariable = require("./FlowVariable");
|
|
17
|
+
const _FlowGraph = require("./FlowGraph");
|
|
17
18
|
function _define_property(obj, key, value) {
|
|
18
19
|
if (key in obj) {
|
|
19
20
|
Object.defineProperty(obj, key, {
|
|
@@ -83,7 +84,39 @@ function _object_spread(target) {
|
|
|
83
84
|
}
|
|
84
85
|
return target;
|
|
85
86
|
}
|
|
87
|
+
function ownKeys(object, enumerableOnly) {
|
|
88
|
+
var keys = Object.keys(object);
|
|
89
|
+
if (Object.getOwnPropertySymbols) {
|
|
90
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
91
|
+
if (enumerableOnly) {
|
|
92
|
+
symbols = symbols.filter(function(sym) {
|
|
93
|
+
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
keys.push.apply(keys, symbols);
|
|
97
|
+
}
|
|
98
|
+
return keys;
|
|
99
|
+
}
|
|
100
|
+
function _object_spread_props(target, source) {
|
|
101
|
+
source = source != null ? source : {};
|
|
102
|
+
if (Object.getOwnPropertyDescriptors) {
|
|
103
|
+
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
104
|
+
} else {
|
|
105
|
+
ownKeys(Object(source)).forEach(function(key) {
|
|
106
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return target;
|
|
110
|
+
}
|
|
86
111
|
let Flow = class Flow {
|
|
112
|
+
get graph() {
|
|
113
|
+
if (!this._graph) {
|
|
114
|
+
const flowNodes = this.elements.filter((e)=>e instanceof _FlowNode.FlowNode);
|
|
115
|
+
this.startReference || (this.startReference = this.findStart());
|
|
116
|
+
this._graph = new _FlowGraph.FlowGraph(flowNodes, this.startReference, this.startNode);
|
|
117
|
+
}
|
|
118
|
+
return this._graph;
|
|
119
|
+
}
|
|
87
120
|
static from(obj) {
|
|
88
121
|
if (obj instanceof Flow) {
|
|
89
122
|
return obj;
|
|
@@ -115,14 +148,14 @@ let Flow = class Flow {
|
|
|
115
148
|
continue;
|
|
116
149
|
}
|
|
117
150
|
const data = this.xmldata[nodeType];
|
|
118
|
-
// Handle start nodes separately - store in startNode
|
|
151
|
+
// Handle start nodes separately - store in startNode property
|
|
119
152
|
if (nodeType === "start") {
|
|
120
153
|
if (Array.isArray(data) && data.length > 0) {
|
|
121
154
|
this.startNode = new _FlowNode.FlowNode(data[0].name || "start", "start", data[0]);
|
|
122
155
|
} else if (!Array.isArray(data)) {
|
|
123
156
|
this.startNode = new _FlowNode.FlowNode(data.name || "start", "start", data);
|
|
124
157
|
}
|
|
125
|
-
continue;
|
|
158
|
+
continue;
|
|
126
159
|
}
|
|
127
160
|
// Process other node types
|
|
128
161
|
if (Flow.ATTRIBUTE_TAGS.includes(nodeType)) {
|
|
@@ -137,6 +170,27 @@ let Flow = class Flow {
|
|
|
137
170
|
}
|
|
138
171
|
this.elements = allNodes;
|
|
139
172
|
this.startReference = this.findStart();
|
|
173
|
+
// Build the connectivity graph
|
|
174
|
+
const flowNodes = allNodes.filter((e)=>e instanceof _FlowNode.FlowNode);
|
|
175
|
+
this._graph = new _FlowGraph.FlowGraph(flowNodes, this.startReference, this.startNode);
|
|
176
|
+
}
|
|
177
|
+
visualize(format = 'mermaid', options = {}) {
|
|
178
|
+
if (format === 'mermaid') {
|
|
179
|
+
var _this_xmldata, _this_startNode_element, _this_startNode, _this_startNode_element1, _this_startNode1;
|
|
180
|
+
return this.graph.toMermaid(_object_spread_props(_object_spread({}, options), {
|
|
181
|
+
flowMetadata: {
|
|
182
|
+
label: this.label,
|
|
183
|
+
processType: this.processType,
|
|
184
|
+
status: this.status,
|
|
185
|
+
description: (_this_xmldata = this.xmldata) === null || _this_xmldata === void 0 ? void 0 : _this_xmldata.description,
|
|
186
|
+
triggerType: (_this_startNode = this.startNode) === null || _this_startNode === void 0 ? void 0 : (_this_startNode_element = _this_startNode.element) === null || _this_startNode_element === void 0 ? void 0 : _this_startNode_element['triggerType'],
|
|
187
|
+
object: (_this_startNode1 = this.startNode) === null || _this_startNode1 === void 0 ? void 0 : (_this_startNode_element1 = _this_startNode1.element) === null || _this_startNode_element1 === void 0 ? void 0 : _this_startNode_element1['object']
|
|
188
|
+
}
|
|
189
|
+
}));
|
|
190
|
+
} else if (format === 'plantuml') {
|
|
191
|
+
return this.graph.toPlantUML();
|
|
192
|
+
}
|
|
193
|
+
throw new Error('Unsupported format');
|
|
140
194
|
}
|
|
141
195
|
processNodeType(data, nodeType, allNodes, NodeClass) {
|
|
142
196
|
if (Array.isArray(data)) {
|
|
@@ -248,6 +302,7 @@ let Flow = class Flow {
|
|
|
248
302
|
* Contains trigger information and connectors.
|
|
249
303
|
* Access start element data via startNode.element
|
|
250
304
|
*/ _define_property(this, "startNode", void 0);
|
|
305
|
+
_define_property(this, "_graph", void 0);
|
|
251
306
|
// Legacy/internal
|
|
252
307
|
_define_property(this, "root", void 0);
|
|
253
308
|
_define_property(this, "xmldata", void 0);
|