Skip to content

Commit ab74777

Browse files
committed
implement umd testing by default
1 parent 34ff81c commit ab74777

File tree

8 files changed

+286
-255
lines changed

8 files changed

+286
-255
lines changed

jest.env-setup.js

Lines changed: 109 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,118 @@ const fs = require("fs");
22
const path = require("path");
33

44
module.exports = () => {
5-
if (process.env.UMD_PATH) {
6-
const umdFilePath = path.resolve(process.env.UMD_PATH);
7-
if (!fs.existsSync(umdFilePath)) {
8-
return; // Skip silently
5+
console.log("UMD Setup: Running, UMD_PATH:", process.env.UMD_PATH || "undefined");
6+
7+
if (!process.env.UMD_PATH) {
8+
console.log("UMD Setup: No UMD_PATH—skipping (Node-only mode)");
9+
return;
10+
}
11+
12+
const umdFilePath = path.resolve(process.env.UMD_PATH);
13+
14+
if (!fs.existsSync(umdFilePath)) {
15+
console.warn("UMD Setup: File not found at", umdFilePath, "—skipping.");
16+
return;
17+
}
18+
19+
let umdCode = fs.readFileSync(umdFilePath, "utf8");
20+
const fastXmlParser = require("fast-xml-parser");
21+
22+
// Vite UMD pattern: (function(global, factory) { ... })(this, function(exports, dependency) { ... });
23+
// We need to extract and execute the factory function
24+
25+
// Try to match Vite's UMD pattern more flexibly
26+
const viteUmdMatch = umdCode.match(
27+
/\(function\s*\([^,]+,\s*(\w+)\)\s*\{[\s\S]*?\}\)\s*\(this,\s*function\s*\((\w+)(?:,\s*(\w+))?\)\s*\{([\s\S]+)\}\s*\)\s*;?\s*$/
28+
);
29+
30+
if (viteUmdMatch) {
31+
const factoryBody = viteUmdMatch[4];
32+
const exportsParam = viteUmdMatch[2];
33+
const depParam = viteUmdMatch[3];
34+
35+
console.log("UMD Setup: Vite UMD pattern matched!");
36+
console.log("UMD Setup: Factory body length:", factoryBody.length);
37+
console.log("UMD Setup: Exports param:", exportsParam, "Dependency param:", depParam);
38+
39+
try {
40+
// Create the factory function with proper parameters
41+
const factoryFn = depParam
42+
? new Function(exportsParam, depParam, factoryBody)
43+
: new Function(exportsParam, factoryBody);
44+
45+
// Create exports object and invoke factory
46+
const exports = {};
47+
if (depParam) {
48+
factoryFn(exports, fastXmlParser);
49+
} else {
50+
factoryFn(exports);
51+
}
52+
53+
// Assign to global
54+
global.lightningflowscanner = exports;
55+
56+
console.log("UMD Setup: Factory invoked successfully");
57+
console.log("UMD Setup: Exported keys:", Object.keys(exports).slice(0, 10));
58+
59+
if (Object.keys(exports).length === 0) {
60+
console.error("UMD Setup: WARNING - exports object is empty!");
61+
}
62+
} catch (e) {
63+
console.error("UMD Factory error:", e.message);
64+
console.error("Stack:", e.stack);
965
}
66+
} else {
67+
// Fallback: Try direct execution approach
68+
console.log("UMD Setup: Trying direct execution approach...");
69+
70+
try {
71+
// Create a mock global/window context
72+
const mockGlobal = {};
73+
const mockFactory = new Function(
74+
"global",
75+
"factory",
76+
`
77+
return (function(root, factoryFn) {
78+
if (typeof exports === 'object' && typeof module !== 'undefined') {
79+
factoryFn(exports, require('fast-xml-parser'));
80+
} else {
81+
factoryFn((root.lightningflowscanner = {}), root.fastXmlParser);
82+
}
83+
})(global, factory);
84+
`
85+
);
1086

11-
let umdCode = fs.readFileSync(umdFilePath, "utf8");
12-
13-
// Inject dep
14-
const fastXmlParser = require("fast-xml-parser");
15-
16-
// Flexible regex
17-
const factoryMatch = umdCode.match(
18-
/function\(w,U\)\{["']use strict["'];\s*(.*)\}\)\);[\s\S]*$/s
19-
);
20-
if (factoryMatch) {
21-
const factoryBody = factoryMatch[1];
22-
try {
23-
const factory = new Function("w", "U", factoryBody);
24-
// Mimic UMD: Create sub-object, invoke on it
25-
const scanner = {};
26-
factory(scanner, fastXmlParser);
27-
global.lightningflowscanner = scanner;
28-
} catch (e) {
29-
// Silent fail or log minimally in prod—keep for dev
30-
console.error("UMD Factory error:", e.message);
87+
// Execute in controlled context
88+
const vm = require("vm");
89+
const sandbox = {
90+
exports: {},
91+
require: (id) => (id === "fast-xml-parser" ? fastXmlParser : require(id)),
92+
module: { exports: {} },
93+
console: console,
94+
};
95+
96+
vm.runInNewContext(umdCode, sandbox);
97+
98+
// Check what got exported
99+
const exports = sandbox.exports.default || sandbox.exports || sandbox.module.exports;
100+
101+
if (exports && Object.keys(exports).length > 0) {
102+
global.lightningflowscanner = exports;
103+
} else {
104+
console.error("UMD Setup: Direct execution failed - no exports found");
31105
}
106+
} catch (e) {
107+
console.error("UMD Direct execution error:", e.message);
108+
console.error("Stack:", e.stack.slice(0, 400));
32109
}
33110
}
111+
112+
// Final verification
113+
if (global.lightningflowscanner && Object.keys(global.lightningflowscanner).length > 0) {
114+
console.log("✓ UMD Setup: SUCCESS - lightningflowscanner loaded on global");
115+
console.log(" Available exports:", Object.keys(global.lightningflowscanner).join(", "));
116+
} else {
117+
console.error("✗ UMD Setup: FAILED - lightningflowscanner not properly loaded");
118+
}
34119
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"build": "npm run clean && npm run build:js && npm run build:types && npm run copy:media && npm run copy:root-files:out",
1919
"test": "npm run test:umd",
2020
"test:node": "jest",
21-
"test:umd": "npm run build:js && vite build && npm run copy:root-files:dist && cross-env UMD_PATH=dist/lightning-flow-scanner-core.umd.js jest --passWithNoTests",
21+
"test:umd": "npm run build:js && vite build && npm run copy:root-files:dist && cross-env UMD_PATH=dist/lightning-flow-scanner-core.umd.js jest",
2222
"link": "npm run build && npm link ./out",
2323
"____pack____": "PACKAGING COMMANDS__",
2424
"swc:sim:pack": "npm run build",

src/main/models/Flow.ts

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,27 @@
11
import { XMLBuilder } from "fast-xml-parser";
22
import * as p from "path";
3-
43
import { FlowElement } from "./FlowElement";
54
import { FlowMetadata } from "./FlowMetadata";
65
import { FlowNode } from "./FlowNode";
76
import { FlowResource } from "./FlowResource";
87
import { FlowVariable } from "./FlowVariable";
98

109
export class Flow {
11-
/**
10+
/**
1211
* Metadata Tags of Salesforce Flow Elements
1312
*/
1413
public static readonly FLOW_METADATA_TAGS = [
1514
"description", "apiVersion", "processMetadataValues", "processType",
1615
"interviewLabel", "label", "status", "runInMode", "startElementReference",
17-
"isTemplate", "fullName", "timeZoneSidKey",
16+
"isTemplate", "fullName", "timeZoneSidKey",
1817
"isAdditionalPermissionRequiredToRun", "migratedFromWorkflowRuleName",
1918
"triggerOrder", "environments", "segment",
20-
];
19+
] as const;
2120

2221
/**
2322
* Categorized flow contents that should be used in the rule implementation
2423
*/
25-
public elements?: FlowElement[];
26-
public fsPath;
27-
public interviewLabel?: string;
28-
public label: string;
29-
public name?: string;
30-
public processMetadataValues?;
31-
public processType?;
32-
public root?;
33-
public start?;
34-
public startElementReference?;
35-
public startReference;
36-
public status?;
37-
public triggerOrder?: number;
38-
public type?;
39-
40-
/**
41-
* XML to JSON conversion in raw format
42-
*/
43-
public xmldata;
44-
private flowMetadata = Flow.FLOW_METADATA_TAGS;
45-
private flowNodes = [
24+
public static readonly FLOW_NODES = [
4625
"actionCalls",
4726
"apexPluginCalls",
4827
"assignments",
@@ -62,12 +41,36 @@ export class Flow {
6241
"waits",
6342
"transforms",
6443
"customErrors",
65-
];
66-
private flowResources = ["textTemplates", "stages"];
67-
private flowVariables = ["choices", "constants", "dynamicChoiceSets", "formulas", "variables"];
44+
] as const;
45+
46+
public static readonly FLOW_RESOURCES = ["textTemplates", "stages"] as const;
47+
public static readonly FLOW_VARIABLES = ["choices", "constants", "dynamicChoiceSets", "formulas", "variables"] as const;
6848

69-
constructor(path?: string, data?: unknown);
70-
constructor(path: string, data?: unknown) {
49+
/**
50+
* Categorized flow contents that should be used in the rule implementation
51+
*/
52+
public elements?: FlowElement[];
53+
54+
public fsPath?: string;
55+
public interviewLabel?: string;
56+
public label: string;
57+
public name?: string;
58+
public processMetadataValues?: any;
59+
public processType?: string;
60+
public root?: any;
61+
public start?: any;
62+
public startElementReference?: string;
63+
public startReference?: string;
64+
public status?: string;
65+
public triggerOrder?: number;
66+
public type?: string;
67+
68+
/**
69+
* XML to JSON conversion in raw format
70+
*/
71+
public xmldata: any;
72+
73+
constructor(path?: string, data?: unknown) {
7174
if (path) {
7275
this.fsPath = p.resolve(path);
7376
let flowName = p.basename(p.basename(this.fsPath), p.extname(this.fsPath));
@@ -89,14 +92,14 @@ export class Flow {
8992
if (obj instanceof Flow) {
9093
return obj;
9194
}
92-
95+
9396
const flow = Object.create(Flow.prototype);
9497
Object.assign(flow, obj);
95-
98+
9699
if (!flow.toXMLString) {
97100
flow.toXMLString = () => '';
98101
}
99-
102+
100103
return flow;
101104
}
102105

@@ -118,31 +121,31 @@ export class Flow {
118121
continue;
119122
}
120123
const data = this.xmldata[nodeType];
121-
if (this.flowMetadata.includes(nodeType)) {
124+
if (Flow.FLOW_METADATA_TAGS.includes(nodeType as any)) {
122125
if (Array.isArray(data)) {
123126
for (const node of data) {
124127
allNodes.push(new FlowMetadata(nodeType, node));
125128
}
126129
} else {
127130
allNodes.push(new FlowMetadata(nodeType, data));
128131
}
129-
} else if (this.flowVariables.includes(nodeType)) {
132+
} else if (Flow.FLOW_VARIABLES.includes(nodeType as any)) {
130133
if (Array.isArray(data)) {
131134
for (const node of data) {
132135
allNodes.push(new FlowVariable(node.name, nodeType, node));
133136
}
134137
} else {
135138
allNodes.push(new FlowVariable(data.name, nodeType, data));
136139
}
137-
} else if (this.flowNodes.includes(nodeType)) {
140+
} else if (Flow.FLOW_NODES.includes(nodeType as any)) {
138141
if (Array.isArray(data)) {
139142
for (const node of data) {
140143
allNodes.push(new FlowNode(node.name, nodeType, node));
141144
}
142145
} else {
143146
allNodes.push(new FlowNode(data.name, nodeType, data));
144147
}
145-
} else if (this.flowResources.includes(nodeType)) {
148+
} else if (Flow.FLOW_RESOURCES.includes(nodeType as any)) {
146149
if (Array.isArray(data)) {
147150
for (const node of data) {
148151
allNodes.push(new FlowResource(node.name, nodeType, node));
@@ -189,14 +192,13 @@ export class Flow {
189192
// eslint-disable-next-line sonarjs/no-clear-text-protocols
190193
const flowXmlNamespace = "http://soap.sforce.com/2006/04/metadata";
191194
const builderOptions = {
192-
attributeNamePrefix: "@_", // Matches parsing (key prefix)
193-
format: true, // Pretty-print (indented; expands empties to </tag>)
194-
ignoreAttributes: false, // Preserve attrs like xmlns
195+
attributeNamePrefix: "@_", // Matches parsing (key prefix)
196+
format: true, // Pretty-print (indented; expands empties to </tag>)
197+
ignoreAttributes: false, // Preserve attrs like xmlns
195198
suppressBooleanAttributes: false, // NEW: Force ="true" for boolean-like strings (fixes missing value)
196-
suppressEmptyNode: false // Keep empty tags (but doesn't force self-closing in pretty)
199+
suppressEmptyNode: false // Keep empty tags (but doesn't force self-closing in pretty)
197200
};
198201
const builder = new XMLBuilder(builderOptions);
199-
200202
// Fallback: Inject xmlns as attribute if missing
201203
const xmldataWithNs = { ...this.xmldata };
202204
if (!xmldataWithNs["@_xmlns"]) {
@@ -206,7 +208,6 @@ export class Flow {
206208
if (!xmldataWithNs["@_xmlns:xsi"]) {
207209
xmldataWithNs["@_xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance";
208210
}
209-
210211
// Build: Wrap in { Flow: ... }
211212
const rootObj = { Flow: xmldataWithNs };
212213
return builder.build(rootObj);

src/main/models/Violation.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { FlowElement } from "./FlowElement";
44
import { FlowNode } from "./FlowNode";
55
import { FlowVariable } from "./FlowVariable";
66

7-
87
export class Violation {
98
public columnNumber: number; // Mandatory post-enrich; defaults to 1 if not found
109
public details?: object; // Optional; only populated for rule-specific needs
@@ -17,8 +16,8 @@ export class Violation {
1716
this.name = violation.name as string;
1817
this.metaType = violation.metaType;
1918
this.type = violation.subtype;
20-
this.lineNumber = 1; // Default; will be overwritten by enrich if found
21-
this.columnNumber = 1; // Default; will be overwritten by enrich if found
19+
this.lineNumber = 1; // Default; will be overwritten by enrich if found
20+
this.columnNumber = 1; // Default; will be overwritten by enrich if found
2221

2322
// Conditionally populate details only if needed (e.g., via config flag later)
2423
if (violation.metaType === "variable") {
@@ -45,10 +44,8 @@ export function enrichViolationsWithLineNumbers(
4544
): void {
4645
if (!flowXml || violations.length === 0) return;
4746
const lines = flowXml.split("\n");
48-
4947
// Flow-level XML tags (same as Flow.flowMetadata)
5048
const flowLevelTags = Flow.FLOW_METADATA_TAGS;
51-
5249
for (const violation of violations) {
5350
// For flow elements (nodes, variables, resources), search by <name> tag
5451
if (violation.metaType !== 'attribute') {
@@ -65,8 +62,8 @@ export function enrichViolationsWithLineNumbers(
6562
if (violation.metaType === 'attribute') {
6663
const tagName = violation.type;
6764

68-
// Only search if it's an actual XML tag
69-
if (flowLevelTags.includes(tagName)) {
65+
// Only search if it's an actual XML tag (type assertion for literal check)
66+
if (flowLevelTags.includes(tagName as any)) {
7067
for (let i = 0; i < lines.length; i++) {
7168
if (lines[i].includes(`<${tagName}>`)) {
7269
violation.lineNumber = i + 1;

0 commit comments

Comments
 (0)