rflib-plugin 0.1.2
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/LICENSE +28 -0
- package/README.md +66 -0
- package/lib/commands/rflib/logging/apex/instrument.d.ts +31 -0
- package/lib/commands/rflib/logging/apex/instrument.js +232 -0
- package/lib/commands/rflib/logging/apex/instrument.js.map +1 -0
- package/lib/commands/rflib/logging/lwc/instrument.d.ts +32 -0
- package/lib/commands/rflib/logging/lwc/instrument.js +238 -0
- package/lib/commands/rflib/logging/lwc/instrument.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/messages/rflib.logging.apex.instrument.md +46 -0
- package/messages/rflib.logging.lwc.instrument.md +64 -0
- package/oclif.lock +8593 -0
- package/oclif.manifest.json +197 -0
- package/package.json +211 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Johannes Fischer
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# RFLIB Plugin for Salesforce CLI
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/rflib-plugin) [](https://npmjs.org/package/rflib-plugin) [](https://raw.githubusercontent.com/salesforcecli/rflib-plugin/main/LICENSE)
|
|
4
|
+
|
|
5
|
+
Plugin for Salesforce CLI to help with the adoption of [RFLIB](https://github.com/j-fischer/rflib) - an open-source logging framework for Salesforce.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Automatically instruments Apex classes with RFLIB logging statements
|
|
10
|
+
- Automatically instruments LWC components with RFLIB logging statements
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
sf plugins install rflib-plugin
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
### `sf rflib logging apex instrument`
|
|
21
|
+
|
|
22
|
+
Adds RFLIB logging statements to Apex classes.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Add logging to all classes in a directory
|
|
26
|
+
sf rflib logging apex instrument --sourcepath force-app/main/default/classes
|
|
27
|
+
|
|
28
|
+
# Preview changes without modifying files
|
|
29
|
+
sf rflib logging apex instrument --sourcepath force-app/main/default/classes --dryrun
|
|
30
|
+
|
|
31
|
+
# Format modified files with Prettier
|
|
32
|
+
sf rflib logging apex instrument --sourcepath force-app/main/default/classes --prettier
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Command Options
|
|
36
|
+
|
|
37
|
+
- `--sourcepath (-s)`: Directory containing Apex classes to instrument
|
|
38
|
+
- `--dryrun (-d)`: Preview changes without modifying files
|
|
39
|
+
- `--prettier (-p)`: Format modified files using Prettier
|
|
40
|
+
|
|
41
|
+
### `sf rflib logging lwc instrument`
|
|
42
|
+
|
|
43
|
+
Adds RFLIB logging statements to Lightning Web Components.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Add logging to all LWC files
|
|
47
|
+
sf rflib logging lwc instrument --sourcepath force-app/main/default/lwc
|
|
48
|
+
|
|
49
|
+
# Preview changes without modifying files
|
|
50
|
+
sf rflib logging lwc instrument --sourcepath force-app/main/default/lwc --dryrun
|
|
51
|
+
|
|
52
|
+
# Add logging and format code
|
|
53
|
+
sf rflib logging lwc instrument --sourcepath force-app/main/default/lwc --prettier
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Contributing
|
|
57
|
+
|
|
58
|
+
1. Fork the repository
|
|
59
|
+
2. Create a feature branch
|
|
60
|
+
3. Make your changes
|
|
61
|
+
4. Submit a pull request
|
|
62
|
+
|
|
63
|
+
## Learn More
|
|
64
|
+
|
|
65
|
+
- [RFLIB Documentation](https://github.com/j-fischer/rflib)
|
|
66
|
+
- [Salesforce CLI Plugin Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_plugins.meta/sfdx_cli_plugins/cli_plugins_architecture_sf_cli.htm)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SfCommand } from '@salesforce/sf-plugins-core';
|
|
2
|
+
export type RflibLoggingApexInstrumentResult = {
|
|
3
|
+
processedFiles: number;
|
|
4
|
+
modifiedFiles: number;
|
|
5
|
+
formattedFiles: number;
|
|
6
|
+
};
|
|
7
|
+
export default class RflibLoggingApexInstrument extends SfCommand<RflibLoggingApexInstrumentResult> {
|
|
8
|
+
static readonly summary: string;
|
|
9
|
+
static readonly description: string;
|
|
10
|
+
static readonly examples: string[];
|
|
11
|
+
static readonly flags: {
|
|
12
|
+
sourcepath: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
dryrun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
prettier: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
private logger;
|
|
17
|
+
private processedFiles;
|
|
18
|
+
private modifiedFiles;
|
|
19
|
+
private formattedFiles;
|
|
20
|
+
private readonly prettierConfig;
|
|
21
|
+
private static isComplexType;
|
|
22
|
+
private static detectExistingLogger;
|
|
23
|
+
private static addLoggerDeclaration;
|
|
24
|
+
private static processParameters;
|
|
25
|
+
private static processMethodDeclarations;
|
|
26
|
+
private static processCatchBlocks;
|
|
27
|
+
run(): Promise<RflibLoggingApexInstrumentResult>;
|
|
28
|
+
private processDirectory;
|
|
29
|
+
private processTestFile;
|
|
30
|
+
private instrumentApexClass;
|
|
31
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
/* eslint-disable @typescript-eslint/quotes */
|
|
3
|
+
/* eslint-disable sf-plugin/no-missing-messages */
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
7
|
+
import { Messages, Logger } from '@salesforce/core';
|
|
8
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
|
+
import * as prettier from 'prettier';
|
|
10
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
11
|
+
const messages = Messages.loadMessages('rflib-plugin', 'rflib.logging.apex.instrument');
|
|
12
|
+
const methodRegex = /(@AuraEnabled\s*[\s\S]*?)?\b(public|private|protected|global)\s+(static\s+)?(?:(\w+(?:\s*<(?:[^<>]|<[^<>]*>)*>)?)|void)\s+(\w+)\s*\(([\s\S]*?)\)\s*{/g;
|
|
13
|
+
const classRegex = /\bclass\s+\w+\s*{/;
|
|
14
|
+
const classLevelLoggerRegex = /\bprivate\s+(?:static\s+)?(?:final\s+)?rflib_Logger\s+(\w+)\b/;
|
|
15
|
+
const genericArgsRegex = /<[^>]+>/g;
|
|
16
|
+
const catchRegex = /catch\s*\(\s*\w+\s+(\w+)\s*\)\s*{/g;
|
|
17
|
+
const testSetupRegex = /@TestSetup\s+((public|private|protected|global)s+)?(?:static\s+)?void\s+(\w+)\s*\([^)]*\)\s*{/g;
|
|
18
|
+
const PRIMITIVE_TYPES = new Set([
|
|
19
|
+
'STRING',
|
|
20
|
+
'INTEGER',
|
|
21
|
+
'LONG',
|
|
22
|
+
'DECIMAL',
|
|
23
|
+
'DOUBLE',
|
|
24
|
+
'BOOLEAN',
|
|
25
|
+
'DATE',
|
|
26
|
+
'DATETIME',
|
|
27
|
+
'TIME',
|
|
28
|
+
'ID',
|
|
29
|
+
]);
|
|
30
|
+
export default class RflibLoggingApexInstrument extends SfCommand {
|
|
31
|
+
static summary = messages.getMessage('summary');
|
|
32
|
+
static description = messages.getMessage('description');
|
|
33
|
+
static examples = messages.getMessages('examples');
|
|
34
|
+
static flags = {
|
|
35
|
+
sourcepath: Flags.string({
|
|
36
|
+
summary: messages.getMessage('flags.sourcepath.summary'),
|
|
37
|
+
description: messages.getMessage('flags.sourcepath.description'),
|
|
38
|
+
char: 's',
|
|
39
|
+
required: true,
|
|
40
|
+
}),
|
|
41
|
+
dryrun: Flags.boolean({
|
|
42
|
+
summary: messages.getMessage('flags.dryrun.summary'),
|
|
43
|
+
description: messages.getMessage('flags.dryrun.description'),
|
|
44
|
+
char: 'd',
|
|
45
|
+
default: false,
|
|
46
|
+
}),
|
|
47
|
+
prettier: Flags.boolean({
|
|
48
|
+
summary: messages.getMessage('flags.prettier.summary'),
|
|
49
|
+
description: messages.getMessage('flags.prettier.description'),
|
|
50
|
+
char: 'p',
|
|
51
|
+
default: false,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
logger;
|
|
55
|
+
processedFiles = 0;
|
|
56
|
+
modifiedFiles = 0;
|
|
57
|
+
formattedFiles = 0;
|
|
58
|
+
prettierConfig = {
|
|
59
|
+
parser: 'apex',
|
|
60
|
+
plugins: ['prettier-plugin-apex'],
|
|
61
|
+
printWidth: 120,
|
|
62
|
+
tabWidth: 4,
|
|
63
|
+
useTabs: false,
|
|
64
|
+
singleQuote: true,
|
|
65
|
+
};
|
|
66
|
+
static isComplexType(paramType) {
|
|
67
|
+
return (paramType.includes('<') ||
|
|
68
|
+
paramType.includes('[') ||
|
|
69
|
+
paramType === 'Object' ||
|
|
70
|
+
!PRIMITIVE_TYPES.has(paramType.toUpperCase()));
|
|
71
|
+
}
|
|
72
|
+
static detectExistingLogger(content) {
|
|
73
|
+
const match = content.match(classLevelLoggerRegex);
|
|
74
|
+
return {
|
|
75
|
+
exists: classLevelLoggerRegex.test(content),
|
|
76
|
+
loggerVariableName: match ? match[1] : 'LOGGER',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
static addLoggerDeclaration(content, className) {
|
|
80
|
+
const { exists, loggerVariableName } = RflibLoggingApexInstrument.detectExistingLogger(content);
|
|
81
|
+
if (!exists) {
|
|
82
|
+
const loggerDeclaration = `private static final rflib_Logger ${loggerVariableName} = rflib_LoggerUtil.getFactory().createLogger('${className}');`;
|
|
83
|
+
return content.replace(classRegex, `$&\n ${loggerDeclaration}`);
|
|
84
|
+
}
|
|
85
|
+
return content;
|
|
86
|
+
}
|
|
87
|
+
static processParameters(args) {
|
|
88
|
+
const parameters = args
|
|
89
|
+
? args
|
|
90
|
+
.replaceAll(genericArgsRegex, '')
|
|
91
|
+
.split(',')
|
|
92
|
+
.map((param) => param.trim())
|
|
93
|
+
: [];
|
|
94
|
+
const logArgs = parameters.length > 0 && parameters[0] !== ''
|
|
95
|
+
? `, new Object[] { ${parameters
|
|
96
|
+
.map((p) => {
|
|
97
|
+
const parts = p.split(' ');
|
|
98
|
+
const paramType = parts[0];
|
|
99
|
+
const paramName = parts.length > 1 ? parts[1] : parts[0];
|
|
100
|
+
return this.isComplexType(paramType) ? `JSON.serialize(${paramName})` : paramName;
|
|
101
|
+
})
|
|
102
|
+
.join(', ')} }`
|
|
103
|
+
: '';
|
|
104
|
+
return { paramList: parameters, logArgs };
|
|
105
|
+
}
|
|
106
|
+
static processMethodDeclarations(content, loggerName) {
|
|
107
|
+
return content.replace(methodRegex, (match, auraEnabled, access, isStatic, returnType, methodName, args) => {
|
|
108
|
+
const { paramList, logArgs } = RflibLoggingApexInstrument.processParameters(args);
|
|
109
|
+
let newMethod = match + '\n';
|
|
110
|
+
newMethod += ` ${loggerName}.info('${methodName}(${paramList.map((_, i) => `{${i}}`).join(', ')})'${logArgs});\n`;
|
|
111
|
+
return newMethod;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
static processCatchBlocks(content, loggerName) {
|
|
115
|
+
return content.replace(catchRegex, (match, exceptionVar, offset) => {
|
|
116
|
+
// Get content before catch block
|
|
117
|
+
const contentBeforeCatch = content.substring(0, offset);
|
|
118
|
+
// Find all method declarations before this catch block
|
|
119
|
+
const methodMatches = [...contentBeforeCatch.matchAll(methodRegex)];
|
|
120
|
+
// Get last method match (closest to catch block)
|
|
121
|
+
const lastMethodMatch = methodMatches[methodMatches.length - 1];
|
|
122
|
+
// Extract method name from match or use 'unknown'
|
|
123
|
+
const methodName = lastMethodMatch
|
|
124
|
+
? lastMethodMatch[5] // Group 4 contains method name in methodRegex
|
|
125
|
+
: 'unknown';
|
|
126
|
+
return `${match}\n ${loggerName}.error('An error occurred in ${methodName}()', ${exceptionVar.trim()});`;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async run() {
|
|
130
|
+
this.logger = await Logger.child(this.ctor.name);
|
|
131
|
+
const startTime = Date.now();
|
|
132
|
+
const { flags } = await this.parse(RflibLoggingApexInstrument);
|
|
133
|
+
const sourcePath = flags.sourcepath;
|
|
134
|
+
const isDryRun = flags.dryrun;
|
|
135
|
+
const usePrettier = flags.prettier;
|
|
136
|
+
this.log(`Scanning Apex classes in ${sourcePath} and sub directories`);
|
|
137
|
+
this.spinner.start('Running...');
|
|
138
|
+
await this.processDirectory(sourcePath, isDryRun, usePrettier);
|
|
139
|
+
this.spinner.stop();
|
|
140
|
+
const duration = Date.now() - startTime;
|
|
141
|
+
this.logger.debug(`Completed instrumentation in ${duration}ms`);
|
|
142
|
+
this.log(`\nInstrumentation complete.`);
|
|
143
|
+
this.log(`Processed files: ${this.processedFiles}`);
|
|
144
|
+
this.log(`Modified files: ${this.modifiedFiles}`);
|
|
145
|
+
this.log(`Formatted files: ${this.formattedFiles}`);
|
|
146
|
+
return {
|
|
147
|
+
processedFiles: this.processedFiles,
|
|
148
|
+
modifiedFiles: this.modifiedFiles,
|
|
149
|
+
formattedFiles: this.formattedFiles,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async processDirectory(dirPath, isDryRun, usePrettier) {
|
|
153
|
+
this.logger.debug(`Processing directory: ${dirPath}`);
|
|
154
|
+
const files = await fs.promises.readdir(dirPath);
|
|
155
|
+
for (const file of files) {
|
|
156
|
+
const filePath = path.join(dirPath, file);
|
|
157
|
+
const stat = await fs.promises.stat(filePath);
|
|
158
|
+
if (stat.isDirectory()) {
|
|
159
|
+
await this.processDirectory(filePath, isDryRun, usePrettier);
|
|
160
|
+
}
|
|
161
|
+
else if (file.endsWith('Test.cls')) {
|
|
162
|
+
await this.processTestFile(filePath, isDryRun);
|
|
163
|
+
}
|
|
164
|
+
else if (file.endsWith('.cls')) {
|
|
165
|
+
await this.instrumentApexClass(filePath, isDryRun, usePrettier);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async processTestFile(filePath, isDryRun) {
|
|
170
|
+
this.logger.debug(`Processing test file: ${filePath}`);
|
|
171
|
+
let content = await fs.promises.readFile(filePath, 'utf8');
|
|
172
|
+
const originalContent = content;
|
|
173
|
+
content = content.replace(testSetupRegex, (match) => `${match}\n rflib_TestUtil.prepareLoggerForUnitTests();`);
|
|
174
|
+
if (content !== originalContent) {
|
|
175
|
+
this.modifiedFiles++;
|
|
176
|
+
if (!isDryRun) {
|
|
177
|
+
await fs.promises.writeFile(filePath, content);
|
|
178
|
+
this.logger.info(`Modified test file: ${filePath}`);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
this.logger.info(`Would modify test file: ${filePath}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async instrumentApexClass(filePath, isDryRun, usePrettier) {
|
|
186
|
+
const className = path.basename(filePath, '.cls');
|
|
187
|
+
this.logger.debug(`Processing class: ${className}`);
|
|
188
|
+
try {
|
|
189
|
+
this.processedFiles++;
|
|
190
|
+
let content = await fs.promises.readFile(filePath, 'utf8');
|
|
191
|
+
const originalContent = content;
|
|
192
|
+
const { loggerVariableName } = RflibLoggingApexInstrument.detectExistingLogger(content);
|
|
193
|
+
content = RflibLoggingApexInstrument.addLoggerDeclaration(content, className);
|
|
194
|
+
content = RflibLoggingApexInstrument.processMethodDeclarations(content, loggerVariableName);
|
|
195
|
+
content = RflibLoggingApexInstrument.processCatchBlocks(content, loggerVariableName);
|
|
196
|
+
if (content !== originalContent) {
|
|
197
|
+
this.modifiedFiles++;
|
|
198
|
+
if (!isDryRun) {
|
|
199
|
+
try {
|
|
200
|
+
const finalContent = usePrettier ? await prettier.format(content, this.prettierConfig) : content;
|
|
201
|
+
await fs.promises.writeFile(filePath, finalContent);
|
|
202
|
+
if (usePrettier) {
|
|
203
|
+
this.formattedFiles++;
|
|
204
|
+
this.logger.info(`Modified and formatted: ${filePath}`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
this.logger.info(`Modified: ${filePath}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (error instanceof Error) {
|
|
212
|
+
this.logger.warn(`Failed to format ${filePath}: ${error.message}`);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
this.logger.warn(`Failed to format ${filePath}: ${String(error)}`);
|
|
216
|
+
}
|
|
217
|
+
await fs.promises.writeFile(filePath, content);
|
|
218
|
+
this.logger.info(`Modified without formatting: ${filePath}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
this.logger.info(`Would modify: ${filePath}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
this.logger.error(`Error processing class ${className}`, error);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=instrument.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrument.js","sourceRoot":"","sources":["../../../../../src/commands/rflib/logging/apex/instrument.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SfCommand } from '@salesforce/sf-plugins-core';
|
|
2
|
+
export type RflibLoggingLwcInstrumentResult = {
|
|
3
|
+
processedFiles: number;
|
|
4
|
+
modifiedFiles: number;
|
|
5
|
+
formattedFiles: number;
|
|
6
|
+
};
|
|
7
|
+
export default class RflibLoggingLwcInstrument extends SfCommand<RflibLoggingLwcInstrumentResult> {
|
|
8
|
+
static readonly summary: string;
|
|
9
|
+
static readonly description: string;
|
|
10
|
+
static readonly examples: string[];
|
|
11
|
+
static readonly flags: {
|
|
12
|
+
sourcepath: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
dryrun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
prettier: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
private logger;
|
|
17
|
+
private processedFiles;
|
|
18
|
+
private modifiedFiles;
|
|
19
|
+
private formattedFiles;
|
|
20
|
+
private readonly prettierConfig;
|
|
21
|
+
private static detectExistingImport;
|
|
22
|
+
private static detectExistingLogger;
|
|
23
|
+
private static addImportAndLogger;
|
|
24
|
+
private static findEnclosingMethod;
|
|
25
|
+
private static processIfStatements;
|
|
26
|
+
private static processMethodLogging;
|
|
27
|
+
private static processPromiseChains;
|
|
28
|
+
private static processTryCatchBlocks;
|
|
29
|
+
run(): Promise<RflibLoggingLwcInstrumentResult>;
|
|
30
|
+
private processDirectory;
|
|
31
|
+
private instrumentLwcFile;
|
|
32
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
|
2
|
+
/* eslint-disable no-await-in-loop */
|
|
3
|
+
/* eslint-disable @typescript-eslint/quotes */
|
|
4
|
+
/* eslint-disable sf-plugin/no-missing-messages */
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
8
|
+
import { Messages, Logger } from '@salesforce/core';
|
|
9
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
10
|
+
import * as prettier from 'prettier';
|
|
11
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
12
|
+
const messages = Messages.loadMessages('rflib-plugin', 'rflib.logging.lwc.instrument');
|
|
13
|
+
const importRegex = /import\s*{\s*createLogger\s*}\s*from\s*['"]c\/rflibLogger['"]/;
|
|
14
|
+
const loggerRegex = /const\s+(\w+)\s*=\s*createLogger\s*\(['"]([\w-]+)['"]\)/;
|
|
15
|
+
const methodRegex = /(?:async\s+)?(?!(?:if|switch|case|while|for|catch)\b)(\b\w+)\s*\((.*?)\)\s*{/g;
|
|
16
|
+
const exportDefaultRegex = /export\s+default\s+class\s+(\w+)/;
|
|
17
|
+
const ifStatementRegex = /if\s*\((.*?)\)\s*{([\s\S]*?)}|if\s*\((.*?)\)\s*([^{\n].*?)(?=\n|$)/g;
|
|
18
|
+
const elseRegex = /}\s*else\s*{([\s\S]*?)}|}\s*else\s+([^{\n].*?)(?=\n|$)/g;
|
|
19
|
+
const promiseChainRegex = /\.(then|catch|finally)\s*\(\s*(?:async\s+)?(?:\(?([^)]*)\)?)?\s*=>\s*(?:\{((?:[^{}]|`[^`]*`)*?)\}|([^{;]*(?:\([^)]*\))*)(?=(?:\)\))|\)|\.))/g;
|
|
20
|
+
const tryCatchBlockRegex = /try\s*{[\s\S]*?}\s*catch\s*\(([^)]*)\)\s*{/g;
|
|
21
|
+
export default class RflibLoggingLwcInstrument extends SfCommand {
|
|
22
|
+
static summary = messages.getMessage('summary');
|
|
23
|
+
static description = messages.getMessage('description');
|
|
24
|
+
static examples = messages.getMessages('examples');
|
|
25
|
+
static flags = {
|
|
26
|
+
sourcepath: Flags.string({
|
|
27
|
+
char: 's',
|
|
28
|
+
required: true,
|
|
29
|
+
summary: messages.getMessage('flags.sourcepath.summary'),
|
|
30
|
+
description: messages.getMessage('flags.sourcepath.description')
|
|
31
|
+
}),
|
|
32
|
+
dryrun: Flags.boolean({
|
|
33
|
+
char: 'd',
|
|
34
|
+
default: false,
|
|
35
|
+
summary: messages.getMessage('flags.dryrun.summary'),
|
|
36
|
+
description: messages.getMessage('flags.dryrun.description')
|
|
37
|
+
}),
|
|
38
|
+
prettier: Flags.boolean({
|
|
39
|
+
char: 'p',
|
|
40
|
+
default: false,
|
|
41
|
+
summary: messages.getMessage('flags.prettier.summary'),
|
|
42
|
+
description: messages.getMessage('flags.prettier.description')
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
logger;
|
|
46
|
+
processedFiles = 0;
|
|
47
|
+
modifiedFiles = 0;
|
|
48
|
+
formattedFiles = 0;
|
|
49
|
+
prettierConfig = {
|
|
50
|
+
parser: 'babel',
|
|
51
|
+
printWidth: 120,
|
|
52
|
+
tabWidth: 4,
|
|
53
|
+
useTabs: false,
|
|
54
|
+
singleQuote: true
|
|
55
|
+
};
|
|
56
|
+
static detectExistingImport(content) {
|
|
57
|
+
return importRegex.test(content);
|
|
58
|
+
}
|
|
59
|
+
static detectExistingLogger(content) {
|
|
60
|
+
const match = content.match(loggerRegex);
|
|
61
|
+
return {
|
|
62
|
+
exists: match !== null,
|
|
63
|
+
loggerName: match ? match[1] : 'logger'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
static addImportAndLogger(content, componentName) {
|
|
67
|
+
let modified = content;
|
|
68
|
+
if (!this.detectExistingImport(content)) {
|
|
69
|
+
modified = `import { createLogger } from 'c/rflibLogger';\n${modified}`;
|
|
70
|
+
}
|
|
71
|
+
const { exists, loggerName } = this.detectExistingLogger(content);
|
|
72
|
+
if (!exists) {
|
|
73
|
+
const exportMatch = content.match(exportDefaultRegex);
|
|
74
|
+
const className = exportMatch ? exportMatch[1] : componentName;
|
|
75
|
+
const loggerDeclaration = `\nconst ${loggerName} = createLogger('${className}');\n`;
|
|
76
|
+
modified = modified.replace(exportDefaultRegex, `${loggerDeclaration}$&`);
|
|
77
|
+
}
|
|
78
|
+
return modified;
|
|
79
|
+
}
|
|
80
|
+
static findEnclosingMethod(content, position) {
|
|
81
|
+
const beforeCatch = content.substring(0, position);
|
|
82
|
+
const methods = [...beforeCatch.matchAll(methodRegex)].reverse();
|
|
83
|
+
const closestMethod = methods[0];
|
|
84
|
+
return closestMethod ? closestMethod[1] : 'unknown';
|
|
85
|
+
}
|
|
86
|
+
static processIfStatements(content, loggerName) {
|
|
87
|
+
let lastCondition = '';
|
|
88
|
+
// First process if statements and store conditions
|
|
89
|
+
let modified = content.replace(ifStatementRegex, (match, blockCondition, blockBody, lineCondition, lineBody) => {
|
|
90
|
+
const condition = typeof blockCondition === 'string' ? blockCondition : (typeof lineCondition === 'string' ? lineCondition : '');
|
|
91
|
+
lastCondition = condition.trim();
|
|
92
|
+
const logStatement = `${loggerName}.debug(\`if (${lastCondition})\`);`;
|
|
93
|
+
if (blockBody) {
|
|
94
|
+
return `if (${condition}) {\n ${logStatement}${blockBody}}`;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
return `if (${condition}) {\n ${logStatement}\n ${lineBody}\n }`;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// Then process else blocks using stored condition
|
|
101
|
+
modified = modified.replace(elseRegex, (match, blockBody, lineBody) => {
|
|
102
|
+
const logStatement = `${loggerName}.debug(\`else for if (${lastCondition})\`);`;
|
|
103
|
+
if (blockBody) {
|
|
104
|
+
return `} else {\n ${logStatement}${blockBody}}`;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
return `} else {\n ${logStatement}\n ${lineBody}\n }`;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return modified;
|
|
111
|
+
}
|
|
112
|
+
static processMethodLogging(content, loggerName) {
|
|
113
|
+
// First handle methods
|
|
114
|
+
let modified = content.replace(methodRegex, (match, methodName, args) => {
|
|
115
|
+
const parameters = args.split(',').map(p => p.trim()).filter(p => p);
|
|
116
|
+
const placeholders = parameters.map((_, i) => `{${i}}`).join(', ');
|
|
117
|
+
const logArgs = parameters.length > 0 ? `, ${parameters.join(', ')}` : '';
|
|
118
|
+
return `${match}\n ${loggerName}.info('${methodName}(${placeholders})'${logArgs});`;
|
|
119
|
+
});
|
|
120
|
+
// Then handle if statements
|
|
121
|
+
modified = this.processIfStatements(modified, loggerName);
|
|
122
|
+
return modified;
|
|
123
|
+
}
|
|
124
|
+
static processPromiseChains(content, loggerName) {
|
|
125
|
+
return content.replace(promiseChainRegex, (match, type, param, blockBody, singleLineBody, offset) => {
|
|
126
|
+
const methodName = this.findEnclosingMethod(content, offset);
|
|
127
|
+
const paramName = typeof param === 'string' ? param.trim() : (type === 'then' ? 'result' : 'error');
|
|
128
|
+
const indentation = match.match(/\n\s*/)?.[0] || '\n ';
|
|
129
|
+
let logStatement;
|
|
130
|
+
switch (type) {
|
|
131
|
+
case 'then':
|
|
132
|
+
logStatement = `${loggerName}.info('${methodName}() promise resolved. Result={0}', ${paramName});`;
|
|
133
|
+
break;
|
|
134
|
+
case 'catch':
|
|
135
|
+
logStatement = `${loggerName}.error('An error occurred in function ${methodName}()', ${paramName});`;
|
|
136
|
+
break;
|
|
137
|
+
case 'finally':
|
|
138
|
+
logStatement = `${loggerName}.info('${methodName}() promise chain completed');`;
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
logStatement = '';
|
|
142
|
+
}
|
|
143
|
+
if (singleLineBody) {
|
|
144
|
+
let trimmedSingleLineBody = singleLineBody.trim();
|
|
145
|
+
if (trimmedSingleLineBody.split(')').length > trimmedSingleLineBody.split('(').length) {
|
|
146
|
+
trimmedSingleLineBody = trimmedSingleLineBody.slice(0, -1);
|
|
147
|
+
}
|
|
148
|
+
return `.${type}((${paramName}) => {
|
|
149
|
+
${logStatement}
|
|
150
|
+
return ${trimmedSingleLineBody};
|
|
151
|
+
}`;
|
|
152
|
+
}
|
|
153
|
+
if (blockBody) {
|
|
154
|
+
return `.${type}((${paramName}) => {${indentation}${logStatement}${indentation}${blockBody}}`;
|
|
155
|
+
}
|
|
156
|
+
return match;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
static processTryCatchBlocks(content, loggerName) {
|
|
160
|
+
return content.replace(tryCatchBlockRegex, (match, exceptionVar, offset) => {
|
|
161
|
+
const methodName = this.findEnclosingMethod(content, offset);
|
|
162
|
+
const errorVar = exceptionVar.trim().split(' ')[0] || 'error';
|
|
163
|
+
return match.replace(/catch\s*\(([^)]*)\)\s*{/, `catch(${exceptionVar}) {
|
|
164
|
+
${loggerName}.error('An error occurred in function ${methodName}()', ${errorVar});`);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async run() {
|
|
168
|
+
this.logger = await Logger.child(this.ctor.name);
|
|
169
|
+
const { flags } = await this.parse(RflibLoggingLwcInstrument);
|
|
170
|
+
this.log(`Scanning LWC components in ${flags.sourcepath}...`);
|
|
171
|
+
await this.processDirectory(flags.sourcepath, flags.dryrun, flags.prettier);
|
|
172
|
+
this.log(`\nInstrumentation complete.`);
|
|
173
|
+
this.log(`Processed files: ${this.processedFiles}`);
|
|
174
|
+
this.log(`Modified files: ${this.modifiedFiles}`);
|
|
175
|
+
this.log(`Formatted files: ${this.formattedFiles}`);
|
|
176
|
+
return {
|
|
177
|
+
processedFiles: this.processedFiles,
|
|
178
|
+
modifiedFiles: this.modifiedFiles,
|
|
179
|
+
formattedFiles: this.formattedFiles
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async processDirectory(dirPath, isDryRun, usePrettier) {
|
|
183
|
+
const files = await fs.promises.readdir(dirPath);
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
const filePath = path.join(dirPath, file);
|
|
186
|
+
const stat = await fs.promises.stat(filePath);
|
|
187
|
+
if (stat.isDirectory()) {
|
|
188
|
+
await this.processDirectory(filePath, isDryRun, usePrettier);
|
|
189
|
+
}
|
|
190
|
+
else if (file.endsWith('.js') && !path.dirname(filePath).includes('aura')) {
|
|
191
|
+
await this.instrumentLwcFile(filePath, isDryRun, usePrettier);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async instrumentLwcFile(filePath, isDryRun, usePrettier) {
|
|
196
|
+
const componentName = path.basename(path.dirname(filePath));
|
|
197
|
+
this.logger.debug(`Processing LWC: ${componentName}`);
|
|
198
|
+
try {
|
|
199
|
+
this.processedFiles++;
|
|
200
|
+
let content = await fs.promises.readFile(filePath, 'utf8');
|
|
201
|
+
const originalContent = content;
|
|
202
|
+
const { loggerName } = RflibLoggingLwcInstrument.detectExistingLogger(content);
|
|
203
|
+
content = RflibLoggingLwcInstrument.addImportAndLogger(content, componentName);
|
|
204
|
+
content = RflibLoggingLwcInstrument.processMethodLogging(content, loggerName);
|
|
205
|
+
content = RflibLoggingLwcInstrument.processTryCatchBlocks(content, loggerName);
|
|
206
|
+
content = RflibLoggingLwcInstrument.processPromiseChains(content, loggerName);
|
|
207
|
+
if (content !== originalContent) {
|
|
208
|
+
this.modifiedFiles++;
|
|
209
|
+
if (!isDryRun) {
|
|
210
|
+
try {
|
|
211
|
+
const finalContent = usePrettier ? await prettier.format(content, this.prettierConfig) : content;
|
|
212
|
+
await fs.promises.writeFile(filePath, finalContent);
|
|
213
|
+
if (usePrettier) {
|
|
214
|
+
this.formattedFiles++;
|
|
215
|
+
this.logger.info(`Modified and formatted: ${filePath}`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.logger.info(`Modified: ${filePath}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
this.logger.warn(`Failed to format ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
223
|
+
await fs.promises.writeFile(filePath, content);
|
|
224
|
+
this.logger.info(`Modified without formatting: ${filePath}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this.logger.info(`Would modify: ${filePath}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
this.logger.error(`Error processing LWC ${componentName}`, error);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=instrument.js.map
|