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.
@@ -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
+