@flow-scanner/lightning-flow-scanner-core 6.18.0 → 6.19.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 +45 -15
- package/main/config/RuleRegistry.js +2 -0
- package/main/interfaces/IRuleDefinition.d.ts +3 -0
- package/main/internals/internals.d.ts +2 -1
- package/main/internals/internals.js +4 -0
- package/main/libs/FixFlows.d.ts +1 -1
- package/main/libs/FixFlows.js +42 -2
- package/main/libs/ScanFlows.js +12 -0
- package/main/models/RuleCommon.d.ts +3 -1
- package/main/models/RuleCommon.js +6 -7
- package/main/models/RuleInfo.d.ts +18 -0
- package/main/models/RuleInfo.js +7 -0
- package/main/rules/APIVersion.js +10 -1
- package/main/rules/AutoLayout.js +2 -1
- package/main/rules/CognitiveComplexity.d.ts +33 -0
- package/main/rules/CognitiveComplexity.js +175 -0
- package/main/rules/CyclomaticComplexity.js +10 -1
- package/main/rules/FlowName.js +9 -1
- package/main/rules/HardcodedId.js +1 -1
- package/main/rules/HardcodedUrl.js +1 -1
- package/main/rules/UnconnectedElement.js +2 -1
- package/main/rules/UnusedVariable.js +2 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -70,20 +70,13 @@ Executing DML operations (insert, update, delete) inside a loop is a high-risk a
|
|
|
70
70
|
**Class Name:** _[DMLStatementInLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/DMLStatementInLoop.ts)_
|
|
71
71
|
**Severity:** 🔴 *Error*
|
|
72
72
|
|
|
73
|
-
#### Hardcoded
|
|
73
|
+
#### Hardcoded Id
|
|
74
74
|
Avoid hard-coding record IDs, as they are unique to a specific org and will not work in other environments. Instead, store IDs in variables—such as merge-field URL parameters or a **Get Records** element—to make the Flow portable, maintainable, and flexible.
|
|
75
75
|
|
|
76
76
|
**Rule ID:** `hardcoded-id`
|
|
77
77
|
**Class Name:** _[HardcodedId](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/HardcodedId.ts)_
|
|
78
78
|
**Severity:** 🔴 *Error*
|
|
79
79
|
|
|
80
|
-
#### Hardcoded Salesforce Url
|
|
81
|
-
Avoid hard-coding URLs, as they may change between environments or over time. Instead, store URLs in variables or custom settings to make the Flow adaptable, maintainable, and environment-independent.
|
|
82
|
-
|
|
83
|
-
**Rule ID:** `hardcoded-url`
|
|
84
|
-
**Class Name:** _[HardcodedUrl](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/HardcodedUrl.ts)_
|
|
85
|
-
**Severity:** 🔴 *Error*
|
|
86
|
-
|
|
87
80
|
#### Hardcoded Secret 
|
|
88
81
|
Avoid hardcoding secrets, API keys, tokens, or credentials in Flows. These should be stored securely in Named Credentials, Custom Settings, Custom Metadata, or external secret management systems.
|
|
89
82
|
|
|
@@ -91,6 +84,13 @@ Avoid hardcoding secrets, API keys, tokens, or credentials in Flows. These shoul
|
|
|
91
84
|
**Class Name:** _[HardcodedSecret](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/HardcodedSecret.ts)_
|
|
92
85
|
**Severity:** 🔴 *Error*
|
|
93
86
|
|
|
87
|
+
#### Hardcoded Url
|
|
88
|
+
Avoid hard-coding URLs, as they may change between environments or over time. Instead, store URLs in variables or custom settings to make the Flow adaptable, maintainable, and environment-independent.
|
|
89
|
+
|
|
90
|
+
**Rule ID:** `hardcoded-url`
|
|
91
|
+
**Class Name:** _[HardcodedUrl](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/HardcodedUrl.ts)_
|
|
92
|
+
**Severity:** 🔴 *Error*
|
|
93
|
+
|
|
94
94
|
#### Process Builder
|
|
95
95
|
Process Builder is retired. Continuing to use it increases maintenance overhead and risks future compatibility issues. Migrating automation to Flow reduces risk and improves maintainability.
|
|
96
96
|
|
|
@@ -167,13 +167,18 @@ Inactive Flows should be deleted or archived to reduce risk. Even when inactive,
|
|
|
167
167
|
**Class Name:** _[InactiveFlow](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/InactiveFlow.ts)_
|
|
168
168
|
**Severity:** 🟡 *Warning*
|
|
169
169
|
|
|
170
|
-
#### Invalid API Version
|
|
170
|
+
#### Invalid API Version 
|
|
171
171
|
Flows running on outdated API versions may behave inconsistently when newer platform features or components are used. From API version 50.0 onward, the API Version attribute explicitly controls Flow runtime behavior. Keeping Flows aligned with a supported API version helps prevent compatibility issues and ensures predictable execution.
|
|
172
172
|
|
|
173
173
|
**Rule ID:** `invalid-api-version`
|
|
174
174
|
**Class Name:** _[APIVersion](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/APIVersion.ts)_
|
|
175
175
|
**Severity:** 🟡 *Warning*
|
|
176
176
|
|
|
177
|
+
| Option | Type | Default | Description |
|
|
178
|
+
|--------|------|---------|-------------|
|
|
179
|
+
| expression | expression | `>= 50` | Comparison expression for API version (e.g., `>= 58`, `< 50`, `=== 60`) |
|
|
180
|
+
|
|
181
|
+
|
|
177
182
|
#### Missing Filter Record Trigger 
|
|
178
183
|
Record-triggered Flows without filters on changed fields or entry conditions execute on every record change. Adding filters ensures the Flow runs only when needed, improving performance.
|
|
179
184
|
|
|
@@ -188,6 +193,18 @@ Before-save Flows can safely update the triggering record directly via $Record,
|
|
|
188
193
|
**Class Name:** _[SameRecordFieldUpdates](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/SameRecordFieldUpdates.ts)_
|
|
189
194
|
**Severity:** 🟡 *Warning*
|
|
190
195
|
|
|
196
|
+
#### Cognitive Complexity
|
|
197
|
+
Flows with deeply nested loops and decisions are hard to understand. Unlike cyclomatic complexity which counts paths, cognitive complexity penalizes nesting depth. Consider extracting nested logic into subflows.
|
|
198
|
+
|
|
199
|
+
**Rule ID:** `cognitive-complexity`
|
|
200
|
+
**Class Name:** _[CognitiveComplexity](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/CognitiveComplexity.ts)_
|
|
201
|
+
**Severity:** 🔵 *Note*
|
|
202
|
+
|
|
203
|
+
| Option | Type | Default | Description |
|
|
204
|
+
|--------|------|---------|-------------|
|
|
205
|
+
| threshold | number | `15` | Maximum cognitive complexity score before triggering a violation |
|
|
206
|
+
|
|
207
|
+
|
|
191
208
|
#### Excessive Cyclomatic Complexity
|
|
192
209
|
High numbers of loops and decision elements increase a Flow's cyclomatic complexity. To maintain simplicity and readability, consider using subflows or splitting a Flow into smaller, ordered Flows.
|
|
193
210
|
|
|
@@ -195,6 +212,11 @@ High numbers of loops and decision elements increase a Flow's cyclomatic complex
|
|
|
195
212
|
**Class Name:** _[CyclomaticComplexity](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/CyclomaticComplexity.ts)_
|
|
196
213
|
**Severity:** 🔵 *Note*
|
|
197
214
|
|
|
215
|
+
| Option | Type | Default | Description |
|
|
216
|
+
|--------|------|---------|-------------|
|
|
217
|
+
| threshold | number | `25` | Maximum cyclomatic complexity score before triggering a violation |
|
|
218
|
+
|
|
219
|
+
|
|
198
220
|
#### Missing Trigger Order
|
|
199
221
|
Record-triggered Flows without a specified Trigger Order may execute in an unpredictable sequence. Setting a Trigger Order ensures your Flows run in the intended order.
|
|
200
222
|
|
|
@@ -229,6 +251,11 @@ Using clear and consistent Flow names improves readability, discoverability, and
|
|
|
229
251
|
**Class Name:** _[FlowName](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/FlowName.ts)_
|
|
230
252
|
**Severity:** 🔴 *Error*
|
|
231
253
|
|
|
254
|
+
| Option | Type | Default | Description |
|
|
255
|
+
|--------|------|---------|-------------|
|
|
256
|
+
| expression | expression | `[A-Za-z0-9]+_[A-Za-z0-9]+` | Regex pattern for valid Flow names |
|
|
257
|
+
|
|
258
|
+
|
|
232
259
|
#### Missing Flow Description
|
|
233
260
|
Flow descriptions are essential for documentation and maintainability. Include a description for each Flow, explaining its purpose and where it's used.
|
|
234
261
|
|
|
@@ -250,21 +277,21 @@ Elements with unclear or duplicated API names, like Copy_X_Of_Element, reduce Fl
|
|
|
250
277
|
**Class Name:** _[CopyAPIName](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/CopyAPIName.ts)_
|
|
251
278
|
**Severity:** 🟡 *Warning*
|
|
252
279
|
|
|
253
|
-
#### Unreachable Element
|
|
280
|
+
#### Unreachable Element 
|
|
254
281
|
Unconnected elements never execute and add unnecessary clutter. Remove or connect unused Flow elements to keep Flows clean and efficient.
|
|
255
282
|
|
|
256
283
|
**Rule ID:** `unreachable-element`
|
|
257
284
|
**Class Name:** _[UnconnectedElement](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/UnconnectedElement.ts)_
|
|
258
285
|
**Severity:** 🟡 *Warning*
|
|
259
286
|
|
|
260
|
-
#### Unused Variable
|
|
287
|
+
#### Unused Variable 
|
|
261
288
|
Unused variables are never referenced and add unnecessary clutter. Remove them to keep Flows efficient and easy to maintain.
|
|
262
289
|
|
|
263
290
|
**Rule ID:** `unused-variable`
|
|
264
291
|
**Class Name:** _[UnusedVariable](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/UnusedVariable.ts)_
|
|
265
292
|
**Severity:** 🟡 *Warning*
|
|
266
293
|
|
|
267
|
-
#### Missing Auto Layout
|
|
294
|
+
#### Missing Auto Layout 
|
|
268
295
|
Auto-Layout automatically arranges and aligns Flow elements, keeping the canvas organized and easier to maintain. Enabling it saves time and improves readability.
|
|
269
296
|
|
|
270
297
|
**Rule ID:** `missing-auto-layout`
|
|
@@ -340,9 +367,9 @@ Available values for severity are `error`, `warning` and `note`. If no severity
|
|
|
340
367
|
}
|
|
341
368
|
```
|
|
342
369
|
|
|
343
|
-
####
|
|
370
|
+
#### Customize Rules
|
|
344
371
|
|
|
345
|
-
Some rules are configurable and allow overriding their default expressions
|
|
372
|
+
Some rules are configurable and allow overriding their default expressions, or setting a threshold as shown in the examples below.
|
|
346
373
|
|
|
347
374
|
```json
|
|
348
375
|
{
|
|
@@ -352,12 +379,15 @@ Some rules are configurable and allow overriding their default expressions. You
|
|
|
352
379
|
},
|
|
353
380
|
"invalid-naming-convention": {
|
|
354
381
|
"expression": "[A-Za-z0-9]" // regular expression
|
|
382
|
+
},
|
|
383
|
+
"excessive-cyclomatic-complexity": {
|
|
384
|
+
"threshold": 10 // threshold
|
|
355
385
|
}
|
|
356
386
|
}
|
|
357
387
|
}
|
|
358
388
|
```
|
|
359
389
|
|
|
360
|
-
#### Customize Messages
|
|
390
|
+
#### Customize Rule Messages
|
|
361
391
|
|
|
362
392
|
If not provided, `message` shows the standard rule summary and `messageUrl` links to the README; providing either overrides the default behavior.
|
|
363
393
|
|
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "ruleRegistry", {
|
|
|
11
11
|
const _ActionCallsInLoop = require("../rules/ActionCallsInLoop");
|
|
12
12
|
const _APIVersion = require("../rules/APIVersion");
|
|
13
13
|
const _AutoLayout = require("../rules/AutoLayout");
|
|
14
|
+
const _CognitiveComplexity = require("../rules/CognitiveComplexity");
|
|
14
15
|
const _CopyAPIName = require("../rules/CopyAPIName");
|
|
15
16
|
const _CyclomaticComplexity = require("../rules/CyclomaticComplexity");
|
|
16
17
|
const _DMLStatementInLoop = require("../rules/DMLStatementInLoop");
|
|
@@ -181,6 +182,7 @@ registry.register("action-call-in-loop", _ActionCallsInLoop.ActionCallsInLoop, "
|
|
|
181
182
|
registry.register("invalid-api-version", _APIVersion.APIVersion, "APIVersion");
|
|
182
183
|
registry.register("missing-auto-layout", _AutoLayout.AutoLayout, "AutoLayout");
|
|
183
184
|
registry.register("unclear-api-naming", _CopyAPIName.CopyAPIName, "CopyAPIName");
|
|
185
|
+
registry.register("cognitive-complexity", _CognitiveComplexity.CognitiveComplexity, "CognitiveComplexity");
|
|
184
186
|
registry.register("excessive-cyclomatic-complexity", _CyclomaticComplexity.CyclomaticComplexity, "CyclomaticComplexity");
|
|
185
187
|
registry.register("dml-in-loop", _DMLStatementInLoop.DMLStatementInLoop, "DMLStatementInLoop");
|
|
186
188
|
registry.register("duplicate-dml", _DuplicateDMLOperation.DuplicateDMLOperation, "DuplicateDMLOperation");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Flow, RuleResult } from "../internals/internals";
|
|
2
|
+
import { ConfigurableOption } from "../models/RuleInfo";
|
|
2
3
|
export interface IRuleDefinition {
|
|
3
4
|
ruleId: string;
|
|
4
5
|
category?: 'problem' | 'suggestion' | 'layout' | 'system';
|
|
@@ -10,6 +11,8 @@ export interface IRuleDefinition {
|
|
|
10
11
|
}>;
|
|
11
12
|
execute(flow: Flow, options?: object, suppressions?: string[]): RuleResult;
|
|
12
13
|
isConfigurable: boolean;
|
|
14
|
+
configurableOptions?: ConfigurableOption[];
|
|
15
|
+
isFixable: boolean;
|
|
13
16
|
label: string;
|
|
14
17
|
name: string;
|
|
15
18
|
severity?: string;
|
|
@@ -5,6 +5,7 @@ import { FlatViolation } from "../models/FlatViolation";
|
|
|
5
5
|
import { Flow } from "../models/Flow";
|
|
6
6
|
import { FlowAttribute } from "../models/FlowAttribute";
|
|
7
7
|
import { FlowElement } from "../models/FlowElement";
|
|
8
|
+
import { FlowGraph } from "../models/FlowGraph";
|
|
8
9
|
import { FlowNode } from "../models/FlowNode";
|
|
9
10
|
import { FlowResource } from "../models/FlowResource";
|
|
10
11
|
import { FlowType } from "../models/FlowType";
|
|
@@ -14,5 +15,5 @@ import { RuleCommon } from "../models/RuleCommon";
|
|
|
14
15
|
import { RuleResult } from "../models/RuleResult";
|
|
15
16
|
import { ScanResult } from "../models/ScanResult";
|
|
16
17
|
import { Violation } from "../models/Violation";
|
|
17
|
-
export { FlowAttribute, FlowElement, FlowNode, FlowType, FlowVariable, FlowResource, Flow, Compiler, ScanResult, RuleResult, Violation, RuleCommon, ParsedFlow, };
|
|
18
|
+
export { FlowAttribute, FlowElement, FlowGraph, FlowNode, FlowType, FlowVariable, FlowResource, Flow, Compiler, ScanResult, RuleResult, Violation, RuleCommon, ParsedFlow, };
|
|
18
19
|
export type { IRuleDefinition, IRulesConfig, FlatViolation };
|
|
@@ -21,6 +21,9 @@ _export(exports, {
|
|
|
21
21
|
get FlowElement () {
|
|
22
22
|
return _FlowElement.FlowElement;
|
|
23
23
|
},
|
|
24
|
+
get FlowGraph () {
|
|
25
|
+
return _FlowGraph.FlowGraph;
|
|
26
|
+
},
|
|
24
27
|
get FlowNode () {
|
|
25
28
|
return _FlowNode.FlowNode;
|
|
26
29
|
},
|
|
@@ -53,6 +56,7 @@ const _Compiler = require("../libs/Compiler");
|
|
|
53
56
|
const _Flow = require("../models/Flow");
|
|
54
57
|
const _FlowAttribute = require("../models/FlowAttribute");
|
|
55
58
|
const _FlowElement = require("../models/FlowElement");
|
|
59
|
+
const _FlowGraph = require("../models/FlowGraph");
|
|
56
60
|
const _FlowNode = require("../models/FlowNode");
|
|
57
61
|
const _FlowResource = require("../models/FlowResource");
|
|
58
62
|
const _FlowType = require("../models/FlowType");
|
package/main/libs/FixFlows.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as core from "../internals/internals";
|
|
2
|
-
export declare function fix(results: core.ScanResult[]): core.ScanResult[];
|
|
2
|
+
export declare function fix(results: core.ScanResult[], ruleOptions?: Map<string, unknown>): core.ScanResult[];
|
|
3
3
|
/**
|
|
4
4
|
* @deprecated Use fix() instead which modifies flows in place.
|
|
5
5
|
* Kept for backward compatibility.
|
package/main/libs/FixFlows.js
CHANGED
|
@@ -58,17 +58,24 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
58
58
|
}
|
|
59
59
|
return newObj;
|
|
60
60
|
}
|
|
61
|
-
function fix(results) {
|
|
61
|
+
function fix(results, ruleOptions) {
|
|
62
62
|
const newResults = [];
|
|
63
63
|
for (const result of results){
|
|
64
64
|
if (!result.ruleResults || result.ruleResults.length === 0) continue;
|
|
65
|
-
const fixables = result.ruleResults.filter((r)=>r.ruleName === "UnusedVariable" && r.occurs || r.ruleName === "UnconnectedElement" && r.occurs || r.ruleName === "AutoLayout" && r.occurs);
|
|
65
|
+
const fixables = result.ruleResults.filter((r)=>r.ruleName === "UnusedVariable" && r.occurs || r.ruleName === "UnconnectedElement" && r.occurs || r.ruleName === "AutoLayout" && r.occurs || r.ruleName === "APIVersion" && r.occurs);
|
|
66
66
|
if (fixables.length === 0) continue;
|
|
67
67
|
// Handle AutoLayout fix separately (modifies metadata, not elements)
|
|
68
68
|
const autoLayoutFix = fixables.find((r)=>r.ruleName === "AutoLayout");
|
|
69
69
|
if (autoLayoutFix) {
|
|
70
70
|
applyAutoLayoutFix(result.flow);
|
|
71
71
|
}
|
|
72
|
+
// Handle APIVersion fix (modifies apiVersion attribute)
|
|
73
|
+
const apiVersionFix = fixables.find((r)=>r.ruleName === "APIVersion");
|
|
74
|
+
if (apiVersionFix) {
|
|
75
|
+
var _ruleOptions_get;
|
|
76
|
+
const options = (_ruleOptions_get = ruleOptions === null || ruleOptions === void 0 ? void 0 : ruleOptions.get("invalid-api-version")) !== null && _ruleOptions_get !== void 0 ? _ruleOptions_get : ruleOptions === null || ruleOptions === void 0 ? void 0 : ruleOptions.get("APIVersion");
|
|
77
|
+
applyAPIVersionFix(result.flow, options);
|
|
78
|
+
}
|
|
72
79
|
// Handle element-based fixes (UnusedVariable, UnconnectedElement)
|
|
73
80
|
// These modify xmldata in place to preserve element order and formatting
|
|
74
81
|
const elementFixables = fixables.filter((r)=>r.ruleName !== "AutoLayout");
|
|
@@ -107,6 +114,39 @@ function applyAutoLayoutFix(flow) {
|
|
|
107
114
|
// Update the flow's processMetadataValues property
|
|
108
115
|
flow.processMetadataValues = flow.xmldata.processMetadataValues;
|
|
109
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Parse an API version expression and return the target version for auto-fix.
|
|
119
|
+
* Fixable expressions: >= N, === N, > N
|
|
120
|
+
* Returns undefined for unfixable expressions (<, <=, !==)
|
|
121
|
+
*/ function parseAPIVersionExpression(expression) {
|
|
122
|
+
if (!expression) {
|
|
123
|
+
// Default behavior: >= 50
|
|
124
|
+
return 50;
|
|
125
|
+
}
|
|
126
|
+
const match = expression.match(/^\s*(>=|<=|>|<|===|!==)\s*(\d+)\s*$/);
|
|
127
|
+
if (!match) return undefined;
|
|
128
|
+
const [, operator, versionStr] = match;
|
|
129
|
+
const version = parseInt(versionStr, 10);
|
|
130
|
+
switch(operator){
|
|
131
|
+
case '>=':
|
|
132
|
+
case '===':
|
|
133
|
+
return version;
|
|
134
|
+
case '>':
|
|
135
|
+
return version + 1;
|
|
136
|
+
// These don't have a clear target version
|
|
137
|
+
case '<':
|
|
138
|
+
case '<=':
|
|
139
|
+
case '!==':
|
|
140
|
+
default:
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function applyAPIVersionFix(flow, options) {
|
|
145
|
+
if (!flow.xmldata) return;
|
|
146
|
+
const targetVersion = parseAPIVersionExpression(options === null || options === void 0 ? void 0 : options.expression);
|
|
147
|
+
if (targetVersion === undefined) return;
|
|
148
|
+
flow.xmldata.apiVersion = targetVersion.toString();
|
|
149
|
+
}
|
|
110
150
|
/**
|
|
111
151
|
* Apply element-based fixes (UnusedVariable, UnconnectedElement) by modifying xmldata in place.
|
|
112
152
|
* This preserves element order and formatting from the original file.
|
package/main/libs/ScanFlows.js
CHANGED
|
@@ -116,5 +116,17 @@ function ScanFlows(flows, ruleOptions) {
|
|
|
116
116
|
});
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
+
// Apply threshold filtering if specified
|
|
120
|
+
const threshold = ruleOptions === null || ruleOptions === void 0 ? void 0 : ruleOptions.threshold;
|
|
121
|
+
if (threshold && threshold !== 'never') {
|
|
122
|
+
for (const scanResult of flowResults){
|
|
123
|
+
scanResult.ruleResults = scanResult.ruleResults.filter((ruleResult)=>{
|
|
124
|
+
// Get effective severity (config override or rule default)
|
|
125
|
+
const config = getRuleConfigByIdOrName(ruleResult.ruleDefinition, ruleOptions === null || ruleOptions === void 0 ? void 0 : ruleOptions.rules);
|
|
126
|
+
const severity = (config === null || config === void 0 ? void 0 : config.severity) || ruleResult.severity || 'warning';
|
|
127
|
+
return (0, _IRulesConfig.meetsThreshold)(severity, threshold);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
119
131
|
return flowResults;
|
|
120
132
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RuleInfo } from "./RuleInfo";
|
|
1
|
+
import { RuleInfo, ConfigurableOption } from "./RuleInfo";
|
|
2
2
|
import * as core from "../internals/internals";
|
|
3
3
|
export declare abstract class RuleCommon {
|
|
4
4
|
category?: 'problem' | 'suggestion' | 'layout' | 'system';
|
|
@@ -9,6 +9,8 @@ export declare abstract class RuleCommon {
|
|
|
9
9
|
path: string;
|
|
10
10
|
}>;
|
|
11
11
|
isConfigurable: boolean;
|
|
12
|
+
configurableOptions?: ConfigurableOption[];
|
|
13
|
+
isFixable: boolean;
|
|
12
14
|
label: string;
|
|
13
15
|
name: string;
|
|
14
16
|
severity?: string;
|
|
@@ -132,6 +132,8 @@ let RuleCommon = class RuleCommon {
|
|
|
132
132
|
_define_property(this, "summary", void 0);
|
|
133
133
|
_define_property(this, "docRefs", []);
|
|
134
134
|
_define_property(this, "isConfigurable", void 0);
|
|
135
|
+
_define_property(this, "configurableOptions", void 0);
|
|
136
|
+
_define_property(this, "isFixable", void 0);
|
|
135
137
|
_define_property(this, "label", void 0);
|
|
136
138
|
_define_property(this, "name", void 0);
|
|
137
139
|
_define_property(this, "severity", void 0);
|
|
@@ -147,13 +149,10 @@ let RuleCommon = class RuleCommon {
|
|
|
147
149
|
this.summary = info.summary;
|
|
148
150
|
this.uri = `https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner/tree/main/src/main/rules/${info.name}.ts`;
|
|
149
151
|
this.docRefs = info.docRefs;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
} else {
|
|
155
|
-
this.isConfigurable = false;
|
|
156
|
-
}
|
|
152
|
+
this.configurableOptions = info.configurableOptions;
|
|
153
|
+
this.isConfigurable = !!(info.configurableOptions && info.configurableOptions.length > 0);
|
|
154
|
+
var _info_isFixable;
|
|
155
|
+
this.isFixable = (_info_isFixable = info.isFixable) !== null && _info_isFixable !== void 0 ? _info_isFixable : false;
|
|
157
156
|
var _optional_severity;
|
|
158
157
|
this.severity = (_optional_severity = optional === null || optional === void 0 ? void 0 : optional.severity) !== null && _optional_severity !== void 0 ? _optional_severity : "warning";
|
|
159
158
|
}
|
|
@@ -3,6 +3,15 @@ export type RuleDefinitionExpression = {
|
|
|
3
3
|
expression?: unknown;
|
|
4
4
|
};
|
|
5
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* Describes a configurable option for a rule.
|
|
8
|
+
*/
|
|
9
|
+
export interface ConfigurableOption {
|
|
10
|
+
name: string;
|
|
11
|
+
type: 'number' | 'string' | 'boolean' | 'expression';
|
|
12
|
+
description: string;
|
|
13
|
+
defaultValue?: unknown;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* Represents a rule metadata; this contains properties to describe the rule
|
|
8
17
|
*/
|
|
@@ -45,4 +54,13 @@ export declare class RuleInfo {
|
|
|
45
54
|
* Use defined types in @see FlowType
|
|
46
55
|
*/
|
|
47
56
|
supportedTypes: string[];
|
|
57
|
+
/**
|
|
58
|
+
* Configurable options for this rule.
|
|
59
|
+
* Used for documentation generation.
|
|
60
|
+
*/
|
|
61
|
+
configurableOptions?: ConfigurableOption[];
|
|
62
|
+
/**
|
|
63
|
+
* Whether this rule supports auto-fix.
|
|
64
|
+
*/
|
|
65
|
+
isFixable?: boolean;
|
|
48
66
|
}
|
package/main/models/RuleInfo.js
CHANGED
|
@@ -50,5 +50,12 @@ let RuleInfo = class RuleInfo {
|
|
|
50
50
|
* The types supported by this rule (e.g., Flow, Process).
|
|
51
51
|
* Use defined types in @see FlowType
|
|
52
52
|
*/ _define_property(this, "supportedTypes", void 0);
|
|
53
|
+
/**
|
|
54
|
+
* Configurable options for this rule.
|
|
55
|
+
* Used for documentation generation.
|
|
56
|
+
*/ _define_property(this, "configurableOptions", void 0);
|
|
57
|
+
/**
|
|
58
|
+
* Whether this rule supports auto-fix.
|
|
59
|
+
*/ _define_property(this, "isFixable", void 0);
|
|
53
60
|
}
|
|
54
61
|
};
|
package/main/rules/APIVersion.js
CHANGED
|
@@ -120,7 +120,16 @@ let APIVersion = class APIVersion extends _RuleCommon.RuleCommon {
|
|
|
120
120
|
description: "Flows running on outdated API versions may behave inconsistently when newer platform features or components are used. From API version 50.0 onward, the API Version attribute explicitly controls Flow runtime behavior. Keeping Flows aligned with a supported API version helps prevent compatibility issues and ensures predictable execution.",
|
|
121
121
|
summary: "Outdated API versions risk compatibility issues",
|
|
122
122
|
supportedTypes: _internals.FlowType.allTypes(),
|
|
123
|
-
docRefs: []
|
|
123
|
+
docRefs: [],
|
|
124
|
+
configurableOptions: [
|
|
125
|
+
{
|
|
126
|
+
name: "expression",
|
|
127
|
+
type: "expression",
|
|
128
|
+
description: "Comparison expression for API version (e.g., `>= 58`, `< 50`, `=== 60`)",
|
|
129
|
+
defaultValue: ">= 50"
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
isFixable: true
|
|
124
133
|
});
|
|
125
134
|
}
|
|
126
135
|
};
|
package/main/rules/AutoLayout.js
CHANGED
|
@@ -72,7 +72,8 @@ let AutoLayout = class AutoLayout extends _RuleCommon.RuleCommon {
|
|
|
72
72
|
description: "Auto-Layout automatically arranges and aligns Flow elements, keeping the canvas organized and easier to maintain. Enabling it saves time and improves readability.",
|
|
73
73
|
summary: "Auto-Layout improves canvas organization and readability",
|
|
74
74
|
supportedTypes: _internals.FlowType.allTypes(),
|
|
75
|
-
docRefs: []
|
|
75
|
+
docRefs: [],
|
|
76
|
+
isFixable: true
|
|
76
77
|
}, {
|
|
77
78
|
severity: "note"
|
|
78
79
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as core from "../internals/internals";
|
|
2
|
+
import { RuleCommon } from "../models/RuleCommon";
|
|
3
|
+
import { IRuleDefinition } from "../interfaces/IRuleDefinition";
|
|
4
|
+
/**
|
|
5
|
+
* Cognitive Complexity Rule
|
|
6
|
+
*
|
|
7
|
+
* Unlike cyclomatic complexity which counts paths, cognitive complexity
|
|
8
|
+
* measures how hard a flow is to understand by penalizing:
|
|
9
|
+
* - Control flow structures (loops, decisions) +1 each
|
|
10
|
+
* - Nesting depth: each level of nesting adds extra penalty
|
|
11
|
+
*
|
|
12
|
+
* A decision inside a loop is harder to understand than a flat sequence.
|
|
13
|
+
*/
|
|
14
|
+
export declare class CognitiveComplexity extends RuleCommon implements IRuleDefinition {
|
|
15
|
+
private defaultThreshold;
|
|
16
|
+
constructor();
|
|
17
|
+
protected check(flow: core.Flow, options: {
|
|
18
|
+
threshold?: number;
|
|
19
|
+
} | undefined): core.Violation[];
|
|
20
|
+
/**
|
|
21
|
+
* Calculate cognitive complexity for a flow.
|
|
22
|
+
*
|
|
23
|
+
* Algorithm:
|
|
24
|
+
* 1. Find all loops and decisions
|
|
25
|
+
* 2. Calculate nesting depth for each (how many loops/decisions contain it)
|
|
26
|
+
* 3. Add 1 + nesting_depth for each control structure
|
|
27
|
+
*/
|
|
28
|
+
private calculateCognitiveComplexity;
|
|
29
|
+
/**
|
|
30
|
+
* Count how many parent loops contain this loop
|
|
31
|
+
*/
|
|
32
|
+
private countParentLoops;
|
|
33
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "CognitiveComplexity", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return CognitiveComplexity;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _internals = /*#__PURE__*/ _interop_require_wildcard(require("../internals/internals"));
|
|
12
|
+
const _RuleCommon = require("../models/RuleCommon");
|
|
13
|
+
function _define_property(obj, key, value) {
|
|
14
|
+
if (key in obj) {
|
|
15
|
+
Object.defineProperty(obj, key, {
|
|
16
|
+
value: value,
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true
|
|
20
|
+
});
|
|
21
|
+
} else {
|
|
22
|
+
obj[key] = value;
|
|
23
|
+
}
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
function _getRequireWildcardCache(nodeInterop) {
|
|
27
|
+
if (typeof WeakMap !== "function") return null;
|
|
28
|
+
var cacheBabelInterop = new WeakMap();
|
|
29
|
+
var cacheNodeInterop = new WeakMap();
|
|
30
|
+
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
31
|
+
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
32
|
+
})(nodeInterop);
|
|
33
|
+
}
|
|
34
|
+
function _interop_require_wildcard(obj, nodeInterop) {
|
|
35
|
+
if (!nodeInterop && obj && obj.__esModule) {
|
|
36
|
+
return obj;
|
|
37
|
+
}
|
|
38
|
+
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
39
|
+
return {
|
|
40
|
+
default: obj
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
var cache = _getRequireWildcardCache(nodeInterop);
|
|
44
|
+
if (cache && cache.has(obj)) {
|
|
45
|
+
return cache.get(obj);
|
|
46
|
+
}
|
|
47
|
+
var newObj = {
|
|
48
|
+
__proto__: null
|
|
49
|
+
};
|
|
50
|
+
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
51
|
+
for(var key in obj){
|
|
52
|
+
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
53
|
+
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
54
|
+
if (desc && (desc.get || desc.set)) {
|
|
55
|
+
Object.defineProperty(newObj, key, desc);
|
|
56
|
+
} else {
|
|
57
|
+
newObj[key] = obj[key];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
newObj.default = obj;
|
|
62
|
+
if (cache) {
|
|
63
|
+
cache.set(obj, newObj);
|
|
64
|
+
}
|
|
65
|
+
return newObj;
|
|
66
|
+
}
|
|
67
|
+
let CognitiveComplexity = class CognitiveComplexity extends _RuleCommon.RuleCommon {
|
|
68
|
+
check(flow, options) {
|
|
69
|
+
var _options_threshold;
|
|
70
|
+
const threshold = (_options_threshold = options === null || options === void 0 ? void 0 : options.threshold) !== null && _options_threshold !== void 0 ? _options_threshold : this.defaultThreshold;
|
|
71
|
+
const complexity = this.calculateCognitiveComplexity(flow);
|
|
72
|
+
if (complexity > threshold) {
|
|
73
|
+
return [
|
|
74
|
+
new _internals.Violation(new _internals.FlowAttribute(`${complexity}`, "CognitiveComplexity", `>${threshold}`))
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Calculate cognitive complexity for a flow.
|
|
81
|
+
*
|
|
82
|
+
* Algorithm:
|
|
83
|
+
* 1. Find all loops and decisions
|
|
84
|
+
* 2. Calculate nesting depth for each (how many loops/decisions contain it)
|
|
85
|
+
* 3. Add 1 + nesting_depth for each control structure
|
|
86
|
+
*/ calculateCognitiveComplexity(flow) {
|
|
87
|
+
let complexity = 0;
|
|
88
|
+
const graph = flow.graph;
|
|
89
|
+
// Get all loops and decisions
|
|
90
|
+
const loops = flow.elements.filter((e)=>e.subtype === "loops");
|
|
91
|
+
const decisions = flow.elements.filter((e)=>e.subtype === "decisions");
|
|
92
|
+
// Build a map of which elements are contained within which control structures
|
|
93
|
+
const nestingDepth = new Map();
|
|
94
|
+
// Calculate nesting depth for each element
|
|
95
|
+
for (const element of flow.elements){
|
|
96
|
+
if (!(element instanceof _internals.FlowNode)) continue;
|
|
97
|
+
let depth = 0;
|
|
98
|
+
// Check if inside any loop
|
|
99
|
+
if (graph.isInLoop(element.name)) {
|
|
100
|
+
depth++;
|
|
101
|
+
// Check for nested loops (loop inside loop)
|
|
102
|
+
const containingLoop = graph.getContainingLoop(element.name);
|
|
103
|
+
if (containingLoop && containingLoop !== element.name) {
|
|
104
|
+
// Check if the containing loop is itself inside another loop
|
|
105
|
+
depth += this.countParentLoops(containingLoop, graph, loops);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
nestingDepth.set(element.name, depth);
|
|
109
|
+
}
|
|
110
|
+
// Add complexity for each loop: 1 + nesting depth
|
|
111
|
+
for (const loop of loops){
|
|
112
|
+
var _nestingDepth_get;
|
|
113
|
+
const depth = (_nestingDepth_get = nestingDepth.get(loop.name)) !== null && _nestingDepth_get !== void 0 ? _nestingDepth_get : 0;
|
|
114
|
+
complexity += 1 + depth;
|
|
115
|
+
}
|
|
116
|
+
// Add complexity for each decision: 1 + nesting depth
|
|
117
|
+
// Also count additional branches beyond 2 (if-else is base, more adds complexity)
|
|
118
|
+
for (const decision of decisions){
|
|
119
|
+
var _decision_rules;
|
|
120
|
+
var _nestingDepth_get1;
|
|
121
|
+
const depth = (_nestingDepth_get1 = nestingDepth.get(decision.name)) !== null && _nestingDepth_get1 !== void 0 ? _nestingDepth_get1 : 0;
|
|
122
|
+
var _decision_rules_length;
|
|
123
|
+
const rulesCount = (_decision_rules_length = (_decision_rules = decision.rules) === null || _decision_rules === void 0 ? void 0 : _decision_rules.length) !== null && _decision_rules_length !== void 0 ? _decision_rules_length : 0;
|
|
124
|
+
// Base complexity: 1 + nesting depth
|
|
125
|
+
complexity += 1 + depth;
|
|
126
|
+
// Additional complexity for multiple branches (beyond binary decision)
|
|
127
|
+
// Each additional rule beyond the first adds complexity
|
|
128
|
+
if (rulesCount > 1) {
|
|
129
|
+
complexity += rulesCount - 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return complexity;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Count how many parent loops contain this loop
|
|
136
|
+
*/ countParentLoops(loopName, graph, allLoops) {
|
|
137
|
+
let count = 0;
|
|
138
|
+
for (const parentLoop of allLoops){
|
|
139
|
+
if (parentLoop.name === loopName) continue;
|
|
140
|
+
// Check if loopName is within this parent loop's body
|
|
141
|
+
const loopElements = graph.getLoopElements(parentLoop.name);
|
|
142
|
+
if (loopElements.has(loopName)) {
|
|
143
|
+
count++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return count;
|
|
147
|
+
}
|
|
148
|
+
constructor(){
|
|
149
|
+
super({
|
|
150
|
+
ruleId: "cognitive-complexity",
|
|
151
|
+
category: "suggestion",
|
|
152
|
+
name: "CognitiveComplexity",
|
|
153
|
+
label: "Cognitive Complexity",
|
|
154
|
+
description: "Flows with deeply nested loops and decisions are hard to understand. Unlike cyclomatic complexity which counts paths, cognitive complexity penalizes nesting depth. Consider extracting nested logic into subflows.",
|
|
155
|
+
summary: "Deeply nested logic harms readability",
|
|
156
|
+
supportedTypes: _internals.FlowType.backEndTypes,
|
|
157
|
+
docRefs: [
|
|
158
|
+
{
|
|
159
|
+
label: "Cognitive Complexity is a measure of how difficult code is to understand, as opposed to Cyclomatic Complexity which measures testability.",
|
|
160
|
+
path: "https://www.sonarsource.com/docs/CognitiveComplexity.pdf"
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
configurableOptions: [
|
|
164
|
+
{
|
|
165
|
+
name: "threshold",
|
|
166
|
+
type: "number",
|
|
167
|
+
description: "Maximum cognitive complexity score before triggering a violation",
|
|
168
|
+
defaultValue: 15
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}, {
|
|
172
|
+
severity: "note"
|
|
173
|
+
}), _define_property(this, "defaultThreshold", 15);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
@@ -67,7 +67,8 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
67
67
|
let CyclomaticComplexity = class CyclomaticComplexity extends _RuleCommon.RuleCommon {
|
|
68
68
|
check(flow, options) {
|
|
69
69
|
var _flow_elements, _flow_elements1;
|
|
70
|
-
|
|
70
|
+
var _options_threshold;
|
|
71
|
+
const threshold = (_options_threshold = options === null || options === void 0 ? void 0 : options.threshold) !== null && _options_threshold !== void 0 ? _options_threshold : this.defaultThreshold;
|
|
71
72
|
let cyclomaticComplexity = 1;
|
|
72
73
|
const flowDecisions = flow === null || flow === void 0 ? void 0 : (_flow_elements = flow.elements) === null || _flow_elements === void 0 ? void 0 : _flow_elements.filter((node)=>node.subtype === "decisions");
|
|
73
74
|
const flowLoops = flow === null || flow === void 0 ? void 0 : (_flow_elements1 = flow.elements) === null || _flow_elements1 === void 0 ? void 0 : _flow_elements1.filter((node)=>node.subtype === "loops");
|
|
@@ -99,6 +100,14 @@ let CyclomaticComplexity = class CyclomaticComplexity extends _RuleCommon.RuleCo
|
|
|
99
100
|
label: `Cyclomatic complexity is a software metric used to indicate the complexity of a program. It is a quantitative measure of the number of linearly independent paths through a program's source code.`,
|
|
100
101
|
path: "https://en.wikipedia.org/wiki/Cyclomatic_complexity"
|
|
101
102
|
}
|
|
103
|
+
],
|
|
104
|
+
configurableOptions: [
|
|
105
|
+
{
|
|
106
|
+
name: "threshold",
|
|
107
|
+
type: "number",
|
|
108
|
+
description: "Maximum cyclomatic complexity score before triggering a violation",
|
|
109
|
+
defaultValue: 25
|
|
110
|
+
}
|
|
102
111
|
]
|
|
103
112
|
}, {
|
|
104
113
|
severity: "note"
|
package/main/rules/FlowName.js
CHANGED
|
@@ -91,7 +91,15 @@ let FlowName = class FlowName extends _RuleCommon.RuleCommon {
|
|
|
91
91
|
],
|
|
92
92
|
label: "Flow Naming Convention",
|
|
93
93
|
name: "FlowName",
|
|
94
|
-
supportedTypes: _internals.FlowType.allTypes()
|
|
94
|
+
supportedTypes: _internals.FlowType.allTypes(),
|
|
95
|
+
configurableOptions: [
|
|
96
|
+
{
|
|
97
|
+
name: "expression",
|
|
98
|
+
type: "expression",
|
|
99
|
+
description: "Regex pattern for valid Flow names",
|
|
100
|
+
defaultValue: "[A-Za-z0-9]+_[A-Za-z0-9]+"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
95
103
|
}, {
|
|
96
104
|
severity: "error"
|
|
97
105
|
}), _define_property(this, "regexRule", new _regexscanner.NamingConvention());
|
|
@@ -80,7 +80,7 @@ let HardcodedId = class HardcodedId extends _RuleCommon.RuleCommon {
|
|
|
80
80
|
ruleId: "hardcoded-id",
|
|
81
81
|
name: "HardcodedId",
|
|
82
82
|
category: "problem",
|
|
83
|
-
label: "Hardcoded
|
|
83
|
+
label: "Hardcoded Id",
|
|
84
84
|
description: "Avoid hard-coding record IDs, as they are unique to a specific org and will not work in other environments. Instead, store IDs in variables—such as merge-field URL parameters or a **Get Records** element—to make the Flow portable, maintainable, and flexible.",
|
|
85
85
|
summary: "Hardcoded IDs break portability across environments",
|
|
86
86
|
supportedTypes: _internals.FlowType.allTypes(),
|
|
@@ -50,7 +50,7 @@ let HardcodedUrl = class HardcodedUrl extends _RuleCommon.RuleCommon {
|
|
|
50
50
|
path: "https://admin.salesforce.com/blog/2021/why-you-should-avoid-hard-coding-and-three-alternative-solutions"
|
|
51
51
|
}
|
|
52
52
|
],
|
|
53
|
-
label: "Hardcoded
|
|
53
|
+
label: "Hardcoded Url",
|
|
54
54
|
name: "HardcodedUrl",
|
|
55
55
|
supportedTypes: _internals.FlowType.allTypes()
|
|
56
56
|
}, {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flow-scanner/lightning-flow-scanner-core",
|
|
3
3
|
"description": "A lightweight engine for Flow metadata in Node.js, and browser environments. Assess and enhance Salesforce Flow automations for best practices, security, governor limits, and performance issues.",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.19.0",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "packages/core"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@flow-scanner/regex-scanner": "1.
|
|
23
|
+
"@flow-scanner/regex-scanner": "1.1.0",
|
|
24
24
|
"fast-xml-parser": "^5.3.0"
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://flow-scanner.github.io/lightning-flow-scanner/",
|