diff --git a/build/src/api/core/orgcheck-api-secretsauce.js b/build/src/api/core/orgcheck-api-secretsauce.js index 7f97921c..50e20664 100644 --- a/build/src/api/core/orgcheck-api-secretsauce.js +++ b/build/src/api/core/orgcheck-api-secretsauce.js @@ -714,6 +714,216 @@ const ALL_SCORE_RULES = [ badField: 'hasDebugMode', applicable: [ SFDC_User ], category: SCORE_RULE_CATEGORIES.USER_ADOPTION + }, + // Lightning Flow Scanner Rules (IDs 100-125) + { + id: 100, + description: '[LFS] Inactive Flow', + formula: () => false, // Applied by LFS scanner + errorMessage: `This flow is inactive. Consider activating it or removing it from your org.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.USELESS + }, { + id: 101, + description: '[LFS] Process Builder', + formula: () => false, + errorMessage: `Time to migrate this process builder to flow!`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.USELESS + }, { + id: 102, + description: '[LFS] Missing Flow Description', + formula: () => false, + errorMessage: `This flow does not have a description. Add documentation about its purpose and usage.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.DOCUMENTATION + }, { + id: 103, + description: '[LFS] Outdated API Version', + formula: () => false, + errorMessage: `The API version of this flow is outdated. Update it to the newest version.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.API_VERSION + }, { + id: 104, + description: '[LFS] Unsafe Running Context', + formula: () => false, + errorMessage: `This flow runs in System Mode without Sharing, which can lead to unsafe data access.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.SECURITY + }, { + id: 105, + description: '[LFS] SOQL Query In Loop', + formula: () => false, + errorMessage: `This flow has SOQL queries inside loops. Consolidate queries at the end of the flow to avoid governor limits.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 106, + description: '[LFS] DML Statement In Loop', + formula: () => false, + errorMessage: `This flow has DML operations inside loops. Consolidate DML at the end to avoid governor limits.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 107, + description: '[LFS] Action Calls In Loop', + formula: () => false, + errorMessage: `This flow has action calls inside loops. Bulkify apex calls using collection variables.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 108, + description: '[LFS] Hardcoded Id', + formula: () => false, + errorMessage: `This flow contains hardcoded IDs which are org-specific. Use variables or merge fields instead.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.HARDCODED_ID + }, { + id: 109, + description: '[LFS] Hardcoded Url', + formula: () => false, + errorMessage: `This flow contains hardcoded URLs. Use $API formulas or custom labels instead.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.HARDCODED_URL + }, { + id: 110, + description: '[LFS] Missing Null Handler', + formula: () => false, + errorMessage: `This flow has Get Records operations without null checks. Use decision elements to validate results.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 111, + description: '[LFS] Missing Fault Path', + formula: () => false, + errorMessage: `This flow has DML or action operations without fault handlers. Add fault paths for better error handling.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 112, + description: '[LFS] Recursive After Update', + formula: () => false, + errorMessage: `This after-update flow modifies the same record that triggered it, risking recursion. Use before-save flows instead.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 113, + description: '[LFS] Duplicate DML Operation', + formula: () => false, + errorMessage: `This flow allows navigation back after DML operations, which may cause duplicate changes.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 114, + description: '[LFS] Get Record All Fields', + formula: () => false, + errorMessage: `This flow uses Get Records with "all fields". Specify only needed fields for better performance.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 115, + description: '[LFS] Record ID as String', + formula: () => false, + errorMessage: `This flow uses a String recordId variable. Modern flows can receive the entire record object, eliminating Get Records queries.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 116, + description: '[LFS] Unconnected Element', + formula: () => false, + errorMessage: `This flow has unconnected elements that are not in use. Remove them to maintain clarity.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.USELESS + }, { + id: 117, + description: '[LFS] Unused Variable', + formula: () => false, + errorMessage: `This flow has unused variables. Remove them to maintain efficiency.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.USELESS + }, { + id: 118, + description: '[LFS] Copy API Name', + formula: () => false, + errorMessage: `This flow has elements with copy-paste naming patterns like "Copy_X_Of_Element". Update API names for readability.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.DOCUMENTATION + }, { + id: 119, + description: '[LFS] Cyclomatic Complexity', + formula: () => false, + errorMessage: `This flow has high cyclomatic complexity. Consider breaking it into subflows or multiple trigger-ordered flows.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 120, + description: '[LFS] Same Record Field Updates', + formula: () => false, + errorMessage: `This before-save flow uses Update Records on $Record. Use direct assignment instead for better performance.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 121, + description: '[LFS] Missing Trigger Order', + formula: () => false, + errorMessage: `This record-triggered flow doesn't have a trigger order specified. Set explicit execution priority.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 122, + description: '[LFS] Missing Metadata Description', + formula: () => false, + errorMessage: `This flow has elements or variables without descriptions. Add documentation for better maintainability.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.DOCUMENTATION + }, { + id: 123, + description: '[LFS] Missing Filter Record Trigger', + formula: () => false, + errorMessage: `This record-triggered flow lacks filters on changed fields or entry conditions, causing unnecessary executions.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 124, + description: '[LFS] Transform Instead of Loop', + formula: () => false, + errorMessage: `This flow uses Loop + Assignment which could be replaced with Transform element (10x faster).`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.CODE_QUALITY + }, { + id: 125, + description: '[LFS] Missing Auto Layout', + formula: () => false, + errorMessage: `This flow doesn't use Auto-Layout mode. Enable it to keep your flow organized automatically.`, + badField: 'lfs_violation', + applicable: [ SFDC_Flow ], + category: SCORE_RULE_CATEGORIES.USER_ADOPTION } ]; Object.freeze(ALL_SCORE_RULES); diff --git a/build/src/api/dataset/orgcheck-api-dataset-flows.js b/build/src/api/dataset/orgcheck-api-dataset-flows.js index 951cf723..41f76f66 100644 --- a/build/src/api/dataset/orgcheck-api-dataset-flows.js +++ b/build/src/api/dataset/orgcheck-api-dataset-flows.js @@ -5,6 +5,7 @@ import { Processor } from '../core/orgcheck-api-processor'; import { SalesforceMetadataTypes } from '../core/orgcheck-api-salesforce-metadatatypes'; import { SalesforceManagerIntf } from '../core/orgcheck-api-salesforcemanager'; import { SFDC_Flow, SFDC_FlowVersion } from '../data/orgcheck-api-data-flow'; +import { LFSScanner } from '../scanner/orgcheck-api-lfs-scanner'; export class DatasetFlows extends Dataset { @@ -160,6 +161,21 @@ export class DatasetFlows extends Dataset { flowDefinition.currentVersionRef = activeFlowVersion; }); + // Scan flows with Lightning Flow Scanner + logger?.log(`Scanning ${records.length} flows with Lightning Flow Scanner...`); + const lfsViolations = await LFSScanner.scanFlows(records, sfdcManager); + + // Apply LFS violations to flow definitions + if (lfsViolations.size > 0) { + logger?.log(`Applying ${lfsViolations.size} LFS violation sets to flow definitions...`); + await Processor.forEach(flowDefinitions, (/** @type {SFDC_Flow} */ flowDefinition) => { + const violations = lfsViolations.get(flowDefinition.currentVersionId); + if (violations) { + LFSScanner.applyViolations(flowDefinition, violations); + } + }); + } + // Compute the score of all definitions await Processor.forEach(flowDefinitions, (/** @type {SFDC_Flow} */ flowDefinition) => flowDefinitionDataFactory.computeScore(flowDefinition)); diff --git a/build/src/api/scanner/orgcheck-api-lfs-scanner.js b/build/src/api/scanner/orgcheck-api-lfs-scanner.js new file mode 100644 index 00000000..ce8f5c45 --- /dev/null +++ b/build/src/api/scanner/orgcheck-api-lfs-scanner.js @@ -0,0 +1,153 @@ +/** + * @description Lightning Flow Scanner integration for OrgCheck + * Scans flows using the LFS_Core.js static resource + */ + +// LFS rule name to OrgCheck rule ID mapping (starting at 100 to avoid conflicts) +const LFS_RULE_TO_ORGCHECK_ID = { + 'InactiveFlow': 100, + 'ProcessBuilder': 101, + 'FlowDescription': 102, + 'APIVersion': 103, + 'UnsafeRunningContext': 104, + 'SOQLQueryInLoop': 105, + 'DMLStatementInLoop': 106, + 'ActionCallsInLoop': 107, + 'HardcodedId': 108, + 'HardcodedUrl': 109, + 'MissingNullHandler': 110, + 'MissingFaultPath': 111, + 'RecursiveAfterUpdate': 112, + 'DuplicateDMLOperation': 113, + 'GetRecordAllFields': 114, + 'RecordIdAsString': 115, + 'UnconnectedElement': 116, + 'UnusedVariable': 117, + 'CopyAPIName': 118, + 'CyclomaticComplexity': 119, + 'SameRecordFieldUpdates': 120, + 'TriggerOrder': 121, + 'MissingMetadataDescription': 122, + 'MissingFilterRecordTrigger': 123, + 'TransformInsteadOfLoop': 124, + 'AutoLayout': 125 +}; + +export class LFSScanner { + + /** + * @description Scan flows using Lightning Flow Scanner + * @param {Array} flowRecords - Flow metadata records from Tooling API + * @param {any} sfdcManager - Salesforce manager instance + * @returns {Promise>>} Map of flow version ID to LFS violations + */ + static async scanFlows(flowRecords, sfdcManager) { + try { + // Load LFS_Core.js from static resource + const lfsCore = await this.loadLFSCore(sfdcManager); + if (!lfsCore) { + console.warn('LFS_Core.js not available, skipping LFS scanning'); + return new Map(); + } + + const { Flow, scan } = lfsCore; + + // Convert flow records to LFS format + const lfsFlows = flowRecords.map(record => ({ + uri: record.Id, + flow: new Flow(record.FullName, record.Metadata) + })); + + // Scan flows + const scanResults = scan(lfsFlows); + + // Map results: flowVersionId -> violations + return this.mapResults(scanResults, sfdcManager); + + } catch (error) { + console.error('Error scanning flows with LFS:', error); + return new Map(); + } + } + + /** + * @description Load LFS_Core.js static resource + * @param {any} sfdcManager - Salesforce manager instance + * @returns {Promise} LFS core module or null + */ + static async loadLFSCore(sfdcManager) { + try { + // In browser environment, check if LFS is already loaded + if (typeof window !== 'undefined' && window.lightningflowscanner) { + return window.lightningflowscanner; + } + + // In Node.js/Salesforce backend, we'd load from static resource + // For now, return null as this needs platform-specific implementation + return null; + + } catch (error) { + console.error('Error loading LFS_Core:', error); + return null; + } + } + + /** + * @description Map LFS scan results to OrgCheck format + * @param {Array} scanResults - LFS scan results + * @param {any} sfdcManager - Salesforce manager instance + * @returns {Map>} Map of flow version ID to violations + */ + static mapResults(scanResults, sfdcManager) { + const violationsMap = new Map(); + + for (const result of scanResults) { + const flowId = result.flow.uri; + const violations = []; + + for (const ruleResult of result.ruleResults) { + if (!ruleResult.occurs) continue; + + const ruleId = LFS_RULE_TO_ORGCHECK_ID[ruleResult.ruleName]; + if (!ruleId) continue; // Skip unmapped rules + + for (const detail of ruleResult.details || []) { + violations.push({ + ruleId: ruleId, + ruleName: ruleResult.ruleName, + severity: ruleResult.severity, + element: detail.name, + type: detail.type, + description: ruleResult.ruleDefinition?.description || ruleResult.errorMessage + }); + } + } + + if (violations.length > 0) { + violationsMap.set(flowId, violations); + } + } + + return violationsMap; + } + + /** + * @description Apply LFS violations to flow definition score + * @param {any} flowDefinition - SFDC_Flow instance + * @param {Array} violations - LFS violations for this flow + */ + static applyViolations(flowDefinition, violations) { + if (!violations || violations.length === 0) return; + + for (const violation of violations) { + flowDefinition.score++; + flowDefinition.badReasonIds.push(violation.ruleId); + + // Map element to field if possible + const fieldName = violation.element || 'lfs_violation'; + if (!flowDefinition.badFields.includes(fieldName)) { + flowDefinition.badFields.push(fieldName); + } + } + } +} diff --git a/force-app/main/default/staticresources/LFS_Core.js b/force-app/main/default/staticresources/LFS_Core.js new file mode 100644 index 00000000..ceb63e6b --- /dev/null +++ b/force-app/main/default/staticresources/LFS_Core.js @@ -0,0 +1,74 @@ +(function(g,O){typeof exports=="object"&&typeof module<"u"?O(exports,require("fast-xml-parser")):typeof define=="function"&&define.amd?define(["exports","fast-xml-parser"],O):(g=typeof globalThis<"u"?globalThis:g||self,O(g.lightningflowscanner={},g["fast-xml-parser"]))})(this,(function(g,O){"use strict";class Q{constructor(){this.visitedElements=new Set}traverseFlow(e,t,s,r,n){let o=[e];for(;o.length>0;){const i=[];for(const c of o)if(!this.visitedElements.has(c)){const l=s.get(c);l&&(t(l),this.visitedElements.add(c),i.push(...this.findNextElements(c,r,s,n)))}if(i.length===0)break;o=i}}findNextElements(e,t,s,r){const n=[],o=t.get(e);if(o)for(const i of o)i!==r&&s.has(i)&&n.push(i);return n}}function Re(a,e=!1){return a.flatMap(t=>{const s=t.flow,r=s.name||s.label,n=s.fsPath?s.fsPath.replace(/\\/g,"/"):s.uri?s.uri.replace(/\\/g,"/"):`${s.name}.flow-meta.xml`;return t.ruleResults.filter(o=>{var i;return o.occurs&&((i=o.details)==null?void 0:i.length)}).flatMap(o=>o.details.map(i=>{const{details:c,...l}=i,u={...l,flowFile:n,flowName:r,ruleName:o.ruleName,severity:o.severity??"warning"};return e&&c&&("dataType"in c&&(u.dataType=c.dataType),"locationX"in c&&(u.locationX=String(c.locationX)),"locationY"in c&&(u.locationY=String(c.locationY)),"connectsTo"in c&&(u.connectsTo=Array.isArray(c.connectsTo)?c.connectsTo.join(", "):String(c.connectsTo)),"expression"in c&&(u.expression=c.expression)),u}))})}function Ee(a){const e=a.map(t=>{const s=t.flow,r=Ne(s);return{artifacts:[{location:{uri:r},sourceLanguage:"xml"}],results:t.ruleResults.filter(n=>n.occurs).flatMap(n=>n.details.map(o=>({level:ce(n.severity),locations:[{physicalLocation:{artifactLocation:{index:0,uri:r},region:xe(o)}}],message:{text:n.errorMessage||`${n.ruleName} in ${o.name}`},properties:{element:o.name,flow:s.name,type:o.type,...o.details},ruleId:n.ruleName}))),tool:{driver:{informationUri:"https://github.com/Flow-Scanner/lightning-flow-scanner",name:"Lightning Flow Scanner",rules:t.ruleResults.filter(n=>n.occurs).map(n=>({defaultConfiguration:{level:ce(n.severity)},fullDescription:{text:n.ruleDefinition.description||""},id:n.ruleName,shortDescription:{text:n.ruleDefinition.description||n.ruleName}})),version:"1.0.0"}}}});return JSON.stringify({$schema:"https://json.schemastore.org/sarif-2.1.0.json",runs:e,version:"2.1.0"},null,2)}function Ne(a){if(a.uri)return a.uri.replace(/\\/g,"/");if(a.fsPath){const e=a.fsPath.match(/(?:force-app|src)\/.+$/);return e?e[0].replace(/\\/g,"/"):a.fsPath.replace(/\\/g,"/")}return`flows/${a.name}.flow-meta.xml`}function xe(a){return{startColumn:a.columnNumber??1,startLine:a.lineNumber??1}}function ce(a){switch(a==null?void 0:a.toLowerCase()){case"info":case"note":return"note";case"warning":return"warning";default:return"warning"}}function Le(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}var le={exports:{}},T=le.exports={},L,F;function Y(){throw new Error("setTimeout has not been defined")}function J(){throw new Error("clearTimeout has not been defined")}(function(){try{typeof setTimeout=="function"?L=setTimeout:L=Y}catch{L=Y}try{typeof clearTimeout=="function"?F=clearTimeout:F=J}catch{F=J}})();function ue(a){if(L===setTimeout)return setTimeout(a,0);if((L===Y||!L)&&setTimeout)return L=setTimeout,setTimeout(a,0);try{return L(a,0)}catch{try{return L.call(null,a,0)}catch{return L.call(this,a,0)}}}function Fe(a){if(F===clearTimeout)return clearTimeout(a);if((F===J||!F)&&clearTimeout)return F=clearTimeout,clearTimeout(a);try{return F(a)}catch{try{return F.call(null,a)}catch{return F.call(this,a)}}}var D=[],V=!1,U,W=-1;function ke(){!V||!U||(V=!1,U.length?D=U.concat(D):W=-1,D.length&&de())}function de(){if(!V){var a=ue(ke);V=!0;for(var e=D.length;e;){for(U=D,D=[];++W1)for(var t=1;t2){var f=o.lastIndexOf("/");if(f!==o.length-1){f===-1?(o="",i=0):(o=o.slice(0,f),i=o.length-1-o.lastIndexOf("/")),c=d,l=0;continue}}else if(o.length===2||o.length===1){o="",i=0,c=d,l=0;continue}}n&&(o.length>0?o+="/..":o="..",i=2)}else o.length>0?o+="/"+r.slice(c+1,d):o=r.slice(c+1,d),i=d-c-1;c=d,l=0}else u===46&&l!==-1?++l:l=-1}return o}function t(r,n){var o=n.dir||n.root,i=n.base||(n.name||"")+(n.ext||"");return o?o===n.root?o+i:o+r+i:i}var s={resolve:function(){for(var n="",o=!1,i,c=arguments.length-1;c>=-1&&!o;c--){var l;c>=0?l=arguments[c]:(i===void 0&&(i=Z.cwd()),l=i),a(l),l.length!==0&&(n=l+"/"+n,o=l.charCodeAt(0)===47)}return n=e(n,!o),o?n.length>0?"/"+n:"/":n.length>0?n:"."},normalize:function(n){if(a(n),n.length===0)return".";var o=n.charCodeAt(0)===47,i=n.charCodeAt(n.length-1)===47;return n=e(n,!o),n.length===0&&!o&&(n="."),n.length>0&&i&&(n+="/"),o?"/"+n:n},isAbsolute:function(n){return a(n),n.length>0&&n.charCodeAt(0)===47},join:function(){if(arguments.length===0)return".";for(var n,o=0;o0&&(n===void 0?n=i:n+="/"+i)}return n===void 0?".":s.normalize(n)},relative:function(n,o){if(a(n),a(o),n===o||(n=s.resolve(n),o=s.resolve(o),n===o))return"";for(var i=1;ib){if(o.charCodeAt(u+p)===47)return o.slice(u+p+1);if(p===0)return o.slice(u+p)}else l>b&&(n.charCodeAt(i+p)===47?m=p:p===0&&(m=0));break}var S=n.charCodeAt(i+p),R=o.charCodeAt(u+p);if(S!==R)break;S===47&&(m=p)}var C="";for(p=i+m+1;p<=c;++p)(p===c||n.charCodeAt(p)===47)&&(C.length===0?C+="..":C+="/..");return C.length>0?C+o.slice(u+m):(u+=m,o.charCodeAt(u)===47&&++u,o.slice(u))},_makeLong:function(n){return n},dirname:function(n){if(a(n),n.length===0)return".";for(var o=n.charCodeAt(0),i=o===47,c=-1,l=!0,u=n.length-1;u>=1;--u)if(o=n.charCodeAt(u),o===47){if(!l){c=u;break}}else l=!1;return c===-1?i?"/":".":i&&c===1?"//":n.slice(0,c)},basename:function(n,o){if(o!==void 0&&typeof o!="string")throw new TypeError('"ext" argument must be a string');a(n);var i=0,c=-1,l=!0,u;if(o!==void 0&&o.length>0&&o.length<=n.length){if(o.length===n.length&&o===n)return"";var d=o.length-1,f=-1;for(u=n.length-1;u>=0;--u){var b=n.charCodeAt(u);if(b===47){if(!l){i=u+1;break}}else f===-1&&(l=!1,f=u+1),d>=0&&(b===o.charCodeAt(d)?--d===-1&&(c=u):(d=-1,c=f))}return i===c?c=f:c===-1&&(c=n.length),n.slice(i,c)}else{for(u=n.length-1;u>=0;--u)if(n.charCodeAt(u)===47){if(!l){i=u+1;break}}else c===-1&&(l=!1,c=u+1);return c===-1?"":n.slice(i,c)}},extname:function(n){a(n);for(var o=-1,i=0,c=-1,l=!0,u=0,d=n.length-1;d>=0;--d){var f=n.charCodeAt(d);if(f===47){if(!l){i=d+1;break}continue}c===-1&&(l=!1,c=d+1),f===46?o===-1?o=d:u!==1&&(u=1):o!==-1&&(u=-1)}return o===-1||c===-1||u===0||u===1&&o===c-1&&o===i+1?"":n.slice(o,c)},format:function(n){if(n===null||typeof n!="object")throw new TypeError('The "pathObject" argument must be of type Object. Received type '+typeof n);return t("/",n)},parse:function(n){a(n);var o={root:"",dir:"",base:"",ext:"",name:""};if(n.length===0)return o;var i=n.charCodeAt(0),c=i===47,l;c?(o.root="/",l=1):l=0;for(var u=-1,d=0,f=-1,b=!0,m=n.length-1,p=0;m>=l;--m){if(i=n.charCodeAt(m),i===47){if(!b){d=m+1;break}continue}f===-1&&(b=!1,f=m+1),i===46?u===-1?u=m:p!==1&&(p=1):u!==-1&&(p=-1)}return u===-1||f===-1||p===0||p===1&&u===f-1&&u===d+1?f!==-1&&(d===0&&c?o.base=o.name=n.slice(1,f):o.base=o.name=n.slice(d,f)):(d===0&&c?(o.name=n.slice(1,u),o.base=n.slice(1,f)):(o.name=n.slice(d,u),o.base=n.slice(d,f)),o.ext=n.slice(u,f)),d>0?o.dir=n.slice(0,d-1):c&&(o.dir="/"),o},sep:"/",delimiter:":",win32:null,posix:null};return s.posix=s,K=s,K}var z=De(),N=(a=>(a.ATTRIBUTE="attribute",a.VARIABLE="variable",a.RESOURCE="resource",a.NODE="node",a))(N||{});class B{constructor(e,t,s,r={}){this.element={},this.element=r,this.subtype=t,this.name=s,this.metaType=e}}class Me extends B{constructor(e,t,s){super(N.ATTRIBUTE,t,e,s)}}class w{constructor(e,t,s){this.element={},this.processed=!1,this.type=e,this.element=t,this.childName=s.childName?s.childName:void 0,this.childOf=s.childOf?s.childOf:void 0,t&&"targetReference"in t&&(this.reference=t.targetReference),t&&"connector"in t&&(this.connectorTargetReference=t.connector)}}const ee={actionCalls:{apex:"⚙️",emailAlert:"📧",emailSimple:"📧",submit:"⚡",default:"⚡"},assignments:{default:"🟰"},collectionProcessors:{FilterCollectionProcessor:"🔽",SortCollectionProcessor:"🔃",default:"📦"},customErrors:{default:"🚫"},decisions:{default:"🔀"},loops:{default:"🔁"},recordCreates:{default:"➕"},recordDeletes:{default:"🗑️"},recordLookups:{default:"🔍"},recordUpdates:{default:"🛠️"},screens:{default:"💻"},subflows:{default:"🔗"},transforms:{default:"♻️"}},he={actionCalls:{apex:"[A]",emailAlert:"[E]",emailSimple:"[E]",submit:"[!]",default:"[!]"},assignments:{default:"[=]"},collectionProcessors:{FilterCollectionProcessor:"[F]",SortCollectionProcessor:"[S]",default:"[C]"},customErrors:{default:"[X]"},decisions:{default:"[?]"},loops:{default:"[L]"},recordCreates:{default:"[+]"},recordDeletes:{default:"[-]"},recordLookups:{default:"[S]"},recordUpdates:{default:"[U]"},screens:{default:"[#]"},subflows:{default:"[>]"},transforms:{default:"[T]"}},$=class $ extends B{constructor(e,t,s){const r=t==="start"?"flowstart":e;super(N.NODE,t,r,s),this.connectors=[],this.label=s.label,this.description=s.description,this.locationX=s.locationX,this.locationY=s.locationY,this.extractTypeSpecificProperties(t,s),this.connectors=this.getConnectors(t,s),this.faultConnector=this.connectors.find(n=>n.type==="faultConnector")}static setIconConfig(e){$.iconConfig=e}static useAsciiIcons(){$.iconConfig=he}static useDefaultIcons(){$.iconConfig=ee}extractTypeSpecificProperties(e,t){switch(e){case"actionCalls":this.actionType=t.actionType,this.actionName=t.actionName;break;case"recordCreates":case"recordUpdates":case"recordDeletes":case"recordLookups":this.object=t.object,this.inputReference=t.inputReference,this.outputReference=t.outputReference;break;case"collectionProcessors":this.elementSubtype=t.elementSubtype,this.collectionReference=t.collectionReference;break;case"subflows":this.flowName=t.flowName;break;case"decisions":this.rules=Array.isArray(t.rules)?t.rules:t.rules?[t.rules]:[],this.defaultConnectorLabel=t.defaultConnectorLabel;break;case"loops":this.collectionReference=t.collectionReference,this.iterationOrder=t.iterationOrder;break;case"screens":this.fields=Array.isArray(t.fields)?t.fields:t.fields?[t.fields]:[],this.allowPause=t.allowPause,this.showFooter=t.showFooter;break}}getSummary(){var t,s;const e=[];switch(this.subtype){case"actionCalls":this.actionType&&e.push(this.prettifyValue(this.actionType)),this.actionName&&e.push(this.actionName);break;case"recordCreates":case"recordUpdates":case"recordDeletes":case"recordLookups":this.object&&e.push(this.object);break;case"collectionProcessors":this.elementSubtype&&e.push(this.prettifyValue(this.elementSubtype));break;case"decisions":e.push(`${((t=this.rules)==null?void 0:t.length)||0} rule${((s=this.rules)==null?void 0:s.length)!==1?"s":""}`);break;case"loops":this.collectionReference&&e.push(`Loop: ${this.collectionReference}`);break;case"subflows":this.flowName&&e.push(this.flowName);break}return this.description&&e.push(this.description.substring(0,50)+(this.description.length>50?"...":"")),e.join(" • ")}getIcon(){const e=$.iconConfig[this.subtype];if(!e){const r=$.iconConfig.default;return r&&"default"in r?r.default:"•"}const t=this.actionType||this.elementSubtype,s=e;return t&&s[t]?s[t]:s.default||"•"}getTypeLabel(){return{actionCalls:"Action",assignments:"Assignment",collectionProcessors:"Collection",customErrors:"Error",decisions:"Decision",loops:"Loop",recordCreates:"Create",recordDeletes:"Delete",recordLookups:"Get Records",recordUpdates:"Update",screens:"Screen",subflows:"Subflow",transforms:"Transform"}[this.subtype]||this.subtype}prettifyValue(e){return e.replace(/([A-Z])/g," $1").replace(/^./,t=>t.toUpperCase()).trim()}getConnectors(e,t){const s=[];if(e==="start"){if(t.connector&&s.push(new w("connector",t.connector,{})),Array.isArray(t.scheduledPaths))for(const r of(t==null?void 0:t.scheduledPaths)||[])r.connector&&s.push(new w("connector",r.connector,{childName:(r==null?void 0:r.name)??"AsyncAfterCommit",childOf:"scheduledPaths"}));else t.scheduledPaths&&s.push(new w("connector",t.scheduledPaths,{childName:t.scheduledPaths.name,childOf:"scheduledPaths"}));return s}else if(e==="decisions"){if(t.defaultConnector&&s.push(new w("defaultConnector",t.defaultConnector,{})),t.rules)if(Array.isArray(t.rules))for(const r of t.rules)r.connector&&s.push(new w("connector",r.connector,{childName:r.name,childOf:"rules"}));else t.rules.connector&&s.push(new w("connector",t.rules.connector,{childName:t.rules.name,childOf:"rules"}));return s}else{if(e==="assignments"||e==="transforms"||e==="customErrors")return t.connector?[new w("connector",t.connector,{})]:[];if(e==="loops")return t.nextValueConnector&&s.push(new w("nextValueConnector",t.nextValueConnector,{})),t.noMoreValuesConnector&&s.push(new w("noMoreValuesConnector",t.noMoreValuesConnector,{})),s;if(e==="actionCalls")return t.connector&&s.push(new w("connector",t.connector,{})),t.faultConnector&&s.push(new w("faultConnector",t.faultConnector,{})),s;if(e==="waits"){if(t.defaultConnector&&s.push(new w("defaultConnector",t.defaultConnector,{})),t.faultConnector&&s.push(new w("faultConnector",t.faultConnector,{})),Array.isArray(t.waitEvents))for(const r of t.waitEvents)r.connector&&s.push(new w("connector",r.connector,{childName:r.name,childOf:"waitEvents"}));return s}else return e==="recordCreates"?(t.connector&&s.push(new w("connector",t.connector,{})),t.faultConnector&&s.push(new w("faultConnector",t.faultConnector,{})),s):e==="recordDeletes"?(t.connector&&s.push(new w("connector",t.connector,{})),t.faultConnector&&s.push(new w("faultConnector",t.faultConnector,{})),s):e==="recordLookups"?(t.connector&&s.push(new w("connector",t.connector,{})),t.faultConnector&&s.push(new w("faultConnector",t.faultConnector,{})),s):e==="recordUpdates"?(t.connector&&s.push(new w("connector",t.connector,{})),t.faultConnector&&s.push(new w("faultConnector",t.faultConnector,{})),s):e==="subflows"?t.connector?[new w("connector",t.connector,{})]:[]:e==="screens"?t.connector?[new w("connector",t.connector,{})]:[]:t.connector?[new w("connector",t.connector,{})]:[]}}};$.iconConfig=ee;let x=$;class te extends B{constructor(e,t,s){super(N.RESOURCE,t,e,s)}}const se={subtypes:{variables:"📊",constants:"🔒",formulas:"🧮",choices:"📋",dynamicChoiceSets:"🔄"},boolean:{true:"✅",false:"⬜"}},me={subtypes:{variables:"[V]",constants:"[C]",formulas:"[F]",choices:"[CH]",dynamicChoiceSets:"[D]"},boolean:{true:"[X]",false:"[ ]"}},P=class P extends B{static setIconConfig(e){P.iconConfig=e}static useAsciiIcons(){P.iconConfig=me}static useDefaultIcons(){P.iconConfig=se}constructor(e,t,s){super(N.VARIABLE,t,e,s),this.dataType=s.dataType,this.isCollection=s.isCollection,this.isInput=s.isInput,this.isOutput=s.isOutput,this.objectType=s.objectType,this.description=s.description,t==="constants"?this.value=s.value:t==="formulas"&&(this.value=s.expression)}getIcon(){return P.iconConfig.subtypes[this.subtype]||"📊"}getBooleanIcon(e){return e===!0?P.iconConfig.boolean.true:e===!1?P.iconConfig.boolean.false:""}getTypeLabel(){return{variables:"Variable",constants:"Constant",formulas:"Formula",choices:"Choice",dynamicChoiceSets:"Dynamic Choice"}[this.subtype]||this.subtype}toTableRow(){return`| ${[this.name,this.dataType||"",this.getBooleanIcon(this.isCollection),this.getBooleanIcon(this.isInput),this.getBooleanIcon(this.isOutput),this.objectType||"",this.description||""].join(" | ")} |`}toMarkdownTable(){let e=`| Property | Value | +|:---|:---| +`;return e+=`| Name | ${this.name} | +`,e+=`| Type | ${this.getIcon()} ${this.getTypeLabel()} | +`,this.dataType&&(e+=`| Data Type | ${this.dataType} | +`),this.objectType&&(e+=`| Object Type | ${this.objectType} | +`),this.isCollection!==void 0&&(e+=`| Collection | ${this.getBooleanIcon(this.isCollection)} | +`),this.isInput!==void 0&&(e+=`| Input | ${this.getBooleanIcon(this.isInput)} | +`),this.isOutput!==void 0&&(e+=`| Output | ${this.getBooleanIcon(this.isOutput)} | +`),this.value!==void 0&&(e+=`| Value | ${this.formatValue(this.value)} | +`),this.description&&(e+=`| Description | ${this.description} | +`),e}formatValue(e){return typeof e=="object"?JSON.stringify(e,null,2):String(e)}};P.iconConfig=se;let j=P;class ge{constructor(e,t,s){this.nodeMap=new Map,this.reachableFromStart=new Set,this.elementsInLoop=new Map,this.faultConnectors=new Map,this.normalConnectors=new Map,this.allConnectors=new Map,this.reverseConnectors=new Map,this.buildNodeMaps(e),this.buildConnectorMaps(e),s?this.addStartNodeConnectors(s):t&&this.addStartEdgeFromReference(t),this.computeLoopBoundaries(),t&&this.computeReachability(t)}addStartNodeConnectors(e){var s,r,n,o;const t="START";if(this.faultConnectors.set(t,new Set),this.normalConnectors.set(t,new Set),this.allConnectors.set(t,new Set),!(!e.connectors||e.connectors.length===0))for(const i of e.connectors){const c=((s=i.connectorTargetReference)==null?void 0:s.targetReference)??i.reference;c&&((r=this.normalConnectors.get(t))==null||r.add(c),(n=this.allConnectors.get(t))==null||n.add(c),this.reverseConnectors.has(c)||this.reverseConnectors.set(c,new Set),(o=this.reverseConnectors.get(c))==null||o.add(t))}}addStartEdgeFromReference(e){var s,r,n;const t="START";this.faultConnectors.set(t,new Set),this.normalConnectors.set(t,new Set),this.allConnectors.set(t,new Set),(s=this.normalConnectors.get(t))==null||s.add(e),(r=this.allConnectors.get(t))==null||r.add(e),this.reverseConnectors.has(e)||this.reverseConnectors.set(e,new Set),(n=this.reverseConnectors.get(e))==null||n.add(t)}buildNodeMaps(e){for(const t of e)this.nodeMap.set(t.name,t)}buildConnectorMaps(e){var t,s,r,n,o;for(const i of e)if(this.faultConnectors.set(i.name,new Set),this.normalConnectors.set(i.name,new Set),this.allConnectors.set(i.name,new Set),!(!i.connectors||i.connectors.length===0))for(const c of i.connectors){const l=((t=c.connectorTargetReference)==null?void 0:t.targetReference)??c.reference;l&&(c.type==="faultConnector"?(s=this.faultConnectors.get(i.name))==null||s.add(l):(r=this.normalConnectors.get(i.name))==null||r.add(l),(n=this.allConnectors.get(i.name))==null||n.add(l),this.reverseConnectors.has(l)||this.reverseConnectors.set(l,new Set),(o=this.reverseConnectors.get(l))==null||o.add(i.name))}}computeReachability(e){new Q().traverseFlow(e,s=>{this.reachableFromStart.add(s.name)},this.nodeMap,this.allConnectors)}computeLoopBoundaries(){var t,s;const e=Array.from(this.nodeMap.values()).filter(r=>r.subtype==="loops");for(const r of e){const n=((s=(t=r.element)==null?void 0:t.noMoreValuesConnector)==null?void 0:s.targetReference)??r.name;new Q().traverseFlow(r.name,i=>{this.elementsInLoop.set(i.name,r.name)},this.nodeMap,this.allConnectors,n)}}isReachable(e){return this.reachableFromStart.has(e)}getReachableElements(){return new Set(this.reachableFromStart)}isInLoop(e){return this.elementsInLoop.has(e)}getContainingLoop(e){return this.elementsInLoop.get(e)}getLoopElements(e){const t=new Set;for(const[s,r]of this.elementsInLoop)r===e&&t.add(s);return t}hasFaultConnector(e){const t=this.faultConnectors.get(e);return t?t.size>0:!1}getFaultTargets(e){return Array.from(this.faultConnectors.get(e)||[])}getNextElements(e){return Array.from(this.normalConnectors.get(e)||[])}getAllNextElements(e){return Array.from(this.allConnectors.get(e)||[])}getPreviousElements(e){return Array.from(this.reverseConnectors.get(e)||[])}getNode(e){return this.nodeMap.get(e)}isPartOfFaultHandling(e){return this.getPreviousElements(e).some(s=>{const r=this.faultConnectors.get(s);return(r==null?void 0:r.has(e))??!1})}getLoopNodes(){return Array.from(this.nodeMap.values()).filter(e=>e.subtype==="loops")}forEachReachable(e){for(const t of this.reachableFromStart){const s=this.nodeMap.get(t);s&&e(s)}}toMermaid(e={}){let t="";const s=this.generateMermaidDiagram(e);return e.includeMarkdownDocs?t=this.generateFullMarkdownDoc(s,e):t=`\`\`\`mermaid +${s} +\`\`\``,t}generateMermaidDiagram(e){let t=`flowchart TB +`;t+=this.generateStartNode(e.flowMetadata)+` + +`;for(const[s,r]of this.nodeMap){const n=r.getIcon(),o=r.getTypeLabel(),i=e.includeDetails?r.getSummary():"";let c=`${n} ${o}
${r.label||r.name}`;i&&(c+=`
${i}`);const l=this.getNodeShape(r.subtype);t+=` ${s}${l[0]}"${c}"${l[1]}:::${r.subtype} +`}return t+=` +`,t+=this.generateEdges()+` +`,t+=this.generateLoopSubgraphs()+` +`,t+=this.generateMermaidStyles(),t}generateStartNode(e){if(!e)return'START(["🚀 START"]):::startClass';let t="🚀 START";if(e.processType==="Flow"?t+="
Screen Flow":e.processType==="AutoLaunchedFlow"?(t+="
AutoLaunched Flow",e.triggerType&&(t+=`
Type: ${this.prettifyValue(e.triggerType)}`)):e.object&&(t+=`
${e.object}`,e.triggerType&&(t+=`
Type: ${this.prettifyValue(e.triggerType)}`)),e.status){const s=e.status==="Active"?"✅":"⚠️";t+=`
${s} ${e.status}`}return`START(["${t}"]):::startClass`}getNodeShape(e){const t={decisions:["{","}"],loops:["{{","}}"],collectionProcessors:["{{","}}"],transforms:["{{","}}"],screens:["([","])"],recordCreates:["[(",")]"],recordDeletes:["[(",")]"],recordLookups:["[(",")]"],recordUpdates:["[(",")]"],subflows:["[[","]]"],assignments:["[\\","/]"],default:["(",")"]};return t[e]||t.default}generateEdges(){let e="";for(const[s,r]of this.allConnectors)for(const n of r)e+=` ${s} --> ${n} +`;for(const[s,r]of this.faultConnectors)for(const n of r)e+=` ${s} -. Fault .-> ${n} +`;const t=this.findEndNodes();for(const s of t)e+=` ${s}(( END )):::endClass +`;return e}findEndNodes(){const e=new Set;for(const[t,s]of this.allConnectors)for(const r of s)this.nodeMap.has(r)||e.add(r);return e}generateLoopSubgraphs(){let e="";for(const t of this.getLoopNodes()){const s=this.getLoopElements(t.name);if(s.size>0){e+=` subgraph "${t.label||t.name} Loop" +`;for(const r of s)e+=` ${r} +`;e+=` end +`}}return e}generateMermaidStyles(){const e={actionCalls:{fill:"#D4E4FC",color:"black"},assignments:{fill:"#FBEED7",color:"black"},collectionProcessors:{fill:"#F0E3FA",color:"black"},customErrors:{fill:"#FFE9E9",color:"black"},decisions:{fill:"#FDEAF6",color:"black"},loops:{fill:"#FDEAF6",color:"black"},recordCreates:{fill:"#FFF8C9",color:"black"},recordDeletes:{fill:"#FFF8C9",color:"black"},recordLookups:{fill:"#EDEAFF",color:"black"},recordUpdates:{fill:"#FFF8C9",color:"black"},screens:{fill:"#DFF6FF",color:"black"},subflows:{fill:"#D4E4FC",color:"black"},transforms:{fill:"#FDEAF6",color:"black"},startClass:{fill:"#D9F2E6",color:"black"},endClass:{fill:"#F9BABA",color:"black"}};let t="";for(const[s,r]of Object.entries(e))t+=` classDef ${s} fill:${r.fill},color:${r.color},stroke:#333,stroke-width:2px +`;return t}generateNodeDetailsMarkdown(e){let t=`## Flow Nodes Details + +`;e&&(t+=`
NODE DETAILS (expand to view) + +`);for(const[s,r]of this.nodeMap)t+=`### ${s} + +`,t+=this.nodeToMarkdownTable(r),t+=` +`;return e&&(t+=`
+ +`),t}nodeToMarkdownTable(e){let t=`| Property | Value | +|:---|:---| +`;if(e.label&&(t+=`| Label | ${e.label} | +`),t+=`| Type | ${e.getTypeLabel()} | +`,e.actionType&&(t+=`| Action Type | ${this.prettifyValue(e.actionType)} | +`),e.actionName&&(t+=`| Action Name | ${e.actionName} | +`),e.object&&(t+=`| Object | ${e.object} | +`),e.flowName&&(t+=`| Subflow | ${e.flowName} | +`),e.collectionReference&&(t+=`| Collection | ${e.collectionReference} | +`),e.elementSubtype&&(t+=`| Subtype | ${this.prettifyValue(e.elementSubtype)} | +`),e.rules&&e.rules.length>0){t+=`| Rules | ${e.rules.length} | +`;for(const s of e.rules){const r=Array.isArray(s.conditions)?s.conditions:s.conditions?[s.conditions]:[];t+=`| ↳ ${s.label||s.name} | ${r.length} condition(s) | +`}}return e.fields&&e.fields.length>0&&(t+=`| Fields | ${e.fields.length} | +`),e.description&&(t+=`| Description | ${e.description} | +`),e.faultConnector&&(t+=`| Has Fault Handler | ✅ | +`),t}prettifyValue(e){return e.replace(/([A-Z])/g," $1").replace(/^./,t=>t.toUpperCase()).trim()}generateFullMarkdownDoc(e,t){let s="";return s+=`## Flow Diagram + +`,s+="```mermaid\n",s+=e,s+="\n```\n\n",t.includeDetails&&(s+=this.generateNodeDetailsMarkdown(t.collapsedDetails)),s}toPlantUML(){let e=`@startuml +skinparam activityBackgroundColor #D4E4FC +`;for(const[t,s]of this.nodeMap)e+=`activity "${s.subtype}: ${t}" as ${t} +`;for(const[t,s]of this.allConnectors)for(const r of s)e+=`${t} --> ${r} +`;for(const t of this.getLoopNodes()){e+=`partition "${t.name} Loop" { +`;const s=this.getLoopElements(t.name);for(const r of s)e+=` ${r} +`;e+=`} +`}return e+="@enduml",e}}const E=class E{constructor(e,t){if(this.elements=[],this.label="",this.name="unnamed",this.processType="AutoLaunchedFlow",this.type="",this.status="",e){this.uri=e,typeof Z<"u"&&typeof Z.cwd=="function"&&(this.fsPath=z.resolve(e));let s=z.basename(z.basename(e),z.extname(e));s.includes(".")&&(s=s.split(".")[0]),this.name=s||"unnamed"}t&&(typeof t=="object"&&t!==null&&"Flow"in t?this.xmldata=t.Flow:this.xmldata=t,this.preProcessNodes())}get graph(){if(!this._graph){const e=this.elements.filter(t=>t instanceof x);this.startReference||(this.startReference=this.findStart()),this._graph=new ge(e,this.startReference,this.startNode)}return this._graph}static from(e){if(e instanceof E)return e;const t=Object.create(E.prototype);return Object.assign(t,e),t.toXMLString||(t.toXMLString=()=>""),t}preProcessNodes(){if(!this.xmldata)return;this.label=this.xmldata.label||"",this.interviewLabel=this.xmldata.interviewLabel,this.processType=this.xmldata.processType||"AutoLaunchedFlow",this.type=this.processType,this.processMetadataValues=this.xmldata.processMetadataValues,this.startElementReference=this.xmldata.startElementReference,this.status=this.xmldata.status||"Draft",this.triggerOrder=this.xmldata.triggerOrder;const e=[];for(const s in this.xmldata){if(s.startsWith("@_")||s==="@xmlns")continue;const r=this.xmldata[s];if(s==="start"){Array.isArray(r)&&r.length>0?this.startNode=new x(r[0].name||"start","start",r[0]):Array.isArray(r)||(this.startNode=new x(r.name||"start","start",r));continue}E.ATTRIBUTE_TAGS.includes(s)?this.processNodeType(r,s,e,Me):E.VARIABLE_TAGS.includes(s)?this.processNodeType(r,s,e,j):E.NODE_TAGS.includes(s)?this.processNodeType(r,s,e,x):E.RESOURCE_TAGS.includes(s)&&this.processNodeType(r,s,e,te)}this.elements=e,this.startReference=this.findStart();const t=e.filter(s=>s instanceof x);this._graph=new ge(t,this.startReference,this.startNode)}visualize(e="mermaid",t={}){var s,r,n,o,i;if(e==="mermaid")return this.graph.toMermaid({...t,flowMetadata:{label:this.label,processType:this.processType,status:this.status,description:(s=this.xmldata)==null?void 0:s.description,triggerType:(n=(r=this.startNode)==null?void 0:r.element)==null?void 0:n.triggerType,object:(i=(o=this.startNode)==null?void 0:o.element)==null?void 0:i.object}});if(e==="plantuml")return this.graph.toPlantUML();throw new Error("Unsupported format")}processNodeType(e,t,s,r){if(Array.isArray(e))for(const n of e)s.push(new r(n.name,t,n));else s.push(new r(e.name,t,e))}findStart(){var e,t;if(this.startElementReference)return this.startElementReference;if(this.startNode&&this.startNode.connectors&&this.startNode.connectors.length>0){const s=this.startNode.connectors[0];if(s.reference)return s.reference}if((e=this.startNode)!=null&&e.element){const s=this.startNode.element.scheduledPaths;if(s){const r=Array.isArray(s)?s:[s];if(r.length>0&&((t=r[0])!=null&&t.connector)){const n=r[0].connector.targetReference;if(n)return n}}}return""}toXMLString(){try{return this.generateDoc()}catch(e){const t=e instanceof Error?e.message:String(e);return console.warn(`Unable to write xml, caught an error: ${t}`),""}}generateDoc(){const e="http://soap.sforce.com/2006/04/metadata",t={attributeNamePrefix:"@_",format:!0,ignoreAttributes:!1,suppressBooleanAttributes:!1,suppressEmptyNode:!1},s=new O.XMLBuilder(t),r={...this.xmldata};r["@_xmlns"]||(r["@_xmlns"]=e),r["@_xmlns:xsi"]||(r["@_xmlns:xsi"]="http://www.w3.org/2001/XMLSchema-instance");const n={Flow:r};return s.build(n)}};E.ATTRIBUTE_TAGS=["description","apiVersion","processMetadataValues","processType","interviewLabel","label","status","runInMode","startElementReference","isTemplate","fullName","timeZoneSidKey","isAdditionalPermissionRequiredToRun","migratedFromWorkflowRuleName","triggerOrder","environments","segment"],E.NODE_TAGS=["actionCalls","apexPluginCalls","assignments","collectionProcessors","decisions","loops","orchestratedStages","recordCreates","recordDeletes","recordLookups","recordUpdates","recordRollbacks","screens","steps","subflows","waits","transforms","customErrors"],E.RESOURCE_TAGS=["textTemplates","stages"],E.VARIABLE_TAGS=["choices","constants","dynamicChoiceSets","formulas","variables"];let _=E;class A extends B{constructor(e,t,s){super(N.ATTRIBUTE,t,e),this.expression=s}}const k=class k{};k.autolaunchedType="AutoLaunchedFlow",k.backEndTypes=[k.autolaunchedType,"CustomEvent","InvocableProcess","Orchestrator","EvaluationFlow","ActionCadenceAutolaunchedFlow"],k.processBuilder=["Workflow"],k.surveyTypes=["Survey"],k.unsupportedTypes=["CheckoutFlow","FSCLending","FSCLending","LoyaltyManagementFlow"],k.visualTypes=["Flow","IndividualObjectLinkingFlow","LoginFlow","RoutingFlow","Appointments","ActionCadenceStepFlow","ContactRequestFlow","ContactRequestFlow","CustomerLifecycle","FieldServiceMobile","FieldServiceWeb","SurveyEnrich"],k.allTypes=function(){return[...this.backEndTypes,...this.visualTypes,...this.surveyTypes]};let h=k;class re{constructor(e,t,s){this.uri=e,this.flow=t,s&&(this.errorMessage=s)}}class v{constructor(e,t){this.docRefs=[],this.name=e.name,this.supportedTypes=e.supportedTypes,this.label=e.label,this.description=e.description,this.uri=`https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner/tree/main/src/main/rules/${e.name}.ts`,this.docRefs=e.docRefs;const s=this.check;if(typeof s=="function"){const r=s.toString();this.isConfigurable=/options[.\?]/.test(r)}else this.isConfigurable=!1;this.severity=(t==null?void 0:t.severity)??"error",this.suppressionElement=e.suppressionElement}execute(e,t,s=[]){if(s.includes("*"))return new q(this,[]);const r=new Set(s);let n=this.check(e,t,r);return n=n.filter(o=>!r.has(o.name)),new q(this,n)}isSuppressed(e,t){return t.has(e)}getStartNode(e){return e.startNode}getStartReference(e){return e.startReference||void 0}findStartIndex(e,t){const s=this.getStartReference(e);return s?t.findIndex(r=>r.name===s):-1}getStartProperty(e,t){var s,r;if((s=e.startNode)!=null&&s.element)return(r=e.startNode.element)==null?void 0:r[t]}}class q{constructor(e,t,s){this.details=[],this.ruleDefinition=e,this.ruleName=e.name,this.severity=e.severity?e.severity:"error",this.occurs=!1,this.details=t,t.length>0&&(this.occurs=!0),s&&(this.errorMessage=s)}}class ye{constructor(e,t){this.flow=e,this.ruleResults=t}}class y{constructor(e){var t;if(this.name=e.name,this.metaType=e.metaType,this.type=e.subtype,this.lineNumber=1,this.columnNumber=1,e.metaType===N.VARIABLE){const s=e;this.details={dataType:s.dataType}}else if(e.metaType===N.NODE){const s=e;this.details={connectsTo:(t=s.connectors)==null?void 0:t.map(r=>r.reference),locationX:s.locationX,locationY:s.locationY}}else if(e.metaType===N.ATTRIBUTE){const s=e;this.details={expression:s.expression}}}}function Pe(a,e){if(!e||a.length===0)return;const t=e.split(` +`),s=_.ATTRIBUTE_TAGS;for(const r of a){if(r.metaType!==N.ATTRIBUTE){for(let n=0;n${r.name}`)){r.lineNumber=n+1,r.columnNumber=t[n].indexOf(r.name)+1;break}}if(r.metaType===N.ATTRIBUTE){const n=r.type;if(s.includes(n)){for(let o=0;o`)){r.lineNumber=o+1,r.columnNumber=t[o].indexOf(`<${n}>`)+1;break}}}}}function _e(a,e,t){return a[t]=e.map(s=>s.element),a}function $e(a){let e={};for(const t of a){const s=t.subtype,r=a.filter(n=>s===n.subtype);e=_e(e,r,s)}return e}function Ue(a){const e=[];for(const t of a){if(!t.ruleResults||t.ruleResults.length===0)continue;const s=t.ruleResults.filter(o=>o.ruleName==="UnusedVariable"&&o.occurs||o.ruleName==="UnconnectedElement"&&o.occurs);if(s.length===0)continue;const r=Oe(t.flow,s);r.elements&&r.elements.length>0&&(t.flow=r,e.push(t))}return e}function Oe(a,e){var c,l,u;const t=e.find(d=>d.ruleName==="UnusedVariable"),s=new Set(((c=t==null?void 0:t.details)==null?void 0:c.map(d=>d.name))??[]),r=e.find(d=>d.ruleName==="UnconnectedElement"),n=new Set(((l=r==null?void 0:r.details)==null?void 0:l.map(d=>d.name))??[]),o=((u=a.elements)==null?void 0:u.filter(d=>{switch(d.metaType){case"attribute":case"resource":return!0;case"node":{const f=d;return!n.has(f.name)}case"variable":{const f=d;return!s.has(f.name)}default:return!1}}))??[],i=$e(o);return new _(a.fsPath,i)}class ne extends v{constructor(e,t){super(e,t)}check(e,t,s){const r=e.graph.getLoopNodes();return r.length?this.findStatementsInLoops(e,r).filter(i=>!s.has(i.name)).map(i=>new y(i)):[]}findLoopElements(e){return e.graph.getLoopNodes()}findLoopEnd(e){var t,s;return((s=(t=e.element)==null?void 0:t.noMoreValuesConnector)==null?void 0:s.targetReference)??e.name}findStatementsInLoops(e,t){const s=[],r=this.getStatementTypes();for(const n of t){const o=e.graph.getLoopElements(n.name);for(const i of o){const c=e.graph.getNode(i);c&&r.includes(c.subtype)&&s.push(c)}}return s}}class Ve extends ne{constructor(){super({description:"To prevent exceeding Apex governor limits, it is advisable to consolidate and bulkify your apex calls, utilize a single action call containing a collection variable at the end of the loop.",docRefs:[{label:"Invocable Method Considerations",path:"https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableMethod.htm"}],label:"Action Call In Loop",name:"ActionCallsInLoop",supportedTypes:h.backEndTypes},{severity:"error"})}getStatementTypes(){return["actionCalls","apexPluginCalls"]}}class Be extends v{constructor(){super({name:"APIVersion",label:"Outdated API Version",description:"Introducing newer API components may lead to unexpected issues with older versions of Flows, as they might not align with the underlying mechanics. Starting from API version 50.0, the 'Api Version' attribute has been readily available on the Flow Object. To ensure smooth operation and reduce discrepancies between API versions, it is strongly advised to regularly update and maintain them.",supportedTypes:h.allTypes(),docRefs:[]})}check(e,t,s){let r=null;if(e.xmldata.apiVersion&&(r=+e.xmldata.apiVersion),!r)return[new y(new A("API Version <49","apiVersion","<49"))];if(t!=null&&t.expression){const n=t.expression.match(/^\s*(>=|<=|>|<|===|!==)\s*(\d+)\s*$/);if(!n)return[new y(new A("Invalid API rule expression","apiVersion",t.expression))];const[,o,i]=n,c=parseFloat(i);let l=!0;switch(o){case">":l=r>c;break;case"<":l=r=":l=r>=c;break;case"<=":l=r<=c;break;case"===":l=r===c;break;case"!==":l=r!==c;break}if(!l)return[new y(new A(`${r}`,"apiVersion",t.expression))]}return[]}}class je extends v{constructor(){super({name:"AutoLayout",label:"Auto-Layout Mode",description:"With Canvas Mode set to Auto-Layout, Elements are spaced, connected, and aligned automatically, keeping your Flow neatly organized thus saving you time.",supportedTypes:h.allTypes(),docRefs:[]},{severity:"note"})}check(e,t){var n;if(!e.processMetadataValues)return[];const s=e.xmldata.processMetadataValues.find(o=>o.name==="CanvasMode");return(s==null?void 0:s.value)&&typeof s.value=="object"&&s.value.stringValue==="AUTO_LAYOUT_CANVAS"?[]:[new y(new A(((n=s==null?void 0:s.value)==null?void 0:n.stringValue)??"undefined","CanvasMode","!== AUTO_LAYOUT_CANVAS"))]}}class ze extends v{constructor(){super({name:"CopyAPIName",label:"Copy API Name",description:"Maintaining multiple elements with a similar name, like 'Copy_X_Of_Element,' can diminish the overall readability of your Flow. When copying and pasting these elements, it's crucial to remember to update the API name of the newly created copy.",supportedTypes:h.allTypes(),docRefs:[]})}check(e){return e.elements.filter(r=>r instanceof x).filter(r=>/Copy_[0-9]+_of_[A-Za-z0-9]+/.test(r.name)).map(r=>new y(r))}}class qe extends v{constructor(){super({name:"CyclomaticComplexity",label:"Cyclomatic Complexity",description:"The number of loops and decision rules, plus the number of decisions. Use a combination of 1) subflows and 2) breaking flows into multiple concise trigger ordered flows, to reduce the cyclomatic complexity within a single flow, ensuring maintainability and simplicity.",supportedTypes:h.backEndTypes,docRefs:[{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.",path:"https://en.wikipedia.org/wiki/Cyclomatic_complexity"}]},{severity:"note"}),this.defaultThreshold=25,this.cyclomaticComplexityUnit=0}check(e,t){var i,c;const s=(t==null?void 0:t.threshold)||this.defaultThreshold;let r=1;const n=(i=e==null?void 0:e.elements)==null?void 0:i.filter(l=>l.subtype==="decisions"),o=(c=e==null?void 0:e.elements)==null?void 0:c.filter(l=>l.subtype==="loops");for(const l of n||[]){const u=l.element.rules;r+=Array.isArray(u)?u.length+1:1}return r+=(o==null?void 0:o.length)??0,this.cyclomaticComplexityUnit=r,r>s?[new y(new A(`${r}`,"CyclomaticComplexity",`>${s}`))]:[]}}class We extends ne{constructor(){super({description:"To prevent exceeding Apex governor limits, it is advisable to consolidate all your database operations, including record creation, updates, or deletions, at the conclusion of the flow.",docRefs:[{label:"Flow Best Practices",path:"https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5"}],label:"DML Statement In A Loop",name:"DMLStatementInLoop",supportedTypes:h.backEndTypes},{severity:"error"})}getStatementTypes(){return["recordDeletes","recordUpdates","recordCreates"]}}class He extends v{constructor(){super({name:"DuplicateDMLOperation",label:"Duplicate DML Operation",description:"When the flow executes database changes between screens, users must not be allowed to navigate back, or duplicate DML operations may occur.",supportedTypes:h.visualTypes,docRefs:[]})}check(e,t,s){const r=e.graph,n=e.startReference;if(!n)return[];const o=[],i=new Set,c=[{name:n,seenDML:!1}];for(;c.length>0;){const{name:l,seenDML:u}=c.pop(),d=`${l}:${u}`;if(i.has(d))continue;i.add(d);const f=r.getNode(l);if(!f)continue;let b=u||this.isDML(f);b&&f.subtype==="screens"&&f.element.allowBack==="true"&&f.element.showFooter==="true"&&!s.has(f.name)&&o.push(new y(f)),b&&f.subtype==="screens"&&f.element.allowBack!=="true"&&(b=!1);for(const m of r.getNextElements(l))c.push({name:m,seenDML:b})}return o}isDML(e){return e.subtype==="recordCreates"||e.subtype==="recordUpdates"||e.subtype==="recordDeletes"}}class Ge extends v{constructor(){super({description:"Descriptions play a vital role in documentation. We highly recommend including details about where they are used and their intended purpose.",docRefs:[],label:"Missing Flow Description",name:"FlowDescription",supportedTypes:[...h.backEndTypes,...h.visualTypes]},{severity:"error"})}check(e,t,s){var r;return(r=e.xmldata)!=null&&r.description?[]:[new y(new A("undefined","description","!==null"))]}}class Xe extends v{constructor(){super({description:"The readability of a flow is of utmost importance. Establishing a naming convention for the Flow Name significantly enhances findability, searchability, and maintains overall consistency. It is advisable to include at least a domain and a brief description of the actions carried out in the flow, for instance, 'Service_OrderFulfillment'.",docRefs:[{label:"Naming your Flows is more critical than ever. By Stephen Church",path:"https://www.linkedin.com/posts/stephen-n-church_naming-your-flows-this-is-more-critical-activity-7099733198175158274-1sPx"}],label:"Flow Naming Convention",name:"FlowName",supportedTypes:h.allTypes()},{severity:"error"})}check(e,t,s){const r=(t==null?void 0:t.expression)??"[A-Za-z0-9]+_[A-Za-z0-9]+",n=e.name??"";return new RegExp(r).test(n)?[]:[new y(new A(n,"name",r))]}}class Qe extends v{constructor(){super({description:"Following the principle of least privilege (PoLP), avoid using Get Records with 'Automatically store all fields' unless necessary.",docRefs:[{label:"SOQL and SOSL | Best Practices for Deployments with Large Data Volumes",path:"https://developer.salesforce.com/docs/atlas.en-us.salesforce_large_data_volumes_bp.meta/salesforce_large_data_volumes_bp/ldv_deployments_best_practices_soql_and_sosl.htm"},{label:"Indexes | Best Practices",path:"https://developer.salesforce.com/docs/atlas.en-us.salesforce_large_data_volumes_bp.meta/salesforce_large_data_volumes_bp/ldv_deployments_infrastructure_indexes.htm"}],label:"Get Record All Fields",name:"GetRecordAllFields",supportedTypes:h.allTypes()},{severity:"warning"})}check(e,t,s){var o;return(((o=e.elements)==null?void 0:o.filter(i=>i.subtype==="recordLookups"))??[]).filter(i=>{const c=i.element,l=typeof c=="object"&&"storeOutputAutomatically"in c&&c.storeOutputAutomatically,u=typeof c=="object"&&Array.isArray(c.queriedFields)&&c.queriedFields.length>0;return l&&!u}).map(i=>new y(i))}}class Ye extends v{constructor(){super({name:"HardcodedId",label:"Hardcoded Id",description:"Avoid hard-coding IDs as they are org-specific. Instead, pass them into variables at the start of the flow. You can achieve this by utilizing merge fields in URL parameters or employing a Get Records element.",supportedTypes:h.allTypes(),docRefs:[{label:"Flow Best Practices",path:"https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5"},{label:"Don't hard code Record Type IDs in Flow. By Stephen Church.",path:"https://www.linkedin.com/feed/update/urn:li:activity:6947530300012826624/"}]},{severity:"error"})}check(e,t,s){const r=/\b[a-zA-Z0-9]{5}0[a-zA-Z0-9]{9}(?:[a-zA-Z0-9]{3})?\b/g;return e.elements.filter(n=>r.test(JSON.stringify(n))).map(n=>new y(n))}}class Je extends v{constructor(){super({description:"Avoid hard-coding URLs as they are org-specific. Instead, use a $API formula (preferred) or you can use an environment-specific such as custom labels, custom metadata, or custom settings.",docRefs:[{label:"The Ultimate Guide to Salesforce Flow Best Practices",path:"https://admin.salesforce.com/blog/2021/the-ultimate-guide-to-flow-best-practices-and-standards"},{label:"Why You Should Avoid Hard Coding and Three Alternative Solutions",path:"https://admin.salesforce.com/blog/2021/why-you-should-avoid-hard-coding-and-three-alternative-solutions"}],label:"Hardcoded Url",name:"HardcodedUrl",supportedTypes:h.allTypes()},{severity:"error"})}check(e,t,s){if(!e.elements||e.elements.length===0)return[];const r=/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}force\.com/g;return e.elements.filter(n=>r.test(JSON.stringify(n))).map(n=>new y(n))}}class Ze extends v{constructor(){super({name:"InactiveFlow",label:"Inactive Flow",description:"Like cleaning out your closet: deleting unused flows is essential. Inactive flows can still cause trouble, like accidentally deleting records during testing, or being activated as subflows within parent flows.",supportedTypes:h.allTypes(),docRefs:[]})}check(e,t,s){return e.status!=="Active"?[new y(new A(e.status,"status","!= Active"))]:[]}}class Ke extends v{constructor(){super({description:"At times, a flow may fail to execute a configured operation as intended. By default, the flow displays an error message to the user and notifies the admin who created the flow via email. However, you can customize this behavior by incorporating a Fault Path. This rule checks DML operations, actions (Send Email, Quick Actions), and Invocable Apex Actions for proper error handling.",docRefs:[{label:"Flow Best Practices",path:"https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5"}],label:"Missing Fault Path",name:"MissingFaultPath",supportedTypes:[...h.backEndTypes,...h.visualTypes]}),this.applicableElements=["recordLookups","recordDeletes","recordUpdates","recordCreates","waits","actionCalls","apexPluginCalls"]}isValidSubtype(e){var t;if(!this.applicableElements.includes(e.subtype))return!1;if(e.subtype==="waits"){const s=(t=e.element)==null?void 0:t.elementSubtype;return!["WaitDuration","WaitDate"].includes(s)}return!0}check(e,t,s){var c;const r=[],n=e.elements.filter(l=>{const u=l;return this.isValidSubtype(u)}).map(l=>l.name),o=this.isRecordBeforeSaveFlow(e),i=l=>{var u;if(!((u=l==null?void 0:l.connectors)!=null&&u.find(d=>d.type==="faultConnector"))&&n.includes(l.name)){if(o&&l.subtype==="recordUpdates")return;this.isPartOfFaultHandlingFlow(l,e)||s.has(l.name)||r.push(new y(l))}};return(c=e.graph)==null||c.forEachReachable(i),r}isRecordBeforeSaveFlow(e){var t,s;return!!((t=e.startNode)!=null&&t.element&&((s=e.startNode.element)==null?void 0:s.triggerType)==="RecordBeforeSave")}isPartOfFaultHandlingFlow(e,t){var s;return((s=t.graph)==null?void 0:s.isPartOfFaultHandling(e.name))||!1}}class et extends v{constructor(){super({description:"When a Get Records operation doesn't find any data, it returns null. To ensure data validation, utilize a decision element on the operation result variable to check for a non-null result.",docRefs:[],label:"Missing Null Handler",name:"MissingNullHandler",supportedTypes:[...h.backEndTypes,...h.visualTypes]})}check(e,t,s){var c,l;const r=["recordLookups"],n=e.elements.filter(u=>u.metaType==="node"&&r.includes(u.subtype)),o=e.elements.filter(u=>u.metaType==="node"&&u.subtype==="decisions"),i=[];for(const u of n){if(s.has(u.name))continue;const d=u.name;if(!(String(u.element.assignNullValuesIfNoRecordsFound).toLowerCase()==="true")||!!u.element.faultConnector||((c=u.connectors)==null?void 0:c.some(R=>R.type==="faultConnector")))continue;const m=[];if(u.element.storeOutputAutomatically)m.push(d);else if(u.element.outputReference)m.push(u.element.outputReference);else if(u.element.outputAssignments){const R=u.element.outputAssignments;for(const C of R)m.push(C.assignToReference)}if(!e.elements.some(R=>{if(R.name===u.name)return!1;const C=JSON.stringify(R.element);return m.some(I=>C.includes(`"${I}"`)||C.includes(`"${I}.`))}))continue;let S=!1;for(const R of o){let C=R.element.rules;Array.isArray(C)||(C=[C]);for(const I of C){let G=I.conditions;Array.isArray(G)||(G=[G]);for(const X of G){let Te=!1,Ce=!1,Se=!1;if(X.leftValueReference){const vt=X.leftValueReference;m.some(Tt=>vt.startsWith(Tt))&&(Te=!0)}X.operator==="IsNull"&&(Ce=!0);const Ae=(l=X.rightValue)==null?void 0:l.booleanValue;if(Ae!=null&&String(Ae).toLowerCase()==="false"&&(Se=!0),Te&&Ce&&Se){S=!0;break}}if(S)break}if(S)break}S||i.push(u)}return i.map(u=>new y(u))}}class tt extends v{constructor(){super({name:"ProcessBuilder",label:"No Process Builder",description:"Salesforce is transitioning away from Workflow Rules and Process Builder in favor of Flow. Ensure you're prepared for this transition by migrating your organization's automation to Flow. Refer to official documentation for more information on the transition process and tools available.",supportedTypes:h.processBuilder,docRefs:[{label:"Process Builder Retirement",path:"https://help.salesforce.com/s/articleView?id=000389396&type=1"}]})}check(e,t,s){return[new y(new A("Workflow","processType","== Workflow"))]}}class st extends v{constructor(){super({description:"After updates are meant to be used for record modifications that are not the same record that triggered the flow. Using after updates on the same record can lead to recursion and unexpected behavior. Consider using before save flows for same record updates.",docRefs:[{label:"Learn about same record field updates",path:"https://architect.salesforce.com/decision-guides/trigger-automation#Same_Record_Field_Updates"}],label:"Recursive After Update",name:"RecursiveAfterUpdate",supportedTypes:[...h.backEndTypes]},{severity:"warning"}),this.qualifiedRecordTriggerTypes=new Set(["Create","CreateAndUpdate","Update"])}check(e,t,s){var f,b,m;const r=[],n=this.getStartProperty(e,"triggerType"),o=this.getStartProperty(e,"recordTriggerType"),i=n==="RecordAfterSave",c=this.qualifiedRecordTriggerTypes.has(o);if(!i||!c)return r;const l=(f=e.elements)==null?void 0:f.filter(p=>p.subtype==="recordUpdates");if(l==null||typeof l[Symbol.iterator]!="function")return r;for(const p of l)typeof p.element=="object"&&"inputReference"in p.element&&p.element.inputReference==="$Record"&&(s.has(p.name)||r.push(new y(p)));const u=this.getStartProperty(e,"object"),d=(m=(b=e.elements)==null?void 0:b.filter(p=>p.subtype==="recordLookups"&&typeof p.element=="object"&&"object"in p.element&&u===p.element.object))==null?void 0:m.map(p=>p.name);if(d==null||typeof d[Symbol.iterator]!="function")return r;for(const p of l)typeof p.element=="object"&&"inputReference"in p.element&&d.includes(p.element.inputReference)&&(s.has(p.name)||r.push(new y(p)));return r}}class rt extends v{constructor(){super({name:"SameRecordFieldUpdates",label:"Same Record Field Updates",description:"Before-save same-record field updates allows you to update the record using variable assignments to `$Record`. This is significantly faster than doing another DML on the same-record that triggered the flow",supportedTypes:[...h.backEndTypes],docRefs:[{label:"Learn about same record field updates",path:"https://architect.salesforce.com/decision-guides/trigger-automation#Same_Record_Field_Updates"}]},{severity:"warning"}),this.qualifiedRecordTriggerTypes=new Set(["Create","Update","CreateAndUpdate"])}check(e,t,s){var u;const r=[],n=this.getStartProperty(e,"triggerType"),o=this.getStartProperty(e,"recordTriggerType"),i=n==="RecordBeforeSave",c=this.qualifiedRecordTriggerTypes.has(o);if(!i||!c)return r;const l=(u=e.elements)==null?void 0:u.filter(d=>d.subtype==="recordUpdates");if(!l)return r;for(const d of l)typeof d.element=="object"&&"inputReference"in d.element&&d.element.inputReference==="$Record"&&r.push(new y(d));return r}}class nt extends ne{constructor(){super({description:"To prevent exceeding Apex governor limits, it is advisable to consolidate all your SOQL queries at the conclusion of the flow.",docRefs:[{label:"Flow Best Practices",path:"https://help.salesforce.com/s/articleView?id=sf.flow_prep_bestpractices.htm&type=5"}],label:"SOQL Query In A Loop",name:"SOQLQueryInLoop",supportedTypes:h.backEndTypes},{severity:"error"})}getStatementTypes(){return["recordLookups"]}}class ot extends v{constructor(){super({name:"TriggerOrder",label:"Trigger Order",description:"With flow trigger ordering, introduced in Spring '22, admins can now assign a priority value to their flows and guarantee their execution order. This priority value is not an absolute value, so the values need not be sequentially numbered as 1, 2, 3, and so on.",supportedTypes:[h.autolaunchedType],docRefs:[{label:"Learn more about flow ordering orchestration",path:"https://architect.salesforce.com/decision-guides/trigger-automation#Ordering___Orchestration"}]},{severity:"note"})}check(e,t,s){return this.getStartProperty(e,"object")?e.triggerOrder?[]:[new y(new A("TriggerOrder","TriggerOrder","10, 20, 30 ..."))]:[]}}class it extends v{constructor(){super({description:"To maintain the efficiency and manageability of your Flow, it's best to avoid including unconnected elements that are not in use.",docRefs:[],label:"Unconnected Element",name:"UnconnectedElement",supportedTypes:[...h.backEndTypes,...h.visualTypes]})}check(e,t,s){var i;const r=((i=e.graph)==null?void 0:i.getReachableElements())||new Set;return e.elements.filter(c=>c instanceof x).filter(c=>!r.has(c.name)&&!s.has(c.name)).map(c=>new y(c))}}class at extends v{constructor(){super({name:"UnsafeRunningContext",label:"Unsafe Running Context",description:"This flow is configured to run in System Mode without Sharing. This system context grants all running users the permission to view and edit all data in your org. Running a flow in System Mode without Sharing can lead to unsafe data access.",supportedTypes:[...h.backEndTypes,...h.visualTypes],docRefs:[{label:"Learn about data safety when running flows in system context in Salesforce Help",path:"https://help.salesforce.com/s/articleView?id=sf.flow_distribute_context_data_safety_system_context.htm&type=5"}]},{severity:"error"})}check(e,t,s){if(!("runInMode"in e.xmldata))return[];const r=e.xmldata.runInMode,n="SystemModeWithoutSharing";return r===n?[new y(new A(r,"runInMode",`== ${n}`))]:[]}}class ct extends v{constructor(){super({name:"UnusedVariable",label:"Unused Variable",description:"To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use.",supportedTypes:[...h.backEndTypes,...h.visualTypes],docRefs:[]})}check(e,t,s){const r=e.elements.filter(o=>o instanceof j),n=[];for(const o of r){const i=o.name;if([...JSON.stringify(e.elements.filter(f=>f instanceof x)).matchAll(new RegExp(i,"gi"))].map(f=>f.index).length>0||[...JSON.stringify(e.elements.filter(f=>f instanceof te)).matchAll(new RegExp(i,"gi"))].map(f=>f.index).length>0)continue;const u=[...JSON.stringify(o).matchAll(new RegExp(o.name,"gi"))].map(f=>f.index);[...JSON.stringify(e.elements.filter(f=>f instanceof j)).matchAll(new RegExp(i,"gi"))].map(f=>f.index).length===u.length&&n.push(o)}return n.map(o=>new y(o))}}class lt extends v{constructor(){super({description:"Every element must have a meaningful description",docRefs:[],label:"Missing Metadata Description",name:"MissingMetadataDescription",supportedTypes:h.allTypes()},{severity:"error"})}check(e,t,s){const r=[];return e.elements.filter(n=>{if(n.metaType!=="attribute"&&!n.element.description&&n.subtype!=="start")return n}).forEach(n=>r.push(new y(n))),r}}class ut extends v{constructor(){super({name:"MissingFilterRecordTrigger",label:"Missing Record Trigger Filter",description:"Detects record-triggered flows that lack filters on changed fields or entry conditions, leading to unnecessary executions on every record change. This can degrade system performance, hit governor limits faster, and increase resource consumption in high-volume orgs.",supportedTypes:[h.autolaunchedType],docRefs:[]})}check(e,t,s){var u,d;const r=[],n=this.getStartProperty(e,"triggerType");if(!n||!["RecordAfterSave","RecordBeforeSave"].includes(n))return r;const i=!!this.getStartProperty(e,"filters"),l=!!((d=(u=e.xmldata)==null?void 0:u.start)==null?void 0:d.scheduledPaths);return!i&&!l&&r.push(new y(new A(n,"triggerType","autolaunched && triggerType"))),r}}class dt extends v{constructor(){super({name:"TransformInsteadOfLoop",label:"Transform Instead of Loop",description:"Detects Loop elements that directly connect to Assignment elements. This pattern can often be replaced with the Transform element, which is on average 10x more performant according to Salesforce documentation.",supportedTypes:h.allTypes(),docRefs:[{label:"Transform Multiple Records - Trailhead",path:"https://trailhead.salesforce.com/content/learn/modules/multirecord-elements-and-transforms-in-flows/transform-multiple-records"}]},{severity:"note"})}check(e,t,s){const r=[];if(this.getStartProperty(e,"triggerType")==="RecordBeforeSave")return r;const i=e.graph.getLoopNodes();for(const c of i){const l=e.graph.getNextElements(c.name);for(const u of l){const d=e.graph.getNode(u);if((d==null?void 0:d.subtype)==="assignments"){r.push(new y(c));break}}}return r}}class ft extends v{constructor(){super({name:"RecordIdAsString",label:"Record ID as String Instead of Record",description:"Detects flows using a String variable named 'recordId' as input when they could receive the entire record object instead. Since recent Salesforce releases, record pages and quick actions can pass the complete record, eliminating the need for an additional Get Records query and improving performance.",supportedTypes:[...h.visualTypes,h.autolaunchedType],docRefs:[{label:"Screen Flow Distribution",path:"https://help.salesforce.com/s/articleView?id=sf.flow_distribute_screen.htm"}]},{severity:"error"})}check(e,t,s){var c;const r=[],n=this.getStartProperty(e,"triggerType");if(n==="RecordAfterSave"||n==="RecordBeforeDelete"||n==="RecordBeforeSave")return r;const i=(c=e.elements)==null?void 0:c.filter(l=>l.subtype==="variables");for(const l of i){const u=l.element;(u.isInput===!0||u.isInput==="true")&&l.name.toLowerCase()==="recordid"&&u.dataType==="String"&&r.push(new y(l))}return r}}const be={ActionCallsInLoop:Ve,APIVersion:Be,AutoLayout:je,CopyAPIName:ze,CyclomaticComplexity:qe,DMLStatementInLoop:We,DuplicateDMLOperation:He,FlowDescription:Ge,FlowName:Xe,GetRecordAllFields:Qe,HardcodedId:Ye,HardcodedUrl:Je,InactiveFlow:Ze,MissingFaultPath:Ke,MissingNullHandler:et,ProcessBuilder:tt,RecursiveAfterUpdate:st,SameRecordFieldUpdates:rt,SOQLQueryInLoop:nt,TriggerOrder:ot,UnconnectedElement:it,UnsafeRunningContext:at,UnusedVariable:ct},oe={MissingMetadataDescription:lt,MissingFilterRecordTrigger:ut,TransformInsteadOfLoop:dt,RecordIdAsString:ft};class we{constructor(e,t=!1){return t&&oe.hasOwnProperty(e)?new oe[e]:new be[e]}}function ie(a,e){const t=[],s=(e==null?void 0:e.betaMode)===!0||(e==null?void 0:e.betamode)===!0;if(((e==null?void 0:e.ruleMode)||"merged")==="isolated"&&a&&a.size>0){for(const o of a.keys())try{const i=a.get(o);if(i&&i.enabled===!1)continue;const c=new we(o,s),l=i==null?void 0:i.severity;l&&(l==="error"||l==="warning"||l==="note")&&(c.severity=l),t.push(c)}catch(i){console.log(i.message)}return t}const n=new Set;for(const o in be)n.add(o);if(s)for(const o in oe)n.add(o);for(const o of n)try{const i=a==null?void 0:a.get(o);if(i&&i.enabled===!1)continue;const c=new we(o,s),l=i==null?void 0:i.severity;l&&(l==="error"||l==="warning"||l==="note")&&(c.severity=l),t.push(c)}catch(i){console.log(i.message)}return t}function pt(a,e){if(a&&a.length>0){const t=new Map(a.map(s=>[s,{severity:"error"}]));return ie(t,e)}return ie(void 0,e)}var ae,ve;function ht(){if(ve)return ae;ve=1;var a=null;return ae=a,ae}var mt=ht();async function gt(a){const e=[],t=new O.XMLParser({attributeNamePrefix:"@_",ignoreAttributes:!1,ignoreNameSpace:!1,parseTagValue:!1,textNodeName:"#text"});for(const s of a)try{const r=z.normalize(s),n=await mt.promises.readFile(r,"utf8"),i=t.parse(n).Flow;e.push(new re(s,new _(s,i)))}catch(r){e.push(new re(s,void 0,r.message??r.toString()))}return e}var H=(a=>(a.ENRICHED="enriched",a.SIMPLE="simple",a))(H||{});function yt(a,e){const t=[];for(const r of a)!r.errorMessage&&r.flow&&t.push(r.flow);return bt(t,e)}function bt(a,e){var c,l,u;const t=[],s=e==null?void 0:e.detailLevel,r=typeof s=="string"&&s.toLowerCase()==="simple"?H.SIMPLE:H.ENRICHED;let n;if(e!=null&&e.rules&&Object.keys(e.rules).length>0){n=new Map;for(const[d,f]of Object.entries(e.rules))n.set(d,f)}const o=ie(n,e),i=new Map;for(const d of a){const f=d instanceof _?d:_.from(d),b=[];for(const m of o)try{if(!m.supportedTypes.includes(f.type)){b.push(new q(m,[]));continue}let p;(c=e==null?void 0:e.rules)!=null&&c[m.name]&&(p=e.rules[m.name]);const S=(u=(l=e==null?void 0:e.exceptions)==null?void 0:l[f.name])==null?void 0:u[m.name],R=S!=null&&S.includes("*")?["*"]:S??[],C=p&&Object.keys(p).length>0?m.execute(f,p,R):m.execute(f,void 0,R);if(C.severity!==m.severity&&(C.severity=m.severity),C.details.length>0){let I=i.get(f.name);I||(I=f.toXMLString(),i.set(f.name,I)),I&&Pe(C.details,I)}b.push(C)}catch(p){const S=`Something went wrong while executing ${m.name} in the Flow: ${f.name} with error ${p}`;b.push(new q(m,[],S))}t.push(new ye(f,b)),i.delete(f.name)}return i.clear(),r===H.SIMPLE&&t.forEach(d=>{d.ruleResults.forEach(f=>{f.details.forEach(b=>{delete b.details})})}),t}function wt(a,e={includeDetails:!0,includeMarkdownDocs:!0,collapsedDetails:!0}){const t=a.filter(n=>n.flow).map(n=>n.flow);let s=`# Flow Documentation + +`;t.length===0&&(s+=`No valid flows found. + +`);for(const n of t){s+=`## ${n.name} + +`;const o={includeDetails:e.includeDetails,includeMarkdownDocs:e.includeMarkdownDocs,collapsedDetails:e.collapsedDetails};s+=n.visualize("mermaid",o)+` + +`}const r=a.filter(n=>n.errorMessage);if(r.length>0){s+=`## Parse Errors + +`;for(const n of r)s+=`- ${n.uri}: ${n.errorMessage} +`;s+=` +`}return s}g.ASCII_ICONS=he,g.ASCII_VARIABLE_ICONS=me,g.Compiler=Q,g.DEFAULT_ICONS=ee,g.DEFAULT_VARIABLE_ICONS=se,g.Flow=_,g.FlowAttribute=A,g.FlowElement=B,g.FlowNode=x,g.FlowResource=te,g.FlowType=h,g.FlowVariable=j,g.ParsedFlow=re,g.RuleResult=q,g.ScanResult=ye,g.Violation=y,g.exportDetails=Re,g.exportDiagram=wt,g.exportSarif=Ee,g.fix=Ue,g.getRules=pt,g.parse=gt,g.scan=yt,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})})); diff --git a/force-app/main/default/staticresources/LFS_Core.resource-meta.xml b/force-app/main/default/staticresources/LFS_Core.resource-meta.xml new file mode 100644 index 00000000..66c44304 --- /dev/null +++ b/force-app/main/default/staticresources/LFS_Core.resource-meta.xml @@ -0,0 +1,6 @@ + + + Public + application/x-javascript + UMD distribution of Lightning Flow Scanner Core +