Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .github/workflows/deploy-BETA.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
persist-credentials: false
- uses: actions/setup-node@v4.0.2
with:
node-version: 20.x
node-version: 22.x
registry-url: https://registry.npmjs.org
scope: rubenhalman
- name: Install Dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-RELEASE.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4.0.2
with:
node-version: 20.x
node-version: 22.x
registry-url: https://registry.npmjs.org
scope: rubenhalman
- name: Publish
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/generate-RELEASE.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4.0.2
with:
node-version: 20.x
node-version: 22.x
registry-url: https://registry.npmjs.org
scope: rubenhalman
- name: Generate New Release Number
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Install node
uses: actions/setup-node@v4.0.2
with:
node-version: 20.x
node-version: 22.x
- name: Install dependencies and link
run: npm ci && npm link
- name: Run tests
Expand Down
478 changes: 264 additions & 214 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,25 @@
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@types/chai": "^5.0.0",
"@types/chai": "^5.0.1",
"@types/mocha": "^10.0.9",
"@types/node": "^22.7.7",
"chai": "^5.1.1",
"@types/node": "^22.9.0",
"chai": "^5.1.2",
"cross-env": "^7.0.3",
"eslint": "^9.13.0",
"eslint": "^9.15.0",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"logging": "^3.3.0",
"mocha": "^10.7.3",
"mocha": "^10.8.2",
"nyc": "^17.1.0",
"prettier": "^3.3.3",
"rollup": "^4.24.0",
"rollup": "^4.27.2",
"rollup-plugin-polyfill-node": "^0.13.0",
"semantic-release": "^24.1.3",
"semantic-release": "^24.2.0",
"ts-node": "^10.9.2",
"tslib": "^2.8.0",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.10.0",
"typescript-eslint": "^8.14.0",
"uglify-js": "^3.19.3"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/main/libs/Compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class Compiler {
for (const elementName of elementsToVisit) {
if (!this.visitedElements.has(elementName)) {
const currentElement = flow.elements?.find(
(element) => element instanceof FlowNode && element.name === elementName
(element) => element.name === elementName
) as FlowNode;
if (currentElement) {
visitCallback(currentElement);
Expand Down
32 changes: 19 additions & 13 deletions src/main/rules/MissingFaultPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import { RuleCommon } from "../models/RuleCommon";
import * as core from "../internals/internals";

export class MissingFaultPath extends RuleCommon implements core.IRuleDefinition {
protected applicableElements: string[] = [
"recordLookups",
"recordDeletes",
"recordUpdates",
"recordCreates",
"waits",
"actionCalls",
];

constructor() {
super({
name: "MissingFaultPath",
Expand All @@ -24,26 +33,23 @@ export class MissingFaultPath extends RuleCommon implements core.IRuleDefinition
const compiler = new core.Compiler();
const results: core.ResultDetails[] = [];
const elementsWhereFaultPathIsApplicable = (
flow.elements.filter(
(node) =>
node instanceof core.FlowNode &&
[
"recordLookups",
"recordDeletes",
"recordUpdates",
"recordCreates",
"waits",
"actionCalls",
].includes(node.subtype)
) as core.FlowNode[]
flow.elements.filter((node) => {
const proxyNode = node as {} as core.FlowNode;
const validSubtype = this.applicableElements.includes(proxyNode.subtype);
return validSubtype;
}) as core.FlowNode[]
).map((e) => e.name);

const isRecordBeforeSave = flow.start.triggerType === "RecordBeforeSave";

const visitCallback = (element: core.FlowNode) => {
// Check if the element should have a fault path
if (
!element.connectors.find((connector) => connector.type === "faultConnector") &&
elementsWhereFaultPathIsApplicable.includes(element.name)
) {
if (isRecordBeforeSave && element.subtype === "recordUpdates") {
return;
}
// Check if the element is part of another fault path
if (!this.isPartOfFaultHandlingFlow(element, flow)) {
results.push(new core.ResultDetails(element));
Expand Down
16 changes: 16 additions & 0 deletions tests/MissingFaultPath.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import "mocha";
import * as core from "../src";
import * as path from "path-browserify";
import { ParsedFlow } from "../src/main/models/ParsedFlow";
import { MissingFaultPath } from "../src/main/rules/MissingFaultPath";

describe("MissingFaultPath", () => {
let expect;
Expand All @@ -24,4 +26,18 @@ describe("MissingFaultPath", () => {
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
expect(occurringResults.length).to.equal(0);
});

it("should skip before save flows due to salesforce limitation", async () => {
const { default: rawFile } = await import(
"./jsonfiles/MissingFaultPath_BeforeSave_Bypass.json",
{
with: { type: "json" },
}
);
const parsedFile: ParsedFlow[] = rawFile as {} as ParsedFlow[];
const missingFaultPathRule = new MissingFaultPath();
const flow: core.Flow = parsedFile.pop()?.flow as core.Flow;
const scanResults: core.RuleResult = missingFaultPathRule.execute(flow);
expect(scanResults.occurs).to.be.false;
});
});
2 changes: 1 addition & 1 deletion tests/MissingNullHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe("MissingNullHandler ", () => {
expect(results[0].ruleResults[0].occurs).to.equal(true);
});

it(" should return no result when null handlers are implemented", async () => {
it("should return no result when null handlers are implemented", async () => {
let flows = await core.parse([fixed_uri]);
const ruleConfig = {
rules: {
Expand Down
167 changes: 167 additions & 0 deletions tests/jsonfiles/MissingFaultPath_BeforeSave_Bypass.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
[
{
"flow": {
"flowVariables": ["choices", "constants", "dynamicChoiceSets", "formulas", "variables"],
"flowResources": ["textTemplates", "stages"],
"flowMetadata": [
"description",
"apiVersion",
"processMetadataValues",
"processType",
"interviewLabel",
"label",
"status",
"runInMode",
"startElementReference",
"isTemplate",
"fullName",
"timeZoneSidKey",
"isAdditionalPermissionRequiredToRun",
"migratedFromWorkflowRuleName",
"triggerOrder",
"environments",
"segment"
],
"flowNodes": [
"actionCalls",
"apexPluginCalls",
"assignments",
"collectionProcessors",
"decisions",
"loops",
"orchestratedStages",
"recordCreates",
"recordDeletes",
"recordLookups",
"recordUpdates",
"recordRollbacks",
"screens",
"start",
"steps",
"subflows",
"waits"
],
"name": "Test_Flow",
"xmldata": {
"@xmlns": "http://soap.sforce.com/2006/04/metadata",
"apiVersion": "62.0",
"environments": "Default",
"interviewLabel": "Test Flow {!$Flow.CurrentDateTime}",
"label": "Test Flow",
"processMetadataValues": [
{ "name": "BuilderType", "value": { "stringValue": "LightningFlowBuilder" } },
{ "name": "CanvasMode", "value": { "stringValue": "AUTO_LAYOUT_CANVAS" } },
{ "name": "OriginBuilderType", "value": { "stringValue": "LightningFlowBuilder" } }
],
"processType": "AutoLaunchedFlow",
"recordUpdates": {
"description": "test",
"name": "Update_triggering_records",
"label": "Update triggering records",
"locationX": "176",
"locationY": "287",
"inputAssignments": { "field": "Active__c", "value": { "stringValue": "Yes" } },
"inputReference": "$Record"
},
"start": {
"locationX": "50",
"locationY": "0",
"connector": { "targetReference": "Update_triggering_records" },
"object": "Account",
"recordTriggerType": "Create",
"triggerType": "RecordBeforeSave"
},
"status": "Draft"
},
"label": "Test Flow",
"interviewLabel": "Test Flow {!$Flow.CurrentDateTime}",
"processType": "AutoLaunchedFlow",
"processMetadataValues": [
{ "name": "BuilderType", "value": { "stringValue": "LightningFlowBuilder" } },
{ "name": "CanvasMode", "value": { "stringValue": "AUTO_LAYOUT_CANVAS" } },
{ "name": "OriginBuilderType", "value": { "stringValue": "LightningFlowBuilder" } }
],
"start": {
"locationX": "50",
"locationY": "0",
"connector": { "targetReference": "Update_triggering_records" },
"object": "Account",
"recordTriggerType": "Create",
"triggerType": "RecordBeforeSave"
},
"status": "Draft",
"type": "AutoLaunchedFlow",
"elements": [
{ "element": "62.0", "subtype": "apiVersion", "metaType": "metadata" },
{ "element": "Default", "subtype": "environments", "metaType": "metadata" },
{
"element": "Test Flow {!$Flow.CurrentDateTime}",
"subtype": "interviewLabel",
"metaType": "metadata"
},
{ "element": "Test Flow", "subtype": "label", "metaType": "metadata" },
{
"element": { "name": "BuilderType", "value": { "stringValue": "LightningFlowBuilder" } },
"subtype": "processMetadataValues",
"metaType": "metadata"
},
{
"element": { "name": "CanvasMode", "value": { "stringValue": "AUTO_LAYOUT_CANVAS" } },
"subtype": "processMetadataValues",
"metaType": "metadata"
},
{
"element": {
"name": "OriginBuilderType",
"value": { "stringValue": "LightningFlowBuilder" }
},
"subtype": "processMetadataValues",
"metaType": "metadata"
},
{ "element": "AutoLaunchedFlow", "subtype": "processType", "metaType": "metadata" },
{
"element": {
"description": "test",
"name": "Update_triggering_records",
"label": "Update triggering records",
"locationX": "176",
"locationY": "287",
"inputAssignments": { "field": "Active__c", "value": { "stringValue": "Yes" } },
"inputReference": "$Record"
},
"subtype": "recordUpdates",
"metaType": "node",
"connectors": [],
"name": "Update_triggering_records",
"locationX": "176",
"locationY": "287"
},
{
"element": {
"locationX": "50",
"locationY": "0",
"connector": { "targetReference": "Update_triggering_records" },
"object": "Account",
"recordTriggerType": "Create",
"triggerType": "RecordBeforeSave"
},
"subtype": "start",
"metaType": "node",
"connectors": [
{
"element": { "targetReference": "Update_triggering_records" },
"processed": false,
"type": "connector",
"reference": "Update_triggering_records"
}
],
"name": "flowstart",
"locationX": "50",
"locationY": "0"
},
{ "element": "Draft", "subtype": "status", "metaType": "metadata" }
],
"startReference": "Update_triggering_records"
}
}
]