rflib-plugin 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/rflib/logging/flow/instrument.d.ts +32 -0
- package/lib/commands/rflib/logging/flow/instrument.js +296 -0
- package/lib/commands/rflib/logging/flow/instrument.js.map +1 -0
- package/messages/rflib.logging.flow.instrument.md +36 -0
- package/oclif.lock +436 -425
- package/oclif.manifest.json +145 -50
- package/package.json +10 -5
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SfCommand } from '@salesforce/sf-plugins-core';
|
|
2
|
+
export type RflibLoggingFlowInstrumentResult = {
|
|
3
|
+
processedFiles: number;
|
|
4
|
+
modifiedFiles: number;
|
|
5
|
+
};
|
|
6
|
+
export declare class FlowInstrumentationService {
|
|
7
|
+
private static readonly parser;
|
|
8
|
+
private static readonly builder;
|
|
9
|
+
static parseFlowContent(content: string): Promise<any>;
|
|
10
|
+
static buildFlowContent(flowObj: any): string;
|
|
11
|
+
static hasRFLIBLogger(flowObj: any): boolean;
|
|
12
|
+
static isFlowType(flowObj: any): boolean;
|
|
13
|
+
static instrumentFlow(flowObj: any, flowName: string): any;
|
|
14
|
+
private static generateUniqueId;
|
|
15
|
+
private static createLoggingAction;
|
|
16
|
+
private static enhanceLoggingWithVariables;
|
|
17
|
+
}
|
|
18
|
+
export default class RflibLoggingFlowInstrument extends SfCommand<RflibLoggingFlowInstrumentResult> {
|
|
19
|
+
static readonly summary: string;
|
|
20
|
+
static readonly description: string;
|
|
21
|
+
static readonly examples: string[];
|
|
22
|
+
static readonly flags: {
|
|
23
|
+
sourcepath: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
|
+
dryrun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
25
|
+
'skip-instrumented': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
26
|
+
};
|
|
27
|
+
private logger;
|
|
28
|
+
private readonly stats;
|
|
29
|
+
run(): Promise<RflibLoggingFlowInstrumentResult>;
|
|
30
|
+
private processDirectory;
|
|
31
|
+
private instrumentFlowFile;
|
|
32
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
/* eslint-disable @typescript-eslint/return-await */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
11
|
+
import { Messages, Logger } from '@salesforce/core';
|
|
12
|
+
import * as xml2js from 'xml2js';
|
|
13
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
14
|
+
const messages = Messages.loadMessages('rflib-plugin', 'rflib.logging.flow.instrument');
|
|
15
|
+
// Type definition removed to fix compiler error
|
|
16
|
+
// This was used to document valid Flow variable types: 'String' | 'Number' | 'Boolean' | 'SObject' | 'SObjectCollection'
|
|
17
|
+
export class FlowInstrumentationService {
|
|
18
|
+
static parser = new xml2js.Parser({
|
|
19
|
+
explicitArray: false,
|
|
20
|
+
preserveChildrenOrder: true,
|
|
21
|
+
xmlns: false
|
|
22
|
+
});
|
|
23
|
+
static builder = new xml2js.Builder({
|
|
24
|
+
xmldec: { version: '1.0', encoding: 'UTF-8' },
|
|
25
|
+
cdata: true
|
|
26
|
+
});
|
|
27
|
+
static async parseFlowContent(content) {
|
|
28
|
+
try {
|
|
29
|
+
return await this.parser.parseStringPromise(content);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
throw new Error(`Flow parsing failed: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
throw new Error('Flow parsing failed with unknown error');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
static buildFlowContent(flowObj) {
|
|
39
|
+
try {
|
|
40
|
+
return this.builder.buildObject(flowObj);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof Error) {
|
|
44
|
+
throw new Error(`Flow building failed: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
throw new Error('Flow building failed with unknown error');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Helper to check if flow already contains RFLIB logger
|
|
50
|
+
static hasRFLIBLogger(flowObj) {
|
|
51
|
+
if (!flowObj?.Flow?.actionCalls) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const actionCalls = Array.isArray(flowObj.Flow.actionCalls)
|
|
55
|
+
? flowObj.Flow.actionCalls
|
|
56
|
+
: [flowObj.Flow.actionCalls];
|
|
57
|
+
return actionCalls.some((action) => action.actionName === 'rflib:Logger' ||
|
|
58
|
+
action.actionName === 'rflib_LoggerFlowAction' ||
|
|
59
|
+
action.actionName === 'rflib_ApplicationEventLoggerAction' ||
|
|
60
|
+
(action.name && typeof action.name === 'string' && action.name.startsWith('RFLIB_Flow_Logger')));
|
|
61
|
+
}
|
|
62
|
+
// Helper to check if flow is of type "Flow" that we want to instrument
|
|
63
|
+
static isFlowType(flowObj) {
|
|
64
|
+
return flowObj?.Flow?.processType === 'Flow';
|
|
65
|
+
}
|
|
66
|
+
// Main instrumentation function
|
|
67
|
+
static instrumentFlow(flowObj, flowName) {
|
|
68
|
+
// Deep clone the object to avoid modifying the original
|
|
69
|
+
const instrumentedFlow = JSON.parse(JSON.stringify(flowObj));
|
|
70
|
+
// Skip if already instrumented
|
|
71
|
+
if (this.hasRFLIBLogger(instrumentedFlow)) {
|
|
72
|
+
return instrumentedFlow;
|
|
73
|
+
}
|
|
74
|
+
// Make sure Flow exists in the object
|
|
75
|
+
if (!instrumentedFlow.Flow) {
|
|
76
|
+
return instrumentedFlow;
|
|
77
|
+
}
|
|
78
|
+
// Create logging action element
|
|
79
|
+
let loggingAction = this.createLoggingAction(flowName);
|
|
80
|
+
// Add variables to the logging message if available
|
|
81
|
+
loggingAction = this.enhanceLoggingWithVariables(loggingAction, instrumentedFlow);
|
|
82
|
+
// Add logging action to actionCalls
|
|
83
|
+
if (!instrumentedFlow.Flow.actionCalls) {
|
|
84
|
+
instrumentedFlow.Flow.actionCalls = loggingAction;
|
|
85
|
+
}
|
|
86
|
+
else if (Array.isArray(instrumentedFlow.Flow.actionCalls)) {
|
|
87
|
+
instrumentedFlow.Flow.actionCalls.push(loggingAction);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// If only one action exists, convert to array
|
|
91
|
+
instrumentedFlow.Flow.actionCalls = [instrumentedFlow.Flow.actionCalls, loggingAction];
|
|
92
|
+
}
|
|
93
|
+
// Find startElementReference and connect logger to it
|
|
94
|
+
if (instrumentedFlow.Flow.startElementReference) {
|
|
95
|
+
// Save the original start reference
|
|
96
|
+
const startNodeReference = instrumentedFlow.Flow.startElementReference;
|
|
97
|
+
// Create connector between logger and original start element
|
|
98
|
+
loggingAction.connector = {
|
|
99
|
+
targetReference: startNodeReference
|
|
100
|
+
};
|
|
101
|
+
// Update flow startElementReference to point to our logger
|
|
102
|
+
instrumentedFlow.Flow.startElementReference = loggingAction.name;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// If no start element, try to find another entry point
|
|
106
|
+
// Common patterns: decisions, screens, or first element in the process
|
|
107
|
+
if (Array.isArray(instrumentedFlow.Flow.decisions) && instrumentedFlow.Flow.decisions.length > 0) {
|
|
108
|
+
// Find the first decision and connect to it
|
|
109
|
+
const firstDecision = instrumentedFlow.Flow.decisions[0];
|
|
110
|
+
loggingAction.connector = {
|
|
111
|
+
targetReference: firstDecision.name
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
else if (Array.isArray(instrumentedFlow.Flow.screens) && instrumentedFlow.Flow.screens.length > 0) {
|
|
115
|
+
// Find the first screen and connect to it
|
|
116
|
+
const firstScreen = instrumentedFlow.Flow.screens[0];
|
|
117
|
+
loggingAction.connector = {
|
|
118
|
+
targetReference: firstScreen.name
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Create a startElementReference pointing to our logger if none exists
|
|
122
|
+
instrumentedFlow.Flow.startElementReference = loggingAction.name;
|
|
123
|
+
}
|
|
124
|
+
// Add interviewLabel if not present
|
|
125
|
+
if (!instrumentedFlow.Flow.interviewLabel) {
|
|
126
|
+
instrumentedFlow.Flow.interviewLabel = `${flowName} {!$Flow.CurrentDateTime}`;
|
|
127
|
+
}
|
|
128
|
+
// Ensure processType is set to 'Flow'
|
|
129
|
+
instrumentedFlow.Flow.processType = 'Flow';
|
|
130
|
+
// Add variables if not present (needed for variable references)
|
|
131
|
+
if (!instrumentedFlow.Flow.variables) {
|
|
132
|
+
instrumentedFlow.Flow.variables = [];
|
|
133
|
+
}
|
|
134
|
+
else if (!Array.isArray(instrumentedFlow.Flow.variables)) {
|
|
135
|
+
instrumentedFlow.Flow.variables = [instrumentedFlow.Flow.variables];
|
|
136
|
+
}
|
|
137
|
+
return instrumentedFlow;
|
|
138
|
+
}
|
|
139
|
+
// Helper to generate unique IDs for new flow elements
|
|
140
|
+
static generateUniqueId() {
|
|
141
|
+
return `RFLIB_LOG_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;
|
|
142
|
+
}
|
|
143
|
+
// Helper to create a logging action element
|
|
144
|
+
static createLoggingAction(flowName) {
|
|
145
|
+
const loggerId = this.generateUniqueId();
|
|
146
|
+
return {
|
|
147
|
+
actionName: 'rflib_LoggerFlowAction',
|
|
148
|
+
actionType: 'apex',
|
|
149
|
+
name: `RFLIB_Flow_Logger_${loggerId}`,
|
|
150
|
+
label: 'Log Flow Invocation',
|
|
151
|
+
locationX: 176,
|
|
152
|
+
locationY: 50,
|
|
153
|
+
inputParameters: [
|
|
154
|
+
{
|
|
155
|
+
name: 'context',
|
|
156
|
+
value: {
|
|
157
|
+
stringValue: flowName,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'logLevel',
|
|
162
|
+
value: {
|
|
163
|
+
stringValue: 'INFO',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'message',
|
|
168
|
+
value: {
|
|
169
|
+
stringValue: `Flow ${flowName} started`,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Helper to add variable references to the logging message when available
|
|
176
|
+
static enhanceLoggingWithVariables(loggingAction, flowObj) {
|
|
177
|
+
// Find input variables or parameters that might be useful to log
|
|
178
|
+
const variables = flowObj.Flow.variables || [];
|
|
179
|
+
const inputVariables = Array.isArray(variables)
|
|
180
|
+
? variables.filter((v) => v.isInput === 'true' || v.isCollection === 'true')
|
|
181
|
+
: (variables.isInput === 'true' || variables.isCollection === 'true' ? [variables] : []);
|
|
182
|
+
if (inputVariables.length > 0) {
|
|
183
|
+
// Find the message parameter - case insensitive search
|
|
184
|
+
const messageParamIndex = loggingAction.inputParameters.findIndex((p) => p.name?.toLowerCase() === 'message');
|
|
185
|
+
if (messageParamIndex >= 0) {
|
|
186
|
+
// Enhance the message with variable information
|
|
187
|
+
const varRefs = inputVariables
|
|
188
|
+
.map((v) => `${v.name}: {!${v.name}}`)
|
|
189
|
+
.join(', ');
|
|
190
|
+
const baseMessage = loggingAction.inputParameters[messageParamIndex];
|
|
191
|
+
const originalMessage = baseMessage.value.stringValue;
|
|
192
|
+
baseMessage.value.stringValue = `${originalMessage} with ${varRefs}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return loggingAction;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export default class RflibLoggingFlowInstrument extends SfCommand {
|
|
199
|
+
static summary = messages.getMessage('summary');
|
|
200
|
+
static description = messages.getMessage('description');
|
|
201
|
+
static examples = messages.getMessages('examples');
|
|
202
|
+
static flags = {
|
|
203
|
+
sourcepath: Flags.string({
|
|
204
|
+
summary: messages.getMessage('flags.sourcepath.summary'),
|
|
205
|
+
description: messages.getMessage('flags.sourcepath.description'),
|
|
206
|
+
char: 's',
|
|
207
|
+
required: true,
|
|
208
|
+
}),
|
|
209
|
+
dryrun: Flags.boolean({
|
|
210
|
+
summary: messages.getMessage('flags.dryrun.summary'),
|
|
211
|
+
description: messages.getMessage('flags.dryrun.description'),
|
|
212
|
+
char: 'd',
|
|
213
|
+
default: false,
|
|
214
|
+
}),
|
|
215
|
+
'skip-instrumented': Flags.boolean({
|
|
216
|
+
summary: messages.getMessage('flags.skip-instrumented.summary') || 'Skip flows that already have RFLIB logging',
|
|
217
|
+
description: messages.getMessage('flags.skip-instrumented.description') || 'Do not instrument flows where RFLIB logging is already present',
|
|
218
|
+
default: false,
|
|
219
|
+
}),
|
|
220
|
+
};
|
|
221
|
+
logger;
|
|
222
|
+
stats = {
|
|
223
|
+
processedFiles: 0,
|
|
224
|
+
modifiedFiles: 0,
|
|
225
|
+
};
|
|
226
|
+
async run() {
|
|
227
|
+
this.logger = await Logger.child(this.ctor.name);
|
|
228
|
+
const startTime = Date.now();
|
|
229
|
+
const { flags } = await this.parse(RflibLoggingFlowInstrument);
|
|
230
|
+
const sourcePath = flags.sourcepath;
|
|
231
|
+
const isDryRun = flags.dryrun;
|
|
232
|
+
const skipInstrumented = flags['skip-instrumented'];
|
|
233
|
+
this.log(`Scanning Flow files in ${sourcePath} and sub directories`);
|
|
234
|
+
this.logger.debug(`Dry run mode: ${isDryRun}`);
|
|
235
|
+
this.logger.debug(`Skip instrumented: ${skipInstrumented}`);
|
|
236
|
+
this.spinner.start('Running...');
|
|
237
|
+
await this.processDirectory(sourcePath, isDryRun, skipInstrumented);
|
|
238
|
+
this.spinner.stop();
|
|
239
|
+
const duration = Date.now() - startTime;
|
|
240
|
+
this.logger.debug(`Completed instrumentation in ${duration}ms`);
|
|
241
|
+
this.log('\nInstrumentation complete.');
|
|
242
|
+
this.log(`Processed files: ${this.stats.processedFiles}`);
|
|
243
|
+
this.log(`Modified files: ${this.stats.modifiedFiles}`);
|
|
244
|
+
return { ...this.stats };
|
|
245
|
+
}
|
|
246
|
+
async processDirectory(dirPath, isDryRun, skipInstrumented) {
|
|
247
|
+
this.logger.debug(`Processing directory: ${dirPath}`);
|
|
248
|
+
const files = await fs.promises.readdir(dirPath);
|
|
249
|
+
for (const file of files) {
|
|
250
|
+
const filePath = path.join(dirPath, file);
|
|
251
|
+
const stat = await fs.promises.stat(filePath);
|
|
252
|
+
if (stat.isDirectory()) {
|
|
253
|
+
await this.processDirectory(filePath, isDryRun, skipInstrumented);
|
|
254
|
+
}
|
|
255
|
+
else if (file.endsWith('.flow-meta.xml')) {
|
|
256
|
+
await this.instrumentFlowFile(filePath, isDryRun, skipInstrumented);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async instrumentFlowFile(filePath, isDryRun, skipInstrumented) {
|
|
261
|
+
const flowName = path.basename(filePath, '.flow-meta.xml');
|
|
262
|
+
this.logger.debug(`Processing flow: ${flowName}`);
|
|
263
|
+
try {
|
|
264
|
+
this.stats.processedFiles++;
|
|
265
|
+
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
266
|
+
const flowObj = await FlowInstrumentationService.parseFlowContent(content);
|
|
267
|
+
// Only instrument flows with processType="Flow", skip all others
|
|
268
|
+
if (!FlowInstrumentationService.isFlowType(flowObj)) {
|
|
269
|
+
this.logger.debug(`Skipping non-Flow type: ${flowName} (processType=${flowObj?.Flow?.processType || 'undefined'})`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Check if flow already has RFLIB logging and skip if needed
|
|
273
|
+
if (skipInstrumented && FlowInstrumentationService.hasRFLIBLogger(flowObj)) {
|
|
274
|
+
this.logger.info(`Skipping already instrumented flow: ${flowName}`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const instrumentedFlow = FlowInstrumentationService.instrumentFlow(flowObj, flowName);
|
|
278
|
+
const newContent = FlowInstrumentationService.buildFlowContent(instrumentedFlow);
|
|
279
|
+
if (content !== newContent) {
|
|
280
|
+
this.stats.modifiedFiles++;
|
|
281
|
+
if (!isDryRun) {
|
|
282
|
+
await fs.promises.writeFile(filePath, newContent);
|
|
283
|
+
this.logger.info(`Modified: ${filePath}`);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
this.logger.info(`Would modify: ${filePath}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
this.logger.error(`Error processing flow ${flowName}`, error);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=instrument.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrument.js","sourceRoot":"","sources":["../../../../../src/commands/rflib/logging/flow/instrument.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable no-await-in-loop */\n/* eslint-disable @typescript-eslint/return-await */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-unsafe-call */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable @typescript-eslint/no-unsafe-return */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { SfCommand, Flags } from '@salesforce/sf-plugins-core';\nimport { Messages, Logger } from '@salesforce/core';\nimport * as xml2js from 'xml2js';\n\nexport type RflibLoggingFlowInstrumentResult = {\n processedFiles: number;\n modifiedFiles: number;\n};\n\nMessages.importMessagesDirectoryFromMetaUrl(import.meta.url);\nconst messages = Messages.loadMessages('rflib-plugin', 'rflib.logging.flow.instrument');\n\n// Type definition removed to fix compiler error\n// This was used to document valid Flow variable types: 'String' | 'Number' | 'Boolean' | 'SObject' | 'SObjectCollection'\n\nexport class FlowInstrumentationService {\n private static readonly parser = new xml2js.Parser({\n explicitArray: false,\n preserveChildrenOrder: true,\n xmlns: false\n });\n\n private static readonly builder = new xml2js.Builder({\n xmldec: { version: '1.0', encoding: 'UTF-8' },\n cdata: true\n });\n\n public static async parseFlowContent(content: string): Promise<any> {\n try {\n return await this.parser.parseStringPromise(content);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Flow parsing failed: ${error.message}`);\n }\n throw new Error('Flow parsing failed with unknown error');\n }\n }\n\n public static buildFlowContent(flowObj: any): string {\n try {\n return this.builder.buildObject(flowObj);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Flow building failed: ${error.message}`);\n }\n throw new Error('Flow building failed with unknown error');\n }\n }\n\n // Helper to check if flow already contains RFLIB logger\n public static hasRFLIBLogger(flowObj: any): boolean {\n if (!flowObj?.Flow?.actionCalls) {\n return false;\n }\n\n const actionCalls = Array.isArray(flowObj.Flow.actionCalls)\n ? flowObj.Flow.actionCalls\n : [flowObj.Flow.actionCalls];\n\n return actionCalls.some(\n (action: any) => \n action.actionName === 'rflib:Logger' || \n action.actionName === 'rflib_LoggerFlowAction' ||\n action.actionName === 'rflib_ApplicationEventLoggerAction' ||\n (action.name && typeof action.name === 'string' && action.name.startsWith('RFLIB_Flow_Logger'))\n );\n }\n\n // Helper to check if flow is of type \"Flow\" that we want to instrument\n public static isFlowType(flowObj: any): boolean {\n return flowObj?.Flow?.processType === 'Flow';\n }\n\n // Main instrumentation function\n public static instrumentFlow(flowObj: any, flowName: string): any {\n // Deep clone the object to avoid modifying the original\n const instrumentedFlow = JSON.parse(JSON.stringify(flowObj));\n\n // Skip if already instrumented\n if (this.hasRFLIBLogger(instrumentedFlow)) {\n return instrumentedFlow;\n }\n\n // Make sure Flow exists in the object\n if (!instrumentedFlow.Flow) {\n return instrumentedFlow;\n }\n\n // Create logging action element\n let loggingAction = this.createLoggingAction(flowName);\n\n // Add variables to the logging message if available\n loggingAction = this.enhanceLoggingWithVariables(loggingAction, instrumentedFlow);\n\n // Add logging action to actionCalls\n if (!instrumentedFlow.Flow.actionCalls) {\n instrumentedFlow.Flow.actionCalls = loggingAction;\n } else if (Array.isArray(instrumentedFlow.Flow.actionCalls)) {\n instrumentedFlow.Flow.actionCalls.push(loggingAction);\n } else {\n // If only one action exists, convert to array\n instrumentedFlow.Flow.actionCalls = [instrumentedFlow.Flow.actionCalls, loggingAction];\n }\n\n // Find startElementReference and connect logger to it\n if (instrumentedFlow.Flow.startElementReference) {\n // Save the original start reference\n const startNodeReference = instrumentedFlow.Flow.startElementReference;\n \n // Create connector between logger and original start element\n loggingAction.connector = {\n targetReference: startNodeReference\n };\n \n // Update flow startElementReference to point to our logger\n instrumentedFlow.Flow.startElementReference = loggingAction.name;\n } else {\n // If no start element, try to find another entry point\n // Common patterns: decisions, screens, or first element in the process\n if (Array.isArray(instrumentedFlow.Flow.decisions) && instrumentedFlow.Flow.decisions.length > 0) {\n // Find the first decision and connect to it\n const firstDecision = instrumentedFlow.Flow.decisions[0];\n loggingAction.connector = {\n targetReference: firstDecision.name\n };\n } else if (Array.isArray(instrumentedFlow.Flow.screens) && instrumentedFlow.Flow.screens.length > 0) {\n // Find the first screen and connect to it\n const firstScreen = instrumentedFlow.Flow.screens[0];\n loggingAction.connector = {\n targetReference: firstScreen.name\n };\n }\n\n // Create a startElementReference pointing to our logger if none exists\n instrumentedFlow.Flow.startElementReference = loggingAction.name;\n }\n\n // Add interviewLabel if not present\n if (!instrumentedFlow.Flow.interviewLabel) {\n instrumentedFlow.Flow.interviewLabel = `${flowName} {!$Flow.CurrentDateTime}`;\n }\n\n // Ensure processType is set to 'Flow'\n instrumentedFlow.Flow.processType = 'Flow';\n\n // Add variables if not present (needed for variable references)\n if (!instrumentedFlow.Flow.variables) {\n instrumentedFlow.Flow.variables = [];\n } else if (!Array.isArray(instrumentedFlow.Flow.variables)) {\n instrumentedFlow.Flow.variables = [instrumentedFlow.Flow.variables];\n }\n\n return instrumentedFlow;\n }\n\n // Helper to generate unique IDs for new flow elements\n private static generateUniqueId(): string {\n return `RFLIB_LOG_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;\n }\n\n // Helper to create a logging action element\n private static createLoggingAction(flowName: string): any {\n const loggerId = this.generateUniqueId();\n return {\n actionName: 'rflib_LoggerFlowAction',\n actionType: 'apex',\n name: `RFLIB_Flow_Logger_${loggerId}`,\n label: 'Log Flow Invocation',\n locationX: 176,\n locationY: 50,\n inputParameters: [\n {\n name: 'context',\n value: {\n stringValue: flowName,\n },\n },\n {\n name: 'logLevel',\n value: {\n stringValue: 'INFO',\n },\n },\n {\n name: 'message',\n value: {\n stringValue: `Flow ${flowName} started`,\n },\n },\n ],\n };\n }\n\n // Helper to add variable references to the logging message when available\n private static enhanceLoggingWithVariables(loggingAction: any, flowObj: any): any {\n // Find input variables or parameters that might be useful to log\n const variables = flowObj.Flow.variables || [];\n const inputVariables = Array.isArray(variables)\n ? variables.filter((v: any) => v.isInput === 'true' || v.isCollection === 'true')\n : (variables.isInput === 'true' || variables.isCollection === 'true' ? [variables] : []);\n\n if (inputVariables.length > 0) {\n // Find the message parameter - case insensitive search\n const messageParamIndex = loggingAction.inputParameters.findIndex(\n (p: any) => p.name?.toLowerCase() === 'message'\n );\n\n if (messageParamIndex >= 0) {\n // Enhance the message with variable information\n const varRefs = inputVariables\n .map((v: any) => `${v.name}: {!${v.name}}`)\n .join(', ');\n\n const baseMessage = loggingAction.inputParameters[messageParamIndex];\n const originalMessage = baseMessage.value.stringValue;\n baseMessage.value.stringValue = `${originalMessage} with ${varRefs}`;\n }\n }\n\n return loggingAction;\n }\n}\n\nexport default class RflibLoggingFlowInstrument extends SfCommand<RflibLoggingFlowInstrumentResult> {\n public static readonly summary = messages.getMessage('summary');\n public static readonly description = messages.getMessage('description');\n public static readonly examples = messages.getMessages('examples');\n\n public static readonly flags = {\n sourcepath: Flags.string({\n summary: messages.getMessage('flags.sourcepath.summary'),\n description: messages.getMessage('flags.sourcepath.description'),\n char: 's',\n required: true,\n }),\n dryrun: Flags.boolean({\n summary: messages.getMessage('flags.dryrun.summary'),\n description: messages.getMessage('flags.dryrun.description'),\n char: 'd',\n default: false,\n }),\n 'skip-instrumented': Flags.boolean({\n summary: messages.getMessage('flags.skip-instrumented.summary') || 'Skip flows that already have RFLIB logging',\n description: messages.getMessage('flags.skip-instrumented.description') || 'Do not instrument flows where RFLIB logging is already present',\n default: false,\n }),\n };\n\n private logger!: Logger;\n private readonly stats: RflibLoggingFlowInstrumentResult = {\n processedFiles: 0,\n modifiedFiles: 0,\n };\n\n public async run(): Promise<RflibLoggingFlowInstrumentResult> {\n this.logger = await Logger.child(this.ctor.name);\n const startTime = Date.now();\n\n const { flags } = await this.parse(RflibLoggingFlowInstrument);\n const sourcePath = flags.sourcepath;\n const isDryRun = flags.dryrun;\n const skipInstrumented = flags['skip-instrumented'];\n\n this.log(`Scanning Flow files in ${sourcePath} and sub directories`);\n this.logger.debug(`Dry run mode: ${isDryRun}`);\n this.logger.debug(`Skip instrumented: ${skipInstrumented}`);\n\n this.spinner.start('Running...');\n await this.processDirectory(sourcePath, isDryRun, skipInstrumented);\n this.spinner.stop();\n\n const duration = Date.now() - startTime;\n this.logger.debug(`Completed instrumentation in ${duration}ms`);\n\n this.log('\\nInstrumentation complete.');\n this.log(`Processed files: ${this.stats.processedFiles}`);\n this.log(`Modified files: ${this.stats.modifiedFiles}`);\n\n return { ...this.stats };\n }\n\n private async processDirectory(dirPath: string, isDryRun: boolean, skipInstrumented: boolean): Promise<void> {\n this.logger.debug(`Processing directory: ${dirPath}`);\n const files = await fs.promises.readdir(dirPath);\n\n for (const file of files) {\n const filePath = path.join(dirPath, file);\n const stat = await fs.promises.stat(filePath);\n\n if (stat.isDirectory()) {\n await this.processDirectory(filePath, isDryRun, skipInstrumented);\n } else if (file.endsWith('.flow-meta.xml')) {\n await this.instrumentFlowFile(filePath, isDryRun, skipInstrumented);\n }\n }\n }\n\n private async instrumentFlowFile(filePath: string, isDryRun: boolean, skipInstrumented: boolean): Promise<void> {\n const flowName = path.basename(filePath, '.flow-meta.xml');\n this.logger.debug(`Processing flow: ${flowName}`);\n\n try {\n this.stats.processedFiles++;\n const content = await fs.promises.readFile(filePath, 'utf8');\n const flowObj = await FlowInstrumentationService.parseFlowContent(content);\n\n // Only instrument flows with processType=\"Flow\", skip all others\n if (!FlowInstrumentationService.isFlowType(flowObj)) {\n this.logger.debug(`Skipping non-Flow type: ${flowName} (processType=${flowObj?.Flow?.processType || 'undefined'})`);\n return;\n }\n\n // Check if flow already has RFLIB logging and skip if needed\n if (skipInstrumented && FlowInstrumentationService.hasRFLIBLogger(flowObj)) {\n this.logger.info(`Skipping already instrumented flow: ${flowName}`);\n return;\n }\n\n const instrumentedFlow = FlowInstrumentationService.instrumentFlow(flowObj, flowName);\n const newContent = FlowInstrumentationService.buildFlowContent(instrumentedFlow);\n\n if (content !== newContent) {\n this.stats.modifiedFiles++;\n if (!isDryRun) {\n await fs.promises.writeFile(filePath, newContent);\n this.logger.info(`Modified: ${filePath}`);\n } else {\n this.logger.info(`Would modify: ${filePath}`);\n }\n }\n } catch (error) {\n this.logger.error(`Error processing flow ${flowName}`, error);\n throw error;\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# summary
|
|
2
|
+
|
|
3
|
+
Summary of a command.
|
|
4
|
+
|
|
5
|
+
# description
|
|
6
|
+
|
|
7
|
+
More information about a command. Don't repeat the summary.
|
|
8
|
+
|
|
9
|
+
# flags.sourcepath.summary
|
|
10
|
+
|
|
11
|
+
Directory containing Apex classes to instrument with logging.
|
|
12
|
+
|
|
13
|
+
# flags.sourcepath.description
|
|
14
|
+
|
|
15
|
+
Path to the source directory containing Apex classes that should be instrumented with RFLIB logging statements. Test classes (ending with 'Test.cls') are automatically excluded.
|
|
16
|
+
|
|
17
|
+
# flags.dryrun.summary
|
|
18
|
+
|
|
19
|
+
Preview changes without modifying files.
|
|
20
|
+
|
|
21
|
+
# flags.dryrun.description
|
|
22
|
+
|
|
23
|
+
When enabled, shows which files would be modified without making actual changes. Useful for reviewing the impact before applying changes.
|
|
24
|
+
|
|
25
|
+
# flags.skip-instrumented.summary
|
|
26
|
+
|
|
27
|
+
Skips any files where a logger is already present.
|
|
28
|
+
|
|
29
|
+
# flags.skip-instrumented.description
|
|
30
|
+
|
|
31
|
+
When provided, the command will not add log statements to any Flows that already contains a RFLIB logging node.
|
|
32
|
+
|
|
33
|
+
# examples
|
|
34
|
+
|
|
35
|
+
- <%= config.bin %> <%= command.id %>
|
|
36
|
+
|