@flow-scanner/lightning-flow-scanner-core 6.11.5 → 6.13.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 +12 -13
- package/main/libs/BuildFlow.js +5 -2
- package/main/libs/GetRuleDefinitions.js +1 -1
- package/main/models/Flow.d.ts +1 -1
- package/main/models/Flow.js +12 -11
- package/main/rules/APIVersion.js +13 -6
- package/main/rules/GetRecordAllFields.js +3 -1
- package/main/rules/MissingNullHandler.js +9 -2
- package/package.json +1 -1
- package/main/libs/ConvertFlowNodes.d.ts +0 -1
- package/main/libs/ConvertFlowNodes.js +0 -14
- /package/main/{store → config}/RuleRegistry.d.ts +0 -0
- /package/main/{store → config}/RuleRegistry.js +0 -0
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
> Want to code a new rule? → See [How to Write a Rule](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/docs/write-a-rule.md)
|
|
54
54
|
|
|
55
|
-
### Action
|
|
55
|
+
### Action Call In A Loop
|
|
56
56
|
_[ActionCallsInLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/ActionCallsInLoop.ts)_ – To prevent exceeding Apex governor limits, it is advisable to consolidate and bulkify your apex calls, utilizing a single action call containing a collection variable at the end of the loop.
|
|
57
57
|
**Rule ID:** `action-call-in-loop`
|
|
58
58
|
**Severity:** 🔴 *Error*
|
|
@@ -193,9 +193,8 @@ _[UnusedVariable](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/ma
|
|
|
193
193
|
|
|
194
194
|
It is recommend to configure and define:
|
|
195
195
|
|
|
196
|
-
- The rules to be executed.
|
|
197
196
|
- The severity of violating any specific rule.
|
|
198
|
-
-
|
|
197
|
+
- Expressions used for rules, such as REGEX patterns and comparison operators.
|
|
199
198
|
- Any known exceptions that should be ignored during scanning.
|
|
200
199
|
|
|
201
200
|
```json
|
|
@@ -216,7 +215,7 @@ By default, all default rules are executed. You can customize individual rules a
|
|
|
216
215
|
```json
|
|
217
216
|
{
|
|
218
217
|
"rules": {
|
|
219
|
-
"<
|
|
218
|
+
"<RuleId>": {
|
|
220
219
|
"severity": "<Severity>", // Override severity level
|
|
221
220
|
"expression": "<Expression>", // Override rule expression
|
|
222
221
|
"enabled": "false" // Disable this rule
|
|
@@ -232,10 +231,10 @@ When the severity is not provided it will be `warning` by default. Other availab
|
|
|
232
231
|
```json
|
|
233
232
|
{
|
|
234
233
|
"rules": {
|
|
235
|
-
"
|
|
234
|
+
"missing-flow-description": {
|
|
236
235
|
"severity": "error"
|
|
237
236
|
},
|
|
238
|
-
"
|
|
237
|
+
"unused-variable": {
|
|
239
238
|
"severity": "note"
|
|
240
239
|
}
|
|
241
240
|
}
|
|
@@ -249,10 +248,10 @@ Some rules have an expression to configure, such as the expression, that will ov
|
|
|
249
248
|
```json
|
|
250
249
|
{
|
|
251
250
|
"rules": {
|
|
252
|
-
"
|
|
251
|
+
"invalid-api-version": {
|
|
253
252
|
"expression": "===58" // comparison operator
|
|
254
253
|
},
|
|
255
|
-
"
|
|
254
|
+
"invalid-naming-convention": {
|
|
256
255
|
"expression": "[A-Za-z0-9]" // regular expression
|
|
257
256
|
}
|
|
258
257
|
}
|
|
@@ -267,7 +266,7 @@ Defining exceptions allows you to exclude specific scenarios from rule enforceme
|
|
|
267
266
|
{
|
|
268
267
|
"exceptions": {
|
|
269
268
|
"<FlowName>": {
|
|
270
|
-
"<
|
|
269
|
+
"<RuleId>": [
|
|
271
270
|
"<ResultName>", // Suppress a result
|
|
272
271
|
"*", // Wildcard to suppress all results
|
|
273
272
|
...
|
|
@@ -284,8 +283,8 @@ _Example_
|
|
|
284
283
|
{
|
|
285
284
|
"exceptions": {
|
|
286
285
|
"MyFlow": {
|
|
287
|
-
"
|
|
288
|
-
"
|
|
286
|
+
"hardcoded-id": ["Old_Lookup_1"],
|
|
287
|
+
"missing-null-handler": ["*"]
|
|
289
288
|
}
|
|
290
289
|
}
|
|
291
290
|
}
|
|
@@ -321,7 +320,7 @@ By default, Lightning Flow Scanner runs **all** default rules and merges any cus
|
|
|
321
320
|
| **[Salesforce CLI Plugin](https://www.npmjs.com/package/lightning-flow-scanner)** | Local development, scratch orgs, CI/CD | `sf plugins install lightning-flow-scanner` |
|
|
322
321
|
| **[VS Code Extension](https://open-vsx.org/extension/ForceConfigControl/lightning-flow-scanner-vsx)** | Real-time scanning inside VS Code | `code --install-extension ForceConfigControl.lightning-flow-scanner-vsx` |
|
|
323
322
|
| **[Salesforce App (Managed Package)](https://github.com/Flow-Scanner/lightning-flow-scanner-app)** | Run scans directly inside a Salesforce org | `sf package install --package 04tgK0000008CLlQAM` |
|
|
324
|
-
| **[GitHub Action](https://github.com/marketplace/actions/lightning-flow-scan)** | Native PR checks | `uses: Flow-Scanner/lightning-flow-scanner@
|
|
323
|
+
| **[GitHub Action](https://github.com/marketplace/actions/lightning-flow-scan)** | Native PR checks | `uses: Flow-Scanner/lightning-flow-scanner@main` |
|
|
325
324
|
| **[Core Library](https://www.npmjs.com/package/@flow-scanner/lightning-flow-scanner-core)** (Node.js + Browser) | Custom tools, scripts, extensions, web apps | `npm install -g @flow-scanner/lightning-flow-scanner-core` |
|
|
326
325
|
|
|
327
326
|
**Privacy:** Zero user data collected. All processing is client-side. → See our [Security Policy](https://github.com/Flow-Scanner/lightning-flow-scanner?tab=security-ov-file).
|
|
@@ -369,7 +368,7 @@ Add a GitHub workflow file `.github/workflows/scan-flows.yml` to detect issues d
|
|
|
369
368
|
```yaml
|
|
370
369
|
- name: Lightning Flow Scan
|
|
371
370
|
id: flowscanner
|
|
372
|
-
uses: Flow-Scanner/lightning-flow-scanner@
|
|
371
|
+
uses: Flow-Scanner/lightning-flow-scanner@main
|
|
373
372
|
|
|
374
373
|
- name: Upload SARIF to Code Scanning
|
|
375
374
|
uses: github/codeql-action/upload-sarif@v3
|
package/main/libs/BuildFlow.js
CHANGED
|
@@ -8,13 +8,16 @@ Object.defineProperty(exports, "BuildFlow", {
|
|
|
8
8
|
return BuildFlow;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const _ConvertFlowNodes = require("./ConvertFlowNodes");
|
|
12
11
|
function BuildFlow(nodesToMerge) {
|
|
13
12
|
let res = {};
|
|
14
13
|
for (const nodeToMerge of nodesToMerge){
|
|
15
14
|
const subtype = nodeToMerge.subtype;
|
|
16
15
|
const nodesOfType = nodesToMerge.filter((node)=>subtype === node.subtype);
|
|
17
|
-
res =
|
|
16
|
+
res = convertFlowNodes(res, nodesOfType, subtype);
|
|
18
17
|
}
|
|
19
18
|
return res;
|
|
20
19
|
}
|
|
20
|
+
function convertFlowNodes(obj, nodes, key) {
|
|
21
|
+
obj[key] = nodes.map((node)=>node.element);
|
|
22
|
+
return obj;
|
|
23
|
+
}
|
|
@@ -16,7 +16,7 @@ _export(exports, {
|
|
|
16
16
|
return getRules;
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
|
-
const _RuleRegistry = require("../
|
|
19
|
+
const _RuleRegistry = require("../config/RuleRegistry");
|
|
20
20
|
function GetRuleDefinitions(ruleConfig, options) {
|
|
21
21
|
const includeBeta = (options === null || options === void 0 ? void 0 : options.betaMode) === true || (options === null || options === void 0 ? void 0 : options.betamode) === true;
|
|
22
22
|
const rulesMode = (options === null || options === void 0 ? void 0 : options.ruleMode) || "merged";
|
package/main/models/Flow.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare class Flow {
|
|
|
5
5
|
/**
|
|
6
6
|
* Metadata Tags of Salesforce Flow Attributes
|
|
7
7
|
*/
|
|
8
|
-
static readonly ATTRIBUTE_TAGS: readonly ["
|
|
8
|
+
static readonly ATTRIBUTE_TAGS: readonly ["apiVersion", "areMetricsLoggedToDataCloud", "description", "environments", "fullName", "interviewLabel", "isAdditionalPermissionRequiredToRun", "isTemplate", "label", "migratedFromWorkflowRuleName", "processMetadataValues", "processType", "runInMode", "segment", "startElementReference", "status", "timeZoneSidKey", "triggerOrder"];
|
|
9
9
|
/**
|
|
10
10
|
* Metadata Tags of Salesforce Flow Nodes
|
|
11
11
|
*/
|
package/main/models/Flow.js
CHANGED
|
@@ -331,23 +331,24 @@ let Flow = class Flow {
|
|
|
331
331
|
/**
|
|
332
332
|
* Metadata Tags of Salesforce Flow Attributes
|
|
333
333
|
*/ _define_property(Flow, "ATTRIBUTE_TAGS", [
|
|
334
|
-
"description",
|
|
335
334
|
"apiVersion",
|
|
336
|
-
"
|
|
337
|
-
"
|
|
335
|
+
"areMetricsLoggedToDataCloud",
|
|
336
|
+
"description",
|
|
337
|
+
"environments",
|
|
338
|
+
"fullName",
|
|
338
339
|
"interviewLabel",
|
|
340
|
+
"isAdditionalPermissionRequiredToRun",
|
|
341
|
+
"isTemplate",
|
|
339
342
|
"label",
|
|
340
|
-
"
|
|
343
|
+
"migratedFromWorkflowRuleName",
|
|
344
|
+
"processMetadataValues",
|
|
345
|
+
"processType",
|
|
341
346
|
"runInMode",
|
|
347
|
+
"segment",
|
|
342
348
|
"startElementReference",
|
|
343
|
-
"
|
|
344
|
-
"fullName",
|
|
349
|
+
"status",
|
|
345
350
|
"timeZoneSidKey",
|
|
346
|
-
"
|
|
347
|
-
"migratedFromWorkflowRuleName",
|
|
348
|
-
"triggerOrder",
|
|
349
|
-
"environments",
|
|
350
|
-
"segment"
|
|
351
|
+
"triggerOrder"
|
|
351
352
|
]);
|
|
352
353
|
/**
|
|
353
354
|
* Metadata Tags of Salesforce Flow Nodes
|
package/main/rules/APIVersion.js
CHANGED
|
@@ -57,14 +57,14 @@ let APIVersion = class APIVersion extends _RuleCommon.RuleCommon {
|
|
|
57
57
|
if (flow.xmldata.apiVersion) {
|
|
58
58
|
flowAPIVersionNumber = +flow.xmldata.apiVersion;
|
|
59
59
|
}
|
|
60
|
-
// No API version
|
|
61
|
-
if (!flowAPIVersionNumber) {
|
|
62
|
-
return [
|
|
63
|
-
new _internals.Violation(new _internals.FlowAttribute("API Version <49", "apiVersion", "<49"))
|
|
64
|
-
];
|
|
65
|
-
}
|
|
66
60
|
// Custom logic
|
|
67
61
|
if (options === null || options === void 0 ? void 0 : options.expression) {
|
|
62
|
+
// No API version with custom expression
|
|
63
|
+
if (!flowAPIVersionNumber) {
|
|
64
|
+
return [
|
|
65
|
+
new _internals.Violation(new _internals.FlowAttribute("apiVersion<50", "apiVersion", "<50"))
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
68
|
// Match something like: >= 58
|
|
69
69
|
const match = options.expression.match(/^\s*(>=|<=|>|<|===|!==)\s*(\d+)\s*$/);
|
|
70
70
|
if (!match) {
|
|
@@ -101,6 +101,13 @@ let APIVersion = class APIVersion extends _RuleCommon.RuleCommon {
|
|
|
101
101
|
new _internals.Violation(new _internals.FlowAttribute(`${flowAPIVersionNumber}`, "apiVersion", options.expression))
|
|
102
102
|
];
|
|
103
103
|
}
|
|
104
|
+
} else {
|
|
105
|
+
// Default: no API version OR version below 50
|
|
106
|
+
if (!flowAPIVersionNumber || flowAPIVersionNumber < 50) {
|
|
107
|
+
return [
|
|
108
|
+
new _internals.Violation(new _internals.FlowAttribute(flowAPIVersionNumber ? `${flowAPIVersionNumber}` : "apiVersion<50", "apiVersion", "<50"))
|
|
109
|
+
];
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
return [];
|
|
106
113
|
}
|
|
@@ -59,7 +59,9 @@ let GetRecordAllFields = class GetRecordAllFields extends _RuleCommon.RuleCommon
|
|
|
59
59
|
const violations = lookupNodes.filter((node)=>{
|
|
60
60
|
const el = node.element;
|
|
61
61
|
const storeAllFields = typeof el === "object" && "storeOutputAutomatically" in el && el.storeOutputAutomatically;
|
|
62
|
-
|
|
62
|
+
// Handle both single field (string) and multiple fields (array)
|
|
63
|
+
const queriedFields = el.queriedFields;
|
|
64
|
+
const hasQueriedFields = queriedFields && (Array.isArray(queriedFields) && queriedFields.length > 0 || typeof queriedFields === "string");
|
|
63
65
|
return storeAllFields && !hasQueriedFields;
|
|
64
66
|
}).map((node)=>new _internals.Violation(node));
|
|
65
67
|
return violations;
|
|
@@ -64,9 +64,12 @@ let MissingNullHandler = class MissingNullHandler extends _RuleCommon.RuleCommon
|
|
|
64
64
|
if (suppressions.has(getElement.name)) continue;
|
|
65
65
|
const elementName = getElement.name;
|
|
66
66
|
const assignNulls = String(getElement.element["assignNullValuesIfNoRecordsFound"]).toLowerCase() === "true";
|
|
67
|
-
if (!assignNulls) continue;
|
|
68
67
|
const hasFaultConnector = !!getElement.element["faultConnector"] || ((_getElement_connectors = getElement.connectors) === null || _getElement_connectors === void 0 ? void 0 : _getElement_connectors.some((c)=>c.type === "faultConnector"));
|
|
69
|
-
if
|
|
68
|
+
// Only skip if NOT assigning nulls AND has fault connector
|
|
69
|
+
// (because fault will catch the "no records" error)
|
|
70
|
+
if (!assignNulls && hasFaultConnector) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
70
73
|
const resultReferences = [];
|
|
71
74
|
if (getElement.element["storeOutputAutomatically"]) {
|
|
72
75
|
resultReferences.push(elementName);
|
|
@@ -84,6 +87,10 @@ let MissingNullHandler = class MissingNullHandler extends _RuleCommon.RuleCommon
|
|
|
84
87
|
return resultReferences.some((ref)=>json.includes(`"${ref}"`) || json.includes(`"${ref}.`));
|
|
85
88
|
});
|
|
86
89
|
if (!resultIsUsed) continue;
|
|
90
|
+
// If assignNullValuesIfNoRecordsFound is TRUE, we need a null check decision
|
|
91
|
+
if (!assignNulls) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
87
94
|
let nullCheckFound = false;
|
|
88
95
|
for (const decision of decisionElements){
|
|
89
96
|
let rules = decision.element["rules"];
|
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.13.0",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function convertFlowNodes(obj: any, nodes: any, key: any): any;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "convertFlowNodes", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return convertFlowNodes;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
function convertFlowNodes(obj, nodes, key) {
|
|
12
|
-
obj[key] = nodes.map((node)=>node.element);
|
|
13
|
-
return obj;
|
|
14
|
-
}
|
|
File without changes
|
|
File without changes
|