@flow-scanner/lightning-flow-scanner-core 6.17.3 → 6.18.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 CHANGED
@@ -39,7 +39,7 @@
39
39
  - [Configure Rules](#configure-rules)
40
40
  - [Define Exceptions](#define-exceptions)
41
41
  - [Exclude Flows](#exclude-flows)
42
- - [Scan Modes](#scan-modes)
42
+ - [Scan Options](#scan-options)
43
43
  - **[Installation](#installation)**
44
44
  - [Distributions](#distributions)
45
45
  - [CICD Templates](#cicd-templates)
@@ -55,221 +55,225 @@
55
55
 
56
56
  > Want to help improve this project? See our [Contributing Guidelines](https://github.com/Flow-Scanner/lightning-flow-scanner?tab=contributing-ov-file)
57
57
 
58
- <!-- START GENERATED_RULES -->
59
-
60
- ---
61
-
62
- ### Problems
63
-
64
- These rules detect anti-patterns and unsafe practices in your Flows that could break functionality, compromise security, or cause deployment failures.
65
-
66
- #### DML Statement In A Loop
67
- Executing DML operations (insert, update, delete) inside a loop is a high-risk anti-pattern that frequently causes governor limit exceptions. All database operations should be collected and executed once, outside the loop.
68
-
69
- **Rule ID:** `dml-in-loop`
70
- **Class Name:** _[DMLStatementInLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/DMLStatementInLoop.ts)_
71
- **Severity:** 🔴 *Error*
72
-
73
- #### Hardcoded Salesforce Id
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
-
76
- **Rule ID:** `hardcoded-id`
77
- **Class Name:** _[HardcodedId](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/HardcodedId.ts)_
78
- **Severity:** 🔴 *Error*
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
- #### Hardcoded Secret ![Beta](https://img.shields.io/badge/status-beta-yellow)
88
- 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
-
90
- **Rule ID:** `hardcoded-secret`
91
- **Class Name:** _[HardcodedSecret](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/HardcodedSecret.ts)_
92
- **Severity:** 🔴 *Error*
93
-
94
- #### Process Builder
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
-
97
- **Rule ID:** `process-builder-usage`
98
- **Class Name:** _[ProcessBuilder](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/ProcessBuilder.ts)_
99
- **Severity:** 🔴 *Error*
100
-
101
- #### SOQL Query In A Loop
102
- Running SOQL queries inside a loop can rapidly exceed query limits and severely degrade performance. Queries should be executed once, with results reused throughout the loop.
103
-
104
- **Rule ID:** `soql-in-loop`
105
- **Class Name:** _[SOQLQueryInLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/SOQLQueryInLoop.ts)_
106
- **Severity:** 🔴 *Error*
107
-
108
- #### Unsafe Running Context
109
- Flows configured to run in System Mode without Sharing grant access to all data, bypassing user permissions. Avoid this setting to prevent security risks and protect sensitive data.
110
-
111
- **Rule ID:** `unsafe-running-context`
112
- **Class Name:** _[UnsafeRunningContext](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/UnsafeRunningContext.ts)_
113
- **Severity:** 🔴 *Error*
114
-
115
- #### Duplicate DML Operation
116
- When a Flow performs database operations across multiple screens, users navigating backward can cause the same actions to run multiple times. To prevent unintended changes, either restrict backward navigation or redesign the Flow so database operations execute in a single, forward-moving step.
117
-
118
- **Rule ID:** `duplicate-dml`
119
- **Class Name:** _[DuplicateDMLOperation](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/DuplicateDMLOperation.ts)_
120
- **Severity:** 🟡 *Warning*
121
-
122
- #### Missing Fault Path
123
- Elements that can fail should include a Fault Path to handle errors gracefully. Without it, failures show generic errors to users. Fault Paths improve reliability and user experience.
124
-
125
- **Rule ID:** `missing-fault-path`
126
- **Class Name:** _[MissingFaultPath](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/MissingFaultPath.ts)_
127
- **Severity:** 🟡 *Warning*
128
-
129
- #### Missing Null Handler
130
- Get Records operations return null when no data is found. Without handling these null values, Flows can fail or produce unintended results. Adding a null check improves reliability and ensures the Flow behaves as expected.
131
-
132
- **Rule ID:** `missing-null-handler`
133
- **Class Name:** _[MissingNullHandler](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/MissingNullHandler.ts)_
134
- **Severity:** 🟡 *Warning*
135
-
136
- #### Recursive After Update
137
- After-save Flows that update the same record can trigger recursion, causing unintended behavior or performance issues. Avoid updating the triggering record in after-save Flows; use before-save Flows instead to prevent recursion.
138
-
139
- **Rule ID:** `recursive-record-update`
140
- **Class Name:** _[RecursiveAfterUpdate](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/RecursiveAfterUpdate.ts)_
141
- **Severity:** 🟡 *Warning*
142
-
143
- ---
144
-
145
- ### Suggestions
146
-
147
- These rules highlight areas where Flows can be improved. Following them increases reliability and long-term maintainability.
148
-
149
- #### Action Call In A Loop
150
- Repeatedly invoking Apex actions inside a loop can exhaust governor limits and lead to performance issues. Where possible, bulkify your logic by moving the action call outside the loop and passing a collection variable instead.
151
-
152
- **Rule ID:** `action-call-in-loop`
153
- **Class Name:** _[ActionCallsInLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/ActionCallsInLoop.ts)_
154
- **Severity:** 🟡 *Warning*
155
-
156
- #### Get Record All Fields
157
- Avoid using Get Records to retrieve all fields unless necessary. This improves performance, reduces processing time, and limits exposure of unnecessary data.
158
-
159
- **Rule ID:** `get-record-all-fields`
160
- **Class Name:** _[GetRecordAllFields](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/GetRecordAllFields.ts)_
161
- **Severity:** 🟡 *Warning*
162
-
163
- #### Inactive Flow
164
- Inactive Flows should be deleted or archived to reduce risk. Even when inactive, they can cause unintended record changes during testing or be activated as subflows. Keeping only active, relevant Flows improves safety and maintainability.
165
-
166
- **Rule ID:** `inactive-flow`
167
- **Class Name:** _[InactiveFlow](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/InactiveFlow.ts)_
168
- **Severity:** 🟡 *Warning*
169
-
170
- #### Invalid API Version
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
-
173
- **Rule ID:** `invalid-api-version`
174
- **Class Name:** _[APIVersion](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/APIVersion.ts)_
175
- **Severity:** 🟡 *Warning*
176
-
177
- #### Missing Filter Record Trigger ![Beta](https://img.shields.io/badge/status-beta-yellow)
178
- 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
-
180
- **Rule ID:** `missing-record-trigger-filter`
181
- **Class Name:** _[MissingFilterRecordTrigger](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/MissingFilterRecordTrigger.ts)_
182
- **Severity:** 🟡 *Warning*
183
-
184
- #### Same Record Field Updates
185
- Before-save Flows can safely update the triggering record directly via $Record, applying changes efficiently without extra DML operations. Using before-save updates improves performance
186
-
187
- **Rule ID:** `same-record-field-updates`
188
- **Class Name:** _[SameRecordFieldUpdates](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/SameRecordFieldUpdates.ts)_
189
- **Severity:** 🟡 *Warning*
190
-
191
- #### Excessive Cyclomatic Complexity
192
- 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
-
194
- **Rule ID:** `excessive-cyclomatic-complexity`
195
- **Class Name:** _[CyclomaticComplexity](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/CyclomaticComplexity.ts)_
196
- **Severity:** 🔵 *Note*
197
-
198
- #### Missing Trigger Order
199
- 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
-
201
- **Rule ID:** `unspecified-trigger-order`
202
- **Class Name:** _[TriggerOrder](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/TriggerOrder.ts)_
203
- **Severity:** 🔵 *Note*
204
-
205
- #### Record ID as String ![Beta](https://img.shields.io/badge/status-beta-yellow)
206
- Flows that use a String variable for a record ID instead of receiving the full record introduce unnecessary complexity and additional Get Records queries. Using the complete record simplifies the Flow and improves performance.
207
-
208
- **Rule ID:** `record-id-as-string`
209
- **Class Name:** _[RecordIdAsString](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/RecordIdAsString.ts)_
210
- **Severity:** 🔵 *Note*
211
-
212
- #### Transform Instead of Loop ![Beta](https://img.shields.io/badge/status-beta-yellow)
213
- Loop elements that perform direct Assignments on each item can slow down Flows. Using Transform elements allows bulk operations on collections, improving performance and reducing complexity.
214
-
215
- **Rule ID:** `transform-instead-of-loop`
216
- **Class Name:** _[TransformInsteadOfLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/TransformInsteadOfLoop.ts)_
217
- **Severity:** 🔵 *Note*
218
-
219
- ---
220
-
221
- ### Layout
222
-
223
- Focused on naming, documentation, and organization, these rules ensure Flows remain clear, easy to understand, and maintainable as automations grow.
224
-
225
- #### Flow Naming Convention
226
- Using clear and consistent Flow names improves readability, discoverability, and maintainability. A good naming convention helps team members quickly understand a Flow's purpose—for example, including a domain and brief description like Service_OrderFulfillment. Adopt a naming pattern that aligns with your organization's standards.
227
-
228
- **Rule ID:** `invalid-naming-convention`
229
- **Class Name:** _[FlowName](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/FlowName.ts)_
230
- **Severity:** 🔴 *Error*
231
-
232
- #### Missing Flow Description
233
- Flow descriptions are essential for documentation and maintainability. Include a description for each Flow, explaining its purpose and where it's used.
234
-
235
- **Rule ID:** `missing-flow-description`
236
- **Class Name:** _[FlowDescription](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/FlowDescription.ts)_
237
- **Severity:** 🔴 *Error*
238
-
239
- #### Missing Metadata Description ![Beta](https://img.shields.io/badge/status-beta-yellow)
240
- Elements and metadata without a description reduce clarity and maintainability. Adding descriptions improves readability and makes your automation easier to understand.
241
-
242
- **Rule ID:** `missing-metadata-description`
243
- **Class Name:** _[MissingMetadataDescription](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/MissingMetadataDescription.ts)_
244
- **Severity:** 🟡 *Warning*
245
-
246
- #### Unclear API Name
247
- Elements with unclear or duplicated API names, like Copy_X_Of_Element, reduce Flow readability. Make sure to update the API name when copying elements to keep your Flow organized.
248
-
249
- **Rule ID:** `unclear-api-naming`
250
- **Class Name:** _[CopyAPIName](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/CopyAPIName.ts)_
251
- **Severity:** 🟡 *Warning*
252
-
253
- #### Unreachable Element
254
- Unconnected elements never execute and add unnecessary clutter. Remove or connect unused Flow elements to keep Flows clean and efficient.
255
-
256
- **Rule ID:** `unreachable-element`
257
- **Class Name:** _[UnconnectedElement](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/UnconnectedElement.ts)_
258
- **Severity:** 🟡 *Warning*
259
-
260
- #### Unused Variable
261
- Unused variables are never referenced and add unnecessary clutter. Remove them to keep Flows efficient and easy to maintain.
262
-
263
- **Rule ID:** `unused-variable`
264
- **Class Name:** _[UnusedVariable](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/UnusedVariable.ts)_
265
- **Severity:** 🟡 *Warning*
266
-
267
- #### Missing Auto Layout
268
- Auto-Layout automatically arranges and aligns Flow elements, keeping the canvas organized and easier to maintain. Enabling it saves time and improves readability.
269
-
270
- **Rule ID:** `missing-auto-layout`
271
- **Class Name:** _[AutoLayout](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/AutoLayout.ts)_
272
- **Severity:** 🔵 *Note*
58
+ <!-- START GENERATED_RULES -->
59
+
60
+ ---
61
+
62
+ ### Problems
63
+
64
+ These rules detect anti-patterns and unsafe practices in your Flows that could break functionality, compromise security, or cause deployment failures.
65
+
66
+ #### DML Statement In A Loop
67
+ Executing DML operations (insert, update, delete) inside a loop is a high-risk anti-pattern that frequently causes governor limit exceptions. All database operations should be collected and executed once, outside the loop.
68
+
69
+ **Rule ID:** `dml-in-loop`
70
+ **Class Name:** _[DMLStatementInLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/DMLStatementInLoop.ts)_
71
+ **Severity:** 🔴 *Error*
72
+
73
+ #### Hardcoded Salesforce Id
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
+
76
+ **Rule ID:** `hardcoded-id`
77
+ **Class Name:** _[HardcodedId](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/HardcodedId.ts)_
78
+ **Severity:** 🔴 *Error*
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
+ #### Hardcoded Secret ![Beta](https://img.shields.io/badge/status-beta-yellow)
88
+ 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
+
90
+ **Rule ID:** `hardcoded-secret`
91
+ **Class Name:** _[HardcodedSecret](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/HardcodedSecret.ts)_
92
+ **Severity:** 🔴 *Error*
93
+
94
+ #### Process Builder
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
+
97
+ **Rule ID:** `process-builder-usage`
98
+ **Class Name:** _[ProcessBuilder](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/ProcessBuilder.ts)_
99
+ **Severity:** 🔴 *Error*
100
+
101
+ #### SOQL Query In A Loop
102
+ Running SOQL queries inside a loop can rapidly exceed query limits and severely degrade performance. Queries should be executed once, with results reused throughout the loop.
103
+
104
+ **Rule ID:** `soql-in-loop`
105
+ **Class Name:** _[SOQLQueryInLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/SOQLQueryInLoop.ts)_
106
+ **Severity:** 🔴 *Error*
107
+
108
+ #### Unsafe Running Context
109
+ Flows configured to run in System Mode without Sharing grant access to all data, bypassing user permissions. Avoid this setting to prevent security risks and protect sensitive data.
110
+
111
+ **Rule ID:** `unsafe-running-context`
112
+ **Class Name:** _[UnsafeRunningContext](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/UnsafeRunningContext.ts)_
113
+ **Severity:** 🔴 *Error*
114
+
115
+ #### Duplicate DML Operation
116
+ When a Flow performs database operations across multiple screens, users navigating backward can cause the same actions to run multiple times. To prevent unintended changes, either restrict backward navigation or redesign the Flow so database operations execute in a single, forward-moving step.
117
+
118
+ **Rule ID:** `duplicate-dml`
119
+ **Class Name:** _[DuplicateDMLOperation](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/DuplicateDMLOperation.ts)_
120
+ **Severity:** 🟡 *Warning*
121
+
122
+ #### Missing Fault Path
123
+ Elements that can fail should include a Fault Path to handle errors gracefully. Without it, failures show generic errors to users. Fault Paths improve reliability and user experience.
124
+
125
+ **Rule ID:** `missing-fault-path`
126
+ **Class Name:** _[MissingFaultPath](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/MissingFaultPath.ts)_
127
+ **Severity:** 🟡 *Warning*
128
+
129
+ #### Missing Null Handler
130
+ Get Records operations return null when no data is found. Without handling these null values, Flows can fail or produce unintended results. Adding a null check improves reliability and ensures the Flow behaves as expected.
131
+
132
+ **Rule ID:** `missing-null-handler`
133
+ **Class Name:** _[MissingNullHandler](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/MissingNullHandler.ts)_
134
+ **Severity:** 🟡 *Warning*
135
+
136
+ #### Recursive After Update
137
+ After-save Flows that update the same record can trigger recursion, causing unintended behavior or performance issues. Avoid updating the triggering record in after-save Flows; use before-save Flows instead to prevent recursion.
138
+
139
+ **Rule ID:** `recursive-record-update`
140
+ **Class Name:** _[RecursiveAfterUpdate](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/RecursiveAfterUpdate.ts)_
141
+ **Severity:** 🟡 *Warning*
142
+
143
+ ---
144
+
145
+ ### Suggestions
146
+
147
+ These rules highlight areas where Flows can be improved. Following them increases reliability and long-term maintainability.
148
+
149
+ #### Action Call In A Loop
150
+ Repeatedly invoking Apex actions inside a loop can exhaust governor limits and lead to performance issues. Where possible, bulkify your logic by moving the action call outside the loop and passing a collection variable instead.
151
+
152
+ **Rule ID:** `action-call-in-loop`
153
+ **Class Name:** _[ActionCallsInLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/ActionCallsInLoop.ts)_
154
+ **Severity:** 🟡 *Warning*
155
+
156
+ #### Get Record All Fields
157
+ Avoid using Get Records to retrieve all fields unless necessary. This improves performance, reduces processing time, and limits exposure of unnecessary data.
158
+
159
+ **Rule ID:** `get-record-all-fields`
160
+ **Class Name:** _[GetRecordAllFields](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/GetRecordAllFields.ts)_
161
+ **Severity:** 🟡 *Warning*
162
+
163
+ #### Inactive Flow
164
+ Inactive Flows should be deleted or archived to reduce risk. Even when inactive, they can cause unintended record changes during testing or be activated as subflows. Keeping only active, relevant Flows improves safety and maintainability.
165
+
166
+ **Rule ID:** `inactive-flow`
167
+ **Class Name:** _[InactiveFlow](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/InactiveFlow.ts)_
168
+ **Severity:** 🟡 *Warning*
169
+
170
+ #### Invalid API Version
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
+
173
+ **Rule ID:** `invalid-api-version`
174
+ **Class Name:** _[APIVersion](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/APIVersion.ts)_
175
+ **Severity:** 🟡 *Warning*
176
+
177
+ #### Missing Filter Record Trigger ![Beta](https://img.shields.io/badge/status-beta-yellow)
178
+ 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
+
180
+ **Rule ID:** `missing-record-trigger-filter`
181
+ **Class Name:** _[MissingFilterRecordTrigger](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/MissingFilterRecordTrigger.ts)_
182
+ **Severity:** 🟡 *Warning*
183
+
184
+ #### Same Record Field Updates
185
+ Before-save Flows can safely update the triggering record directly via $Record, applying changes efficiently without extra DML operations. Using before-save updates improves performance
186
+
187
+ **Rule ID:** `same-record-field-updates`
188
+ **Class Name:** _[SameRecordFieldUpdates](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/SameRecordFieldUpdates.ts)_
189
+ **Severity:** 🟡 *Warning*
190
+
191
+ #### Excessive Cyclomatic Complexity
192
+ 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
+
194
+ **Rule ID:** `excessive-cyclomatic-complexity`
195
+ **Class Name:** _[CyclomaticComplexity](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/CyclomaticComplexity.ts)_
196
+ **Severity:** 🔵 *Note*
197
+
198
+ #### Missing Trigger Order
199
+ 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
+
201
+ **Rule ID:** `unspecified-trigger-order`
202
+ **Class Name:** _[TriggerOrder](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/TriggerOrder.ts)_
203
+ **Severity:** 🔵 *Note*
204
+
205
+ #### Record ID as String ![Beta](https://img.shields.io/badge/status-beta-yellow)
206
+ Flows that use a String variable for a record ID instead of receiving the full record introduce unnecessary complexity and additional Get Records queries. Using the complete record simplifies the Flow and improves performance.
207
+
208
+ **Rule ID:** `record-id-as-string`
209
+ **Class Name:** _[RecordIdAsString](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/RecordIdAsString.ts)_
210
+ **Severity:** 🔵 *Note*
211
+
212
+ #### Transform Instead of Loop ![Beta](https://img.shields.io/badge/status-beta-yellow)
213
+ Loop elements that perform direct Assignments on each item can slow down Flows. Using Transform elements allows bulk operations on collections, improving performance and reducing complexity.
214
+
215
+ **Rule ID:** `transform-instead-of-loop`
216
+ **Class Name:** _[TransformInsteadOfLoop](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/TransformInsteadOfLoop.ts)_
217
+ **Severity:** 🔵 *Note*
218
+
219
+ ---
220
+
221
+ ### Layout
222
+
223
+ Focused on naming, documentation, and organization, these rules ensure Flows remain clear, easy to understand, and maintainable as automations grow.
224
+
225
+ #### Flow Naming Convention
226
+ Using clear and consistent Flow names improves readability, discoverability, and maintainability. A good naming convention helps team members quickly understand a Flow's purpose—for example, including a domain and brief description like Service_OrderFulfillment. Adopt a naming pattern that aligns with your organization's standards.
227
+
228
+ **Rule ID:** `invalid-naming-convention`
229
+ **Class Name:** _[FlowName](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/FlowName.ts)_
230
+ **Severity:** 🔴 *Error*
231
+
232
+ #### Missing Flow Description
233
+ Flow descriptions are essential for documentation and maintainability. Include a description for each Flow, explaining its purpose and where it's used.
234
+
235
+ **Rule ID:** `missing-flow-description`
236
+ **Class Name:** _[FlowDescription](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/FlowDescription.ts)_
237
+ **Severity:** 🔴 *Error*
238
+
239
+ #### Missing Metadata Description ![Beta](https://img.shields.io/badge/status-beta-yellow)
240
+ Elements and metadata without a description reduce clarity and maintainability. Adding descriptions improves readability and makes your automation easier to understand.
241
+
242
+ **Rule ID:** `missing-metadata-description`
243
+ **Class Name:** _[MissingMetadataDescription](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/MissingMetadataDescription.ts)_
244
+ **Severity:** 🟡 *Warning*
245
+
246
+ #### Unclear API Name
247
+ Elements with unclear or duplicated API names, like Copy_X_Of_Element, reduce Flow readability. Make sure to update the API name when copying elements to keep your Flow organized.
248
+
249
+ **Rule ID:** `unclear-api-naming`
250
+ **Class Name:** _[CopyAPIName](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/CopyAPIName.ts)_
251
+ **Severity:** 🟡 *Warning*
252
+
253
+ #### Unreachable Element
254
+ Unconnected elements never execute and add unnecessary clutter. Remove or connect unused Flow elements to keep Flows clean and efficient.
255
+
256
+ **Rule ID:** `unreachable-element`
257
+ **Class Name:** _[UnconnectedElement](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/UnconnectedElement.ts)_
258
+ **Severity:** 🟡 *Warning*
259
+
260
+ #### Unused Variable
261
+ Unused variables are never referenced and add unnecessary clutter. Remove them to keep Flows efficient and easy to maintain.
262
+
263
+ **Rule ID:** `unused-variable`
264
+ **Class Name:** _[UnusedVariable](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/UnusedVariable.ts)_
265
+ **Severity:** 🟡 *Warning*
266
+
267
+ #### Missing Auto Layout
268
+ Auto-Layout automatically arranges and aligns Flow elements, keeping the canvas organized and easier to maintain. Enabling it saves time and improves readability.
269
+
270
+ **Rule ID:** `missing-auto-layout`
271
+ **Class Name:** _[AutoLayout](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/packages/core/src/main/rules/AutoLayout.ts)_
272
+ **Severity:** 🔵 *Note*
273
+
274
+ #### System (subcategory)
275
+
276
+ System rules are a subset of Layout rules that detect structural issues normally prevented by the Flow Builder UI. See [System Rules Documentation](https://github.com/Flow-Scanner/lightning-flow-scanner/blob/main/docs/system-rules.md) for the full list.
273
277
  <!-- END GENERATED_RULES -->
274
278
 
275
279
  ---
@@ -281,20 +285,26 @@ It is recommend to configure and define:
281
285
  - The severity of violating any specific rule.
282
286
  - Expressions used for rules, such as REGEX patterns and comparison operators.
283
287
  - Any known exceptions that should be ignored during scanning.
288
+ - (Optional) Implement filters based on a severity **threshold** or **rule categories**.
289
+
290
+ Most distributions automatically load configuration from:
291
+ - `.flow-scanner.yml`
292
+ - `.flow-scanner.json`
293
+ - `package.json` → `"flowScanner"` key
284
294
 
285
295
  ```json
286
296
  {
287
297
  "rules": {
288
- // Your rule configurations
298
+ // rule customizations (severity, expression, enabled, ...)
289
299
  },
290
300
  "exceptions": {
291
- // Your defined exceptions
292
- }
301
+ // flow rule → result suppressions
302
+ },
303
+ "threshold": "error", // only consider errors
304
+ "categories": ["problem", "layout"] // only run rules from these categories
293
305
  }
294
306
  ```
295
307
 
296
- Most Lightning Flow Scanner distributions automatically resolve configurations from `.flow-scanner.yml`, `.flow-scanner.json`, or `package.json` → `flowScanner`.
297
-
298
308
  ### Configure Rules
299
309
 
300
310
  By default, all default rules are executed. You can customize individual rules and override the rules to be executed without having to specify every rule. Below is a breakdown of the available attributes of rule configuration:
@@ -315,7 +325,7 @@ By default, all default rules are executed. You can customize individual rules a
315
325
 
316
326
  #### Configure Severity
317
327
 
318
- When the severity is not provided it will be `warning` by default. Other available values for severity are `error` and `note`. Configure the severity per rule as demonstrated below:
328
+ Available values for severity are `error`, `warning` and `note`. If no severity is provided, a default value is applied. Configure the severity per rule as demonstrated below:
319
329
 
320
330
  ```json
321
331
  {
@@ -444,7 +454,19 @@ Exclude specific flows by their unique API names, regardless of their location.
444
454
 
445
455
  **Environment compatibility**: works in **all environments** including Node.js and browser/web distributions, as it operates on parsed flow data rather than file system paths.
446
456
 
447
- ### Scan Modes
457
+ ### Scan Options
458
+
459
+ #### Severity Threshold
460
+ Only report on violations at or above a chosen severity level:
461
+ ```json
462
+ { "threshold": "error" }
463
+ ```
464
+
465
+ #### Filter by category
466
+ Restrict the scan to specific categories of rules:
467
+ ```json
468
+ { "categories": ["problem", "layout"] }
469
+ ```
448
470
 
449
471
  #### Beta Mode
450
472
 
@@ -469,9 +491,10 @@ By default, Lightning Flow Scanner runs **all** default rules and merges any cus
469
491
  |----------------------------------------------------------------|-----------------------------------------------|---------------------------------------------------------------------------------------------------------|
470
492
  | **[Salesforce CLI Plugin](https://www.npmjs.com/package/lightning-flow-scanner)** | Local development, scratch orgs, CI/CD | `sf plugins install lightning-flow-scanner` |
471
493
  | **[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` |
472
- | **[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` |
494
+ | **[Salesforce App](https://github.com/Flow-Scanner/lightning-flow-scanner-app)** | Run scans directly inside a Salesforce org | `sf package install --package 04tgK0000008CLlQAM` |
473
495
  | **[GitHub Action](https://github.com/marketplace/actions/lightning-flow-scan)** | Native PR checks | `uses: Flow-Scanner/lightning-flow-scanner@main` |
474
496
  | **[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` |
497
+ | **[Regex Scanner](https://www.npmjs.com/package/@flow-scanner/regex-scanner)** | Regex-based scanning | `npm install -g @flow-scanner/regex-scanner`
475
498
 
476
499
  **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).
477
500
 
package/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { IRuleDefinition } from "./main/interfaces/IRuleDefinition";
2
- import type { IRulesConfig } from "./main/interfaces/IRulesConfig";
2
+ import type { IRulesConfig, RuleCategory, Severity, Threshold } from "./main/interfaces/IRulesConfig";
3
+ import { SEVERITY_ORDER, meetsThreshold, countThresholdViolations, filterByThreshold } from "./main/interfaces/IRulesConfig";
3
4
  import type { FlatViolation } from "./main/models/FlatViolation";
4
5
  import { Compiler } from "./main/libs/Compiler";
5
6
  import { exportDetails } from "./main/libs/ExportDetails";
@@ -22,5 +23,5 @@ import { Violation } from "./main/models/Violation";
22
23
  import { DEFAULT_ICONS, ASCII_ICONS, type NodeIconConfig } from "./main/config/NodeIcons";
23
24
  import { DEFAULT_VARIABLE_ICONS, ASCII_VARIABLE_ICONS, type VariableIconConfig } from "./main/config/VariableIcons";
24
25
  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 };
26
+ 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, SEVERITY_ORDER, meetsThreshold, countThresholdViolations, filterByThreshold, };
27
+ export type { FlatViolation, IRuleDefinition, IRulesConfig, RuleCategory, Severity, Threshold, NodeIconConfig, DiagramOptions, VariableIconConfig };
package/index.js CHANGED
@@ -52,12 +52,19 @@ _export(exports, {
52
52
  get RuleResult () {
53
53
  return _RuleResult.RuleResult;
54
54
  },
55
+ get // Threshold utilities
56
+ SEVERITY_ORDER () {
57
+ return _IRulesConfig.SEVERITY_ORDER;
58
+ },
55
59
  get ScanResult () {
56
60
  return _ScanResult.ScanResult;
57
61
  },
58
62
  get Violation () {
59
63
  return _Violation.Violation;
60
64
  },
65
+ get countThresholdViolations () {
66
+ return _IRulesConfig.countThresholdViolations;
67
+ },
61
68
  get exportDetails () {
62
69
  return _ExportDetails.exportDetails;
63
70
  },
@@ -67,12 +74,18 @@ _export(exports, {
67
74
  get exportSarif () {
68
75
  return _ExportSarif.exportSarif;
69
76
  },
77
+ get filterByThreshold () {
78
+ return _IRulesConfig.filterByThreshold;
79
+ },
70
80
  get fix () {
71
81
  return _FixFlows.fix;
72
82
  },
73
83
  get getRules () {
74
84
  return _GetRuleDefinitions.getRules;
75
85
  },
86
+ get meetsThreshold () {
87
+ return _IRulesConfig.meetsThreshold;
88
+ },
76
89
  get parse () {
77
90
  return _ParseFlows.parse;
78
91
  },
@@ -80,6 +93,7 @@ _export(exports, {
80
93
  return _ScanFlows.scan;
81
94
  }
82
95
  });
96
+ const _IRulesConfig = require("./main/interfaces/IRulesConfig");
83
97
  const _Compiler = require("./main/libs/Compiler");
84
98
  const _ExportDetails = require("./main/libs/ExportDetails");
85
99
  const _ExportSarif = require("./main/libs/ExportSarif");
@@ -34,6 +34,7 @@ const _UnsafeRunningContext = require("../rules/UnsafeRunningContext");
34
34
  const _UnusedVariable = require("../rules/UnusedVariable");
35
35
  const _MissingMetadataDescription = require("../rules/MissingMetadataDescription");
36
36
  const _MissingRecordTriggerFilter = require("../rules/MissingRecordTriggerFilter");
37
+ const _MissingStartReference = require("../rules/MissingStartReference");
37
38
  const _TransformInsteadOfLoop = require("../rules/TransformInsteadOfLoop");
38
39
  const _RecordIdAsString = require("../rules/RecordIdAsString");
39
40
  function _define_property(obj, key, value) {
@@ -201,6 +202,7 @@ registry.register("unsafe-running-context", _UnsafeRunningContext.UnsafeRunningC
201
202
  registry.register("unused-variable", _UnusedVariable.UnusedVariable, "UnusedVariable");
202
203
  registry.register("missing-metadata-description", _MissingMetadataDescription.MissingMetadataDescription, "MissingMetadataDescription", true);
203
204
  registry.register("missing-record-trigger-filter", _MissingRecordTriggerFilter.MissingRecordTriggerFilter, "MissingFilterRecordTrigger", true);
205
+ registry.register("missing-start-reference", _MissingStartReference.MissingStartReference, "MissingStartReference", true);
204
206
  registry.register("transform-instead-of-loop", _TransformInsteadOfLoop.TransformInsteadOfLoop, "TransformInsteadOfLoop", true);
205
207
  registry.register("record-id-as-string", _RecordIdAsString.RecordIdAsString, "RecordIdAsString", true);
206
208
  registry.register("hardcoded-secret", _HardcodedSecret.HardcodedSecret, "HardcodedSecret", true);
@@ -1,6 +1,7 @@
1
1
  import { Flow, RuleResult } from "../internals/internals";
2
2
  export interface IRuleDefinition {
3
3
  ruleId: string;
4
+ category?: 'problem' | 'suggestion' | 'layout' | 'system';
4
5
  description: string;
5
6
  summary: string;
6
7
  docRefs: Array<{
@@ -4,12 +4,45 @@ export declare enum DetailLevel {
4
4
  ENRICHED = "enriched",
5
5
  SIMPLE = "simple"
6
6
  }
7
+ export type RuleCategory = 'problem' | 'suggestion' | 'layout';
8
+ export type Severity = 'error' | 'warning' | 'note';
9
+ export type Threshold = Severity | 'never';
10
+ /** Severity levels ordered from most to least severe */
11
+ export declare const SEVERITY_ORDER: Severity[];
7
12
  export interface IRulesConfig {
8
13
  betaMode?: boolean;
9
14
  betamode?: boolean;
15
+ systemRules?: boolean;
16
+ categories?: RuleCategory[];
17
+ threshold?: Threshold;
10
18
  detailLevel?: 'enriched' | 'simple' | DetailLevel;
11
19
  exceptions?: IExceptions;
12
20
  rules?: IRuleOptions;
13
21
  ruleMode?: "merged" | "isolated";
14
22
  ignoreFlows?: string[];
15
23
  }
24
+ /**
25
+ * Check if a severity meets or exceeds the threshold.
26
+ * @param severity - The severity to check
27
+ * @param threshold - The threshold to compare against
28
+ * @returns true if severity >= threshold (more severe or equal)
29
+ */
30
+ export declare function meetsThreshold(severity: string | undefined, threshold: Threshold): boolean;
31
+ /**
32
+ * Count violations that meet or exceed the threshold.
33
+ * @param results - Array of results with severity property
34
+ * @param threshold - The threshold to compare against
35
+ * @returns Number of violations meeting the threshold
36
+ */
37
+ export declare function countThresholdViolations(results: Array<{
38
+ severity?: string;
39
+ }>, threshold: Threshold): number;
40
+ /**
41
+ * Filter results to only include those meeting the threshold.
42
+ * @param results - Array of results with severity property
43
+ * @param threshold - The threshold to filter by
44
+ * @returns Filtered array of results meeting the threshold ('never' returns all)
45
+ */
46
+ export declare function filterByThreshold<T extends {
47
+ severity?: string;
48
+ }>(results: T[], threshold: Threshold): T[];
@@ -2,10 +2,27 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "DetailLevel", {
6
- enumerable: true,
7
- get: function() {
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get DetailLevel () {
8
13
  return DetailLevel;
14
+ },
15
+ get SEVERITY_ORDER () {
16
+ return SEVERITY_ORDER;
17
+ },
18
+ get countThresholdViolations () {
19
+ return countThresholdViolations;
20
+ },
21
+ get filterByThreshold () {
22
+ return filterByThreshold;
23
+ },
24
+ get meetsThreshold () {
25
+ return meetsThreshold;
9
26
  }
10
27
  });
11
28
  var DetailLevel = /*#__PURE__*/ function(DetailLevel) {
@@ -13,3 +30,24 @@ var DetailLevel = /*#__PURE__*/ function(DetailLevel) {
13
30
  DetailLevel["SIMPLE"] = "simple";
14
31
  return DetailLevel;
15
32
  }({});
33
+ const SEVERITY_ORDER = [
34
+ 'error',
35
+ 'warning',
36
+ 'note'
37
+ ];
38
+ function meetsThreshold(severity, threshold) {
39
+ if (threshold === 'never') return false;
40
+ const sev = severity || 'warning';
41
+ const sevIndex = SEVERITY_ORDER.indexOf(sev);
42
+ const thresholdIndex = SEVERITY_ORDER.indexOf(threshold);
43
+ // Lower index = more severe, so severity meets threshold if sevIndex <= thresholdIndex
44
+ return sevIndex >= 0 && sevIndex <= thresholdIndex;
45
+ }
46
+ function countThresholdViolations(results, threshold) {
47
+ if (threshold === 'never') return 0;
48
+ return results.filter((r)=>meetsThreshold(r.severity, threshold)).length;
49
+ }
50
+ function filterByThreshold(results, threshold) {
51
+ if (threshold === 'never') return results;
52
+ return results.filter((r)=>meetsThreshold(r.severity, threshold));
53
+ }
@@ -1,3 +1,7 @@
1
1
  import * as core from "../internals/internals";
2
2
  export declare function fix(results: core.ScanResult[]): core.ScanResult[];
3
+ /**
4
+ * @deprecated Use fix() instead which modifies flows in place.
5
+ * Kept for backward compatibility.
6
+ */
3
7
  export declare function FixFlows(flow: core.Flow, ruleResults: core.RuleResult[]): core.Flow;
@@ -17,7 +17,6 @@ _export(exports, {
17
17
  }
18
18
  });
19
19
  const _internals = /*#__PURE__*/ _interop_require_wildcard(require("../internals/internals"));
20
- const _BuildFlow = require("./BuildFlow");
21
20
  function _getRequireWildcardCache(nodeInterop) {
22
21
  if (typeof WeakMap !== "function") return null;
23
22
  var cacheBabelInterop = new WeakMap();
@@ -63,45 +62,105 @@ function fix(results) {
63
62
  const newResults = [];
64
63
  for (const result of results){
65
64
  if (!result.ruleResults || result.ruleResults.length === 0) continue;
66
- const fixables = result.ruleResults.filter((r)=>r.ruleName === "UnusedVariable" && r.occurs || r.ruleName === "UnconnectedElement" && r.occurs);
65
+ const fixables = result.ruleResults.filter((r)=>r.ruleName === "UnusedVariable" && r.occurs || r.ruleName === "UnconnectedElement" && r.occurs || r.ruleName === "AutoLayout" && r.occurs);
67
66
  if (fixables.length === 0) continue;
68
- const newFlow = FixFlows(result.flow, fixables);
69
- const hasRemainingElements = newFlow.elements && newFlow.elements.length > 0;
70
- if (hasRemainingElements) {
71
- result.flow = newFlow;
72
- newResults.push(result);
67
+ // Handle AutoLayout fix separately (modifies metadata, not elements)
68
+ const autoLayoutFix = fixables.find((r)=>r.ruleName === "AutoLayout");
69
+ if (autoLayoutFix) {
70
+ applyAutoLayoutFix(result.flow);
73
71
  }
72
+ // Handle element-based fixes (UnusedVariable, UnconnectedElement)
73
+ // These modify xmldata in place to preserve element order and formatting
74
+ const elementFixables = fixables.filter((r)=>r.ruleName !== "AutoLayout");
75
+ if (elementFixables.length > 0) {
76
+ applyElementFixes(result.flow, elementFixables);
77
+ }
78
+ newResults.push(result);
74
79
  }
75
80
  return newResults;
76
81
  }
77
- function FixFlows(flow, ruleResults) {
78
- var _unusedVariableRes_details, _unconnectedElementsRes_details, _flow_elements;
82
+ function applyAutoLayoutFix(flow) {
83
+ if (!flow.xmldata) return;
84
+ // Ensure processMetadataValues is an array
85
+ if (!flow.xmldata.processMetadataValues) {
86
+ flow.xmldata.processMetadataValues = [];
87
+ } else if (!Array.isArray(flow.xmldata.processMetadataValues)) {
88
+ flow.xmldata.processMetadataValues = [
89
+ flow.xmldata.processMetadataValues
90
+ ];
91
+ }
92
+ // Find existing CanvasMode entry
93
+ const canvasModeIndex = flow.xmldata.processMetadataValues.findIndex((mdv)=>mdv.name === "CanvasMode");
94
+ const autoLayoutValue = {
95
+ name: "CanvasMode",
96
+ value: {
97
+ stringValue: "AUTO_LAYOUT_CANVAS"
98
+ }
99
+ };
100
+ if (canvasModeIndex >= 0) {
101
+ // Update existing entry
102
+ flow.xmldata.processMetadataValues[canvasModeIndex] = autoLayoutValue;
103
+ } else {
104
+ // Add new entry
105
+ flow.xmldata.processMetadataValues.push(autoLayoutValue);
106
+ }
107
+ // Update the flow's processMetadataValues property
108
+ flow.processMetadataValues = flow.xmldata.processMetadataValues;
109
+ }
110
+ /**
111
+ * Apply element-based fixes (UnusedVariable, UnconnectedElement) by modifying xmldata in place.
112
+ * This preserves element order and formatting from the original file.
113
+ */ function applyElementFixes(flow, ruleResults) {
114
+ var _unusedVariableRes_details, _unconnectedElementsRes_details;
115
+ if (!flow.xmldata) return;
79
116
  const unusedVariableRes = ruleResults.find((r)=>r.ruleName === "UnusedVariable");
80
117
  var _unusedVariableRes_details_map;
81
118
  const unusedVariableNames = new Set((_unusedVariableRes_details_map = unusedVariableRes === null || unusedVariableRes === void 0 ? void 0 : (_unusedVariableRes_details = unusedVariableRes.details) === null || _unusedVariableRes_details === void 0 ? void 0 : _unusedVariableRes_details.map((d)=>d.name)) !== null && _unusedVariableRes_details_map !== void 0 ? _unusedVariableRes_details_map : []);
82
119
  const unconnectedElementsRes = ruleResults.find((r)=>r.ruleName === "UnconnectedElement");
83
120
  var _unconnectedElementsRes_details_map;
84
121
  const unconnectedElementNames = new Set((_unconnectedElementsRes_details_map = unconnectedElementsRes === null || unconnectedElementsRes === void 0 ? void 0 : (_unconnectedElementsRes_details = unconnectedElementsRes.details) === null || _unconnectedElementsRes_details === void 0 ? void 0 : _unconnectedElementsRes_details.map((d)=>d.name)) !== null && _unconnectedElementsRes_details_map !== void 0 ? _unconnectedElementsRes_details_map : []);
85
- var _flow_elements_filter;
86
- const nodesToKeep = (_flow_elements_filter = (_flow_elements = flow.elements) === null || _flow_elements === void 0 ? void 0 : _flow_elements.filter((node)=>{
87
- switch(node.metaType){
88
- case "attribute":
89
- case "resource":
90
- return true;
91
- case "node":
92
- {
93
- const nodeElement = node;
94
- return !unconnectedElementNames.has(nodeElement.name);
95
- }
96
- case "variable":
97
- {
98
- const nodeVar = node;
99
- return !unusedVariableNames.has(nodeVar.name);
100
- }
101
- default:
102
- return false;
122
+ // Remove unused variables from xmldata
123
+ if (unusedVariableNames.size > 0) {
124
+ for (const varTag of _internals.Flow.VARIABLE_TAGS){
125
+ removeElementsByName(flow.xmldata, varTag, unusedVariableNames);
126
+ }
127
+ }
128
+ // Remove unconnected elements from xmldata
129
+ if (unconnectedElementNames.size > 0) {
130
+ for (const nodeTag of _internals.Flow.NODE_TAGS){
131
+ removeElementsByName(flow.xmldata, nodeTag, unconnectedElementNames);
132
+ }
133
+ }
134
+ // Update the flow's elements array to match the modified xmldata
135
+ flow.preProcessNodes();
136
+ }
137
+ /**
138
+ * Remove elements from xmldata by name.
139
+ * Handles both single element and array cases.
140
+ */ function removeElementsByName(xmldata, tagName, namesToRemove) {
141
+ const elements = xmldata[tagName];
142
+ if (!elements) return;
143
+ if (Array.isArray(elements)) {
144
+ const filtered = elements.filter((el)=>!namesToRemove.has(el === null || el === void 0 ? void 0 : el.name));
145
+ if (filtered.length === 0) {
146
+ delete xmldata[tagName];
147
+ } else if (filtered.length === 1) {
148
+ // Keep as single element if only one remains (matches original format)
149
+ xmldata[tagName] = filtered[0];
150
+ } else {
151
+ xmldata[tagName] = filtered;
103
152
  }
104
- })) !== null && _flow_elements_filter !== void 0 ? _flow_elements_filter : [];
105
- const xmldata = (0, _BuildFlow.BuildFlow)(nodesToKeep);
106
- return new _internals.Flow(flow.fsPath, xmldata);
153
+ } else if (typeof elements === 'object' && elements !== null) {
154
+ // Single element case
155
+ if (namesToRemove.has(elements.name)) {
156
+ delete xmldata[tagName];
157
+ }
158
+ }
159
+ }
160
+ function FixFlows(flow, ruleResults) {
161
+ // Create a shallow clone of xmldata to avoid modifying the original
162
+ const clonedXmldata = JSON.parse(JSON.stringify(flow.xmldata));
163
+ const clonedFlow = new _internals.Flow(flow.fsPath, clonedXmldata);
164
+ applyElementFixes(clonedFlow, ruleResults);
165
+ return clonedFlow;
107
166
  }
@@ -19,6 +19,8 @@ _export(exports, {
19
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
+ const includeSystem = (options === null || options === void 0 ? void 0 : options.systemRules) !== false; // defaults to true
23
+ const categories = options === null || options === void 0 ? void 0 : options.categories; // undefined means all categories
22
24
  const rulesMode = (options === null || options === void 0 ? void 0 : options.ruleMode) || "merged";
23
25
  const selectedRules = [];
24
26
  const ruleIds = _RuleRegistry.ruleRegistry.getAllRuleIds(includeBeta);
@@ -31,6 +33,10 @@ function GetRuleDefinitions(ruleConfig, options) {
31
33
  const config = ruleConfig.get(key);
32
34
  if ((config === null || config === void 0 ? void 0 : config.enabled) === false) continue;
33
35
  const rule = _RuleRegistry.ruleRegistry.createInstance(entry.ruleId); // Always use ruleId to instantiate
36
+ // Skip system rules if disabled
37
+ if (rule.category === 'system' && !includeSystem) continue;
38
+ // Skip rules not in selected categories (if categories filter is specified)
39
+ if (!isCategoryIncluded(rule.category, categories, includeSystem)) continue;
34
40
  if (config === null || config === void 0 ? void 0 : config.severity) {
35
41
  rule.severity = config.severity;
36
42
  }
@@ -41,6 +47,10 @@ function GetRuleDefinitions(ruleConfig, options) {
41
47
  // MERGED MODE (default)
42
48
  for (const ruleId of ruleIds){
43
49
  const rule = _RuleRegistry.ruleRegistry.createInstance(ruleId);
50
+ // Skip system rules if disabled
51
+ if (rule.category === 'system' && !includeSystem) continue;
52
+ // Skip rules not in selected categories (if categories filter is specified)
53
+ if (!isCategoryIncluded(rule.category, categories, includeSystem)) continue;
44
54
  var _ruleConfig_get;
45
55
  // Try to find config by ruleId first, then fall back to legacy name
46
56
  const config = (_ruleConfig_get = ruleConfig === null || ruleConfig === void 0 ? void 0 : ruleConfig.get(rule.ruleId)) !== null && _ruleConfig_get !== void 0 ? _ruleConfig_get : ruleConfig === null || ruleConfig === void 0 ? void 0 : ruleConfig.get(rule.name) // rule.name is the legacy camelCase name (e.g. "ActionCallsInLoop")
@@ -53,6 +63,26 @@ function GetRuleDefinitions(ruleConfig, options) {
53
63
  }
54
64
  return selectedRules;
55
65
  }
66
+ /**
67
+ * Check if a rule's category should be included based on the categories filter.
68
+ * - If no categories filter is specified, all categories are included
69
+ * - System rules are handled separately via includeSystem flag
70
+ * - Rules with matching category are included
71
+ * - Category matching is case-insensitive
72
+ */ function isCategoryIncluded(ruleCategory, categories, includeSystem) {
73
+ // System category is controlled by systemRules flag, not categories filter
74
+ if (ruleCategory === 'system') {
75
+ return includeSystem;
76
+ }
77
+ // If no categories filter specified, include all non-system categories
78
+ if (!categories || categories.length === 0) {
79
+ return true;
80
+ }
81
+ // Normalize categories to lowercase for case-insensitive matching
82
+ const normalizedCategories = categories.map((c)=>c.toLowerCase());
83
+ // Check if rule's category is in the allowed list (case-insensitive)
84
+ return normalizedCategories.includes(ruleCategory === null || ruleCategory === void 0 ? void 0 : ruleCategory.toLowerCase());
85
+ }
56
86
  function getRules(ruleNames, options) {
57
87
  return _RuleRegistry.ruleRegistry.getRulesByNames(ruleNames, options);
58
88
  }
@@ -66,4 +66,5 @@ export declare class Flow {
66
66
  private findStart;
67
67
  toXMLString(): string;
68
68
  private generateDoc;
69
+ private hasXsiAttributes;
69
70
  }
@@ -259,16 +259,42 @@ let Flow = class Flow {
259
259
  };
260
260
  const builder = new _fastxmlparser.XMLBuilder(builderOptions);
261
261
  const xmldataWithNs = _object_spread({}, this.xmldata);
262
+ // Always ensure the base xmlns is present
262
263
  if (!xmldataWithNs["@_xmlns"]) {
263
264
  xmldataWithNs["@_xmlns"] = flowXmlNamespace;
264
265
  }
265
- if (!xmldataWithNs["@_xmlns:xsi"]) {
266
+ // Only add xmlns:xsi if the content actually uses xsi: attributes
267
+ // Don't add it unconditionally to avoid unnecessary diffs
268
+ if (!xmldataWithNs["@_xmlns:xsi"] && this.hasXsiAttributes(xmldataWithNs)) {
266
269
  xmldataWithNs["@_xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance";
267
270
  }
268
271
  const rootObj = {
269
272
  Flow: xmldataWithNs
270
273
  };
271
- return builder.build(rootObj);
274
+ const xmlContent = builder.build(rootObj);
275
+ // Add XML declaration if not present
276
+ const xmlDeclaration = '<?xml version="1.0" encoding="UTF-8"?>\n';
277
+ if (!xmlContent.startsWith('<?xml')) {
278
+ return xmlDeclaration + xmlContent;
279
+ }
280
+ return xmlContent;
281
+ }
282
+ hasXsiAttributes(obj) {
283
+ if (obj === null || obj === undefined) {
284
+ return false;
285
+ }
286
+ if (typeof obj !== 'object') {
287
+ return false;
288
+ }
289
+ for (const key of Object.keys(obj)){
290
+ if (key.includes(':xsi') || key.includes('xsi:')) {
291
+ return true;
292
+ }
293
+ if (this.hasXsiAttributes(obj[key])) {
294
+ return true;
295
+ }
296
+ }
297
+ return false;
272
298
  }
273
299
  constructor(path, data){
274
300
  // Flow elements (excludes legacy start nodes)
@@ -1,7 +1,7 @@
1
1
  import { RuleInfo } from "./RuleInfo";
2
2
  import * as core from "../internals/internals";
3
3
  export declare abstract class RuleCommon {
4
- category?: 'problem' | 'suggestion' | 'layout';
4
+ category?: 'problem' | 'suggestion' | 'layout' | 'system';
5
5
  description: string;
6
6
  summary: string;
7
7
  docRefs: Array<{
@@ -29,9 +29,8 @@ export declare class RuleInfo {
29
29
  label: string;
30
30
  /**
31
31
  * The category for the rule.
32
- * 'problem' | 'suggestion' | 'layout'
33
32
  */
34
- category: 'problem' | 'suggestion' | 'layout';
33
+ category: 'problem' | 'suggestion' | 'layout' | 'system';
35
34
  /**
36
35
  * Stable public identifier used for config, suppression, and reporting.
37
36
  */
@@ -38,7 +38,6 @@ let RuleInfo = class RuleInfo {
38
38
  */ _define_property(this, "label", void 0);
39
39
  /**
40
40
  * The category for the rule.
41
- * 'problem' | 'suggestion' | 'layout'
42
41
  */ _define_property(this, "category", void 0);
43
42
  /**
44
43
  * Stable public identifier used for config, suppression, and reporting.
@@ -0,0 +1,7 @@
1
+ import * as core from "../internals/internals";
2
+ import { RuleCommon } from "../models/RuleCommon";
3
+ import { IRuleDefinition } from "../internals/internals";
4
+ export declare class MissingStartReference extends RuleCommon implements IRuleDefinition {
5
+ constructor();
6
+ protected check(flow: core.Flow, _options: object | undefined, _suppressions: Set<string>): core.Violation[];
7
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "MissingStartReference", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return MissingStartReference;
9
+ }
10
+ });
11
+ const _internals = /*#__PURE__*/ _interop_require_wildcard(require("../internals/internals"));
12
+ const _RuleCommon = require("../models/RuleCommon");
13
+ function _getRequireWildcardCache(nodeInterop) {
14
+ if (typeof WeakMap !== "function") return null;
15
+ var cacheBabelInterop = new WeakMap();
16
+ var cacheNodeInterop = new WeakMap();
17
+ return (_getRequireWildcardCache = function(nodeInterop) {
18
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
19
+ })(nodeInterop);
20
+ }
21
+ function _interop_require_wildcard(obj, nodeInterop) {
22
+ if (!nodeInterop && obj && obj.__esModule) {
23
+ return obj;
24
+ }
25
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
26
+ return {
27
+ default: obj
28
+ };
29
+ }
30
+ var cache = _getRequireWildcardCache(nodeInterop);
31
+ if (cache && cache.has(obj)) {
32
+ return cache.get(obj);
33
+ }
34
+ var newObj = {
35
+ __proto__: null
36
+ };
37
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
38
+ for(var key in obj){
39
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
40
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
41
+ if (desc && (desc.get || desc.set)) {
42
+ Object.defineProperty(newObj, key, desc);
43
+ } else {
44
+ newObj[key] = obj[key];
45
+ }
46
+ }
47
+ }
48
+ newObj.default = obj;
49
+ if (cache) {
50
+ cache.set(obj, newObj);
51
+ }
52
+ return newObj;
53
+ }
54
+ let MissingStartReference = class MissingStartReference extends _RuleCommon.RuleCommon {
55
+ check(flow, _options, _suppressions) {
56
+ const violations = [];
57
+ if (!flow.startNode) {
58
+ violations.push(new _internals.Violation(new _internals.FlowAttribute("undefined", "startNode", "startNode")));
59
+ }
60
+ return violations;
61
+ }
62
+ constructor(){
63
+ super({
64
+ ruleId: "missing-start-reference",
65
+ category: "system",
66
+ name: "MissingStartReference",
67
+ label: "Missing Start Reference",
68
+ description: "When a flow has no start reference.",
69
+ summary: "Ensure flow has a start reference node",
70
+ supportedTypes: _internals.FlowType.allTypes(),
71
+ docRefs: []
72
+ }, {
73
+ severity: "error"
74
+ });
75
+ }
76
+ };
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.17.3",
4
+ "version": "6.18.0",
5
5
  "main": "index.js",
6
6
  "exports": {
7
7
  ".": {