x-fidelity 3.11.0 → 3.12.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/.xfi-config.json +1 -5
- package/CHANGELOG.md +28 -0
- package/dist/demoConfig/rules/newSdkFeatureNotAdoped-global-rule.json +2 -2
- package/dist/demoConfig/rules/noDatabases-iterative-rule.json +5 -1
- package/dist/demoConfig/rules/sensitiveLogging-iterative-rule.json +1 -3
- package/dist/index.js +2 -17
- package/dist/index.test.js +1 -0
- package/dist/notifications/notificationManager.d.ts +3 -3
- package/dist/notifications/notificationManager.js +137 -42
- package/dist/notifications/providers/emailProvider.js +9 -1
- package/dist/types/notificationTypes.d.ts +0 -1
- package/dist/types/typeDefs.d.ts +0 -4
- package/dist/xfidelity +2 -17
- package/package.json +2 -1
- package/src/demoConfig/rules/newSdkFeatureNotAdoped-global-rule.json +2 -2
- package/src/demoConfig/rules/noDatabases-iterative-rule.json +5 -1
- package/src/demoConfig/rules/sensitiveLogging-iterative-rule.json +1 -3
- package/src/index.test.ts +1 -0
- package/src/index.ts +2 -22
- package/src/notifications/notificationManager.ts +170 -61
- package/src/notifications/providers/emailProvider.ts +9 -1
- package/src/types/notificationTypes.ts +0 -1
- package/src/types/typeDefs.ts +0 -4
package/.xfi-config.json
CHANGED
|
@@ -51,10 +51,6 @@
|
|
|
51
51
|
},
|
|
52
52
|
"codeOwners": true,
|
|
53
53
|
"notifyOnSuccess": false,
|
|
54
|
-
"notifyOnFailure": true
|
|
55
|
-
"customTemplates": {
|
|
56
|
-
"success": "# Success! 🎉\n\nYour codebase passed all X-Fidelity checks.\n\n- Archetype: ${archetype}\n- Files analyzed: ${fileCount}\n- Execution time: ${executionTime} seconds\n\nGreat job keeping the code clean!",
|
|
57
|
-
"failure": "# Issues Detected ⚠️\n\nX-Fidelity found issues in your codebase:\n\n- Archetype: ${archetype}\n- Files analyzed: ${fileCount}\n- Total issues: ${totalIssues}\n - Warnings: ${warningCount}\n - Errors: ${errorCount}\n - Fatalities: ${fatalityCount}\n\n## Affected Files\n${affectedFiles}\n\nPlease address these issues as soon as possible."
|
|
58
|
-
}
|
|
54
|
+
"notifyOnFailure": true
|
|
59
55
|
}
|
|
60
56
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
# [3.12.0](https://github.com/zotoio/x-fidelity/compare/v3.11.0...v3.12.0) (2025-03-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add HTML email support with plain text fallback ([ec0f252](https://github.com/zotoio/x-fidelity/commit/ec0f2529d4f05ca570cd4b11db07ede2d4d21113))
|
|
7
|
+
* correct string interpolation syntax in notification template ([092b64d](https://github.com/zotoio/x-fidelity/commit/092b64d3f614d505c7a792bd7a559e10aed3192c))
|
|
8
|
+
* correct template literal syntax in notification manager ([f5720c2](https://github.com/zotoio/x-fidelity/commit/f5720c230051d8e38bbc95024a02d7298d2299f5))
|
|
9
|
+
* escape nested template literals in notification template ([4feb497](https://github.com/zotoio/x-fidelity/commit/4feb497255615b93655ba7bba8729e55b56b4016))
|
|
10
|
+
* **notify:** email formatting fixes ([06e80a8](https://github.com/zotoio/x-fidelity/commit/06e80a81bab30c1ab03d202d0649306fe331debd))
|
|
11
|
+
* remove duplicate fileIssues declaration in notification manager ([d5678a1](https://github.com/zotoio/x-fidelity/commit/d5678a1620fae5b78ee475b95ee2248051d17c8f))
|
|
12
|
+
* remove local directory paths from notification file links ([d55da96](https://github.com/zotoio/x-fidelity/commit/d55da9637477b0b14acb45d99a8a1a4b9e661c30))
|
|
13
|
+
* remove trailing comma in .xfi-config.json ([41c7368](https://github.com/zotoio/x-fidelity/commit/41c7368a7e7ba8b989f3d8bf2fc0ce9e3e4d1183))
|
|
14
|
+
* replace nested template literals with string concatenation ([89712cf](https://github.com/zotoio/x-fidelity/commit/89712cf77e231863e5bcb6c214fedf5b9f0d1f3f))
|
|
15
|
+
* resolve TypeScript errors with yaml imports and variable declarations ([af2ea8e](https://github.com/zotoio/x-fidelity/commit/af2ea8ee80d48b9dddb1f586d61285518aee0b1c))
|
|
16
|
+
* use repoPath from results metadata instead of undefined variable ([36b42fa](https://github.com/zotoio/x-fidelity/commit/36b42fae14895ba147955806efcf3cc94ec1d306))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* add failed rule names to notification template for each file ([d09d019](https://github.com/zotoio/x-fidelity/commit/d09d019bc1f9eff2f5c375185aec7f8f54fb7f96))
|
|
22
|
+
* add severity levels to failed rules in notification output ([a90ffe7](https://github.com/zotoio/x-fidelity/commit/a90ffe724c440f60f1f1c59be0ec3074316c28c1))
|
|
23
|
+
* add YAML results attachment to email notifications ([e2671b4](https://github.com/zotoio/x-fidelity/commit/e2671b46081168b1ac911f595a05f3301f8df780))
|
|
24
|
+
* enhance notification formatting with HTML and color-coded severity ([f034a85](https://github.com/zotoio/x-fidelity/commit/f034a858c0da629d7fc134818babcbf28fa7b03e))
|
|
25
|
+
* enhance notification templates with clickable links and emojis ([3afbd5d](https://github.com/zotoio/x-fidelity/commit/3afbd5db68cfbb604407e791f58c184961e7dc78))
|
|
26
|
+
* support GitHub Enterprise links in notifications ([092867f](https://github.com/zotoio/x-fidelity/commit/092867fd3948e1bfcc10e8ecfc312686f37c9afa))
|
|
27
|
+
* use git commands to extract GitHub repo details ([8f35471](https://github.com/zotoio/x-fidelity/commit/8f3547145a612444b9320c0b5d1b75529edfaa4a))
|
|
28
|
+
|
|
1
29
|
# [3.11.0](https://github.com/zotoio/x-fidelity/compare/v3.10.0...v3.11.0) (2025-03-09)
|
|
2
30
|
|
|
3
31
|
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"fact": "globalFileAnalysis",
|
|
13
13
|
"params": {
|
|
14
14
|
"newPatterns": [
|
|
15
|
-
"
|
|
15
|
+
"const plugin: XFiPlugin = {"
|
|
16
16
|
],
|
|
17
17
|
"legacyPatterns": [
|
|
18
|
-
"
|
|
18
|
+
"import {.*FactDefn.*} from '\\.\\./types/typeDefs';"
|
|
19
19
|
],
|
|
20
20
|
"fileFilter": ".*\\.(ts|js)$",
|
|
21
21
|
"resultFact": "sdkUsageAnalysis"
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
{
|
|
12
12
|
"fact": "repoFileAnalysis",
|
|
13
13
|
"params": {
|
|
14
|
-
"checkPattern": [
|
|
14
|
+
"checkPattern": [
|
|
15
|
+
"[\\s\\'\\\"\\.](oracle)[\\s\\'\\\"\\.]",
|
|
16
|
+
"[\\s\\'\\\"\\.](postgres)[\\s\\'\\\"\\.]",
|
|
17
|
+
"[\\s\\'\\\"\\.](mongodb)[\\s\\'\\\"\\.]"
|
|
18
|
+
],
|
|
15
19
|
"resultFact": "fileResultsDB"
|
|
16
20
|
},
|
|
17
21
|
"operator": "fileContains",
|
|
@@ -23,11 +23,9 @@
|
|
|
23
23
|
"checkPattern": [
|
|
24
24
|
"(api[_-]?key|auth[_-]?token|access[_-]?token|secret[_-]?key)",
|
|
25
25
|
"(aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key)",
|
|
26
|
-
"(password|passphrase)",
|
|
27
26
|
"(private[_-]?key|ssh[_-]?key)",
|
|
28
27
|
"(oauth[_-]?token|jwt[_-]?token)",
|
|
29
|
-
"db[_-]?password"
|
|
30
|
-
"(declare)"
|
|
28
|
+
"db[_-]?password"
|
|
31
29
|
],
|
|
32
30
|
"resultFact": "fileResults"
|
|
33
31
|
},
|
package/dist/index.js
CHANGED
|
@@ -123,14 +123,11 @@ function main() {
|
|
|
123
123
|
// Send notifications if enabled
|
|
124
124
|
if (notificationConfig.enabled) {
|
|
125
125
|
logger_1.logger.debug('Notifications are enabled, preparing to send report');
|
|
126
|
-
// Get list of affected files (those with issues)
|
|
127
|
-
const affectedFiles = getAffectedFiles(resultMetadata);
|
|
128
126
|
logger_1.logger.debug({
|
|
129
|
-
affectedFilesCount:
|
|
130
|
-
hasRepoConfig: !!resultMetadata.XFI_RESULT.repoXFIConfig
|
|
127
|
+
affectedFilesCount: resultMetadata.XFI_RESULT.issueDetails.length
|
|
131
128
|
}, 'Preparing notification data');
|
|
132
129
|
// Pass the repo config to the notification manager
|
|
133
|
-
yield notificationManager.sendReport(resultMetadata
|
|
130
|
+
yield notificationManager.sendReport(resultMetadata);
|
|
134
131
|
}
|
|
135
132
|
// if results are found, there were issues found in the codebase
|
|
136
133
|
if (resultMetadata.XFI_RESULT.totalIssues > 0) {
|
|
@@ -171,18 +168,6 @@ function main() {
|
|
|
171
168
|
}
|
|
172
169
|
});
|
|
173
170
|
}
|
|
174
|
-
// Helper function to extract affected files from results
|
|
175
|
-
function getAffectedFiles(resultMetadata) {
|
|
176
|
-
const affectedFiles = [];
|
|
177
|
-
if (resultMetadata.XFI_RESULT.issueDetails) {
|
|
178
|
-
for (const issue of resultMetadata.XFI_RESULT.issueDetails) {
|
|
179
|
-
if (issue.filePath && !affectedFiles.includes(issue.filePath)) {
|
|
180
|
-
affectedFiles.push(issue.filePath);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return affectedFiles;
|
|
185
|
-
}
|
|
186
171
|
var configManager_1 = require("./core/configManager");
|
|
187
172
|
Object.defineProperty(exports, "repoDir", { enumerable: true, get: function () { return configManager_1.repoDir; } });
|
|
188
173
|
__exportStar(require("./utils/logger"), exports);
|
package/dist/index.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ResultMetadata
|
|
1
|
+
import { ResultMetadata } from '../types/typeDefs';
|
|
2
2
|
import { NotificationProvider, NotificationConfig } from '../types/notificationTypes';
|
|
3
3
|
export declare class NotificationManager {
|
|
4
4
|
private static instance;
|
|
@@ -8,12 +8,12 @@ export declare class NotificationManager {
|
|
|
8
8
|
private constructor();
|
|
9
9
|
static getInstance(config: NotificationConfig): NotificationManager;
|
|
10
10
|
registerProvider(provider: NotificationProvider): void;
|
|
11
|
-
sendReport(results: ResultMetadata
|
|
11
|
+
sendReport(results: ResultMetadata): Promise<void>;
|
|
12
12
|
private mergeNotificationConfig;
|
|
13
13
|
private getRecipients;
|
|
14
14
|
private loadCodeOwners;
|
|
15
15
|
private getCodeOwnersForFiles;
|
|
16
16
|
private matchesGlob;
|
|
17
|
-
private
|
|
17
|
+
private getAffectedFiles;
|
|
18
18
|
private generateReportContent;
|
|
19
19
|
}
|
|
@@ -14,7 +14,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.NotificationManager = void 0;
|
|
16
16
|
const logger_1 = require("../utils/logger");
|
|
17
|
+
const child_process_1 = require("child_process");
|
|
17
18
|
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const yaml_1 = require("yaml");
|
|
18
20
|
class NotificationManager {
|
|
19
21
|
constructor(config) {
|
|
20
22
|
this.providers = new Map();
|
|
@@ -36,17 +38,16 @@ class NotificationManager {
|
|
|
36
38
|
this.providers.set(provider.name, provider);
|
|
37
39
|
logger_1.logger.info(`Registered notification provider: ${provider.name}`);
|
|
38
40
|
}
|
|
39
|
-
sendReport(results
|
|
41
|
+
sendReport(results) {
|
|
40
42
|
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
-
var _a, _b;
|
|
42
43
|
if (!this.config.enabled) {
|
|
43
44
|
logger_1.logger.debug('Notifications are disabled');
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
47
|
// Merge global config with repo-specific config
|
|
47
|
-
const notifyConfig = this.mergeNotificationConfig(repoXFIConfig);
|
|
48
|
+
const notifyConfig = this.mergeNotificationConfig(results.XFI_RESULT.repoXFIConfig);
|
|
48
49
|
// Determine if we should send notification based on results
|
|
49
|
-
const hasIssues = results.XFI_RESULT.
|
|
50
|
+
const hasIssues = results.XFI_RESULT.issueDetails.length > 0;
|
|
50
51
|
if (hasIssues && !notifyConfig.notifyOnFailure) {
|
|
51
52
|
logger_1.logger.debug('Skipping notification for failure as notifyOnFailure is disabled');
|
|
52
53
|
return;
|
|
@@ -56,7 +57,7 @@ class NotificationManager {
|
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
59
|
// Get recipients from multiple sources
|
|
59
|
-
const recipients = this.getRecipients(
|
|
60
|
+
const recipients = this.getRecipients(this.getAffectedFiles(results), results.XFI_RESULT.repoXFIConfig, notifyConfig);
|
|
60
61
|
if (Object.values(recipients).every(list => list.length === 0)) {
|
|
61
62
|
logger_1.logger.warn('No recipients found for notification');
|
|
62
63
|
return;
|
|
@@ -65,12 +66,7 @@ class NotificationManager {
|
|
|
65
66
|
const subject = hasIssues
|
|
66
67
|
? `[X-Fidelity] Issues found in your codebase (${results.XFI_RESULT.totalIssues})`
|
|
67
68
|
: '[X-Fidelity] Your codebase passed all checks';
|
|
68
|
-
|
|
69
|
-
const templateKey = hasIssues ? 'failure' : 'success';
|
|
70
|
-
const customTemplate = (_b = (_a = repoXFIConfig === null || repoXFIConfig === void 0 ? void 0 : repoXFIConfig.notifications) === null || _a === void 0 ? void 0 : _a.customTemplates) === null || _b === void 0 ? void 0 : _b[templateKey];
|
|
71
|
-
const content = customTemplate
|
|
72
|
-
? this.applyTemplate(customTemplate, results, affectedFiles)
|
|
73
|
-
: this.generateReportContent(results, affectedFiles);
|
|
69
|
+
const content = this.generateReportContent(results);
|
|
74
70
|
// Send through all configured providers
|
|
75
71
|
for (const providerName of this.config.providers) {
|
|
76
72
|
const provider = this.providers.get(providerName);
|
|
@@ -208,42 +204,141 @@ class NotificationManager {
|
|
|
208
204
|
}
|
|
209
205
|
return filePath === pattern;
|
|
210
206
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
.replace(/\${date}/g, new Date().toISOString())
|
|
223
|
-
.replace(/\${executionTime}/g, String(results.XFI_RESULT.durationSeconds));
|
|
207
|
+
// Helper function to extract affected files from results
|
|
208
|
+
getAffectedFiles(results) {
|
|
209
|
+
const affectedFiles = [];
|
|
210
|
+
if (results.XFI_RESULT.issueDetails) {
|
|
211
|
+
for (const issue of results.XFI_RESULT.issueDetails) {
|
|
212
|
+
if (issue.filePath && !affectedFiles.includes(issue.filePath)) {
|
|
213
|
+
affectedFiles.push(issue.filePath);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return affectedFiles;
|
|
224
218
|
}
|
|
225
|
-
generateReportContent(results
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
219
|
+
generateReportContent(results) {
|
|
220
|
+
var _a, _b;
|
|
221
|
+
// Get GitHub details from git commands with fallbacks to env vars
|
|
222
|
+
let githubServer = '';
|
|
223
|
+
let githubRepo = '';
|
|
224
|
+
let githubBranch = '';
|
|
225
|
+
try {
|
|
226
|
+
// Get remote URL and extract server/repo
|
|
227
|
+
const remoteUrl = (_a = (0, child_process_1.execSync)('git config --get remote.origin.url', { encoding: 'utf8', cwd: results.XFI_RESULT.repoPath })) === null || _a === void 0 ? void 0 : _a.toString().trim();
|
|
228
|
+
if (remoteUrl) {
|
|
229
|
+
const match = remoteUrl.match(/^(?:https?:\/\/|git@)([^/:]+)[/:]([^/]+\/[^/.]+)(?:\.git)?$/);
|
|
230
|
+
if (match) {
|
|
231
|
+
githubServer = process.env.GITHUB_SERVER_URL || `https://${match[1]}`;
|
|
232
|
+
githubRepo = process.env.GITHUB_REPOSITORY || match[2];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Get current branch
|
|
236
|
+
githubBranch = (_b = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', cwd: results.XFI_RESULT.repoPath })) === null || _b === void 0 ? void 0 : _b.toString().trim();
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
logger_1.logger.warn('Failed to get git details, falling back to defaults', { error });
|
|
240
|
+
githubServer = process.env.GITHUB_SERVER_URL || 'https://github.com';
|
|
241
|
+
githubRepo = process.env.GITHUB_REPOSITORY || results.XFI_RESULT.repoUrl
|
|
242
|
+
.replace(/\.git$/, '')
|
|
243
|
+
.replace(/^git@([^:]+):/, 'https://$1/')
|
|
244
|
+
.replace(/^https?:\/\/[^\/]+\//, '');
|
|
245
|
+
githubBranch = process.env.GITHUB_REF_NAME || 'main';
|
|
246
|
+
}
|
|
247
|
+
const githubUrl = `${githubServer}/${githubRepo}/blob/${githubBranch}`;
|
|
248
|
+
let fileDetails = '';
|
|
249
|
+
if (results.XFI_RESULT.issueDetails.length > 0) {
|
|
250
|
+
try {
|
|
251
|
+
// Failure template
|
|
252
|
+
fileDetails = results.XFI_RESULT.issueDetails.map(issue => {
|
|
253
|
+
logger_1.logger.debug(issue, `generating report content for issue: ${issue.filePath}`);
|
|
254
|
+
// Remove local path prefix from file paths
|
|
255
|
+
const relativePath = issue.filePath.replace(results.XFI_RESULT.repoPath + '/', '');
|
|
256
|
+
const fileIssues = issue.errors;
|
|
257
|
+
// Start with file name as bold list item with link
|
|
258
|
+
let output = `<li><strong><a href="${githubUrl}/${relativePath}">${relativePath}</a></strong><ul>`;
|
|
259
|
+
// Add rule failures with severity-based colors and line links
|
|
260
|
+
const ruleDetails = fileIssues === null || fileIssues === void 0 ? void 0 : fileIssues.map(error => {
|
|
261
|
+
var _a, _b;
|
|
262
|
+
const ruleName = error.ruleFailure;
|
|
263
|
+
const level = error.level;
|
|
264
|
+
const resultDetails = error === null || error === void 0 ? void 0 : error.details;
|
|
265
|
+
const message = resultDetails === null || resultDetails === void 0 ? void 0 : resultDetails.message;
|
|
266
|
+
let output = `<li>
|
|
267
|
+
<span>${ruleName}: ${level}</span><br>
|
|
268
|
+
<span>${message}</span><ul>
|
|
269
|
+
`;
|
|
270
|
+
if (!Array.isArray((resultDetails === null || resultDetails === void 0 ? void 0 : resultDetails.details) || !((_a = resultDetails === null || resultDetails === void 0 ? void 0 : resultDetails.details) === null || _a === void 0 ? void 0 : _a.details[0].lineNumber))) {
|
|
271
|
+
return '</li>' + output + '<pre>' + (0, yaml_1.stringify)(resultDetails === null || resultDetails === void 0 ? void 0 : resultDetails.details) + '</pre></li></ul></li>';
|
|
272
|
+
}
|
|
273
|
+
const errorDetails = (_b = resultDetails === null || resultDetails === void 0 ? void 0 : resultDetails.details) === null || _b === void 0 ? void 0 : _b.map((errorInstance) => {
|
|
274
|
+
const lineNumber = errorInstance === null || errorInstance === void 0 ? void 0 : errorInstance.lineNumber;
|
|
275
|
+
const lineLink = lineNumber ? `${githubUrl}/${relativePath}#L${lineNumber}` : `${githubUrl}/${relativePath}`;
|
|
276
|
+
const color = level === 'fatality' ? '#f20707' :
|
|
277
|
+
level === 'error' ? '#53046c' :
|
|
278
|
+
level === 'warning' ? '#944801' : '#1a1818';
|
|
279
|
+
return `<li><span style="color: ${color};font-weight:bold">${ruleName}</span>${lineNumber ? ` - <a href="${lineLink}">Line ${lineNumber}</a>` : ''}</li>`;
|
|
280
|
+
}).join('');
|
|
281
|
+
return output + errorDetails + '</ul></li>';
|
|
282
|
+
}).join('');
|
|
283
|
+
logger_1.logger.debug(`fileDetails: ${output + ruleDetails}</ul></li>`);
|
|
284
|
+
return output + ruleDetails + '</ul></li>';
|
|
285
|
+
}).join('');
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
logger_1.logger.error(error, 'Failed to generate report content');
|
|
289
|
+
logger_1.logger.error(JSON.stringify(error));
|
|
290
|
+
logger_1.logger.error(error.message);
|
|
291
|
+
logger_1.logger.error(error.stack);
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
// Generate YAML attachment
|
|
295
|
+
const yamlAttachment = (0, yaml_1.stringify)(results.XFI_RESULT);
|
|
296
|
+
const yamlSection = `
|
|
297
|
+
<h2>📎 Full Results (YAML)</h2>
|
|
298
|
+
<pre style="background-color: #d1e3f6; padding: 16px; border-radius: 6px; overflow-x: auto;">
|
|
299
|
+
${yamlAttachment}
|
|
300
|
+
</pre>`;
|
|
301
|
+
const result = `<h1>🚨 Issues Detected</h1>
|
|
302
|
+
|
|
303
|
+
<p>X-Fidelity found issues in your codebase:</p>
|
|
229
304
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
305
|
+
<h2>📊 Summary</h2>
|
|
306
|
+
<ul>
|
|
307
|
+
<li><strong>Archetype:</strong> <code>${results.XFI_RESULT.archetype}</code></li>
|
|
308
|
+
<li><strong>Files analyzed:</strong> ${results.XFI_RESULT.fileCount}</li>
|
|
309
|
+
<li><strong>Total issues:</strong> ${results.XFI_RESULT.totalIssues}</li>
|
|
310
|
+
<li>⚠️ Warnings: ${results.XFI_RESULT.warningCount}</li>
|
|
311
|
+
<li>❌ Errors: ${results.XFI_RESULT.errorCount}</li>
|
|
312
|
+
<li>🔥 Fatalities: ${results.XFI_RESULT.fatalityCount}</li>
|
|
313
|
+
</ul>
|
|
238
314
|
|
|
239
|
-
|
|
240
|
-
|
|
315
|
+
<h2>📝 Issues by File</h2>
|
|
316
|
+
<ul>
|
|
317
|
+
${fileDetails}
|
|
318
|
+
</ul>
|
|
241
319
|
|
|
242
|
-
|
|
243
|
-
${results.XFI_RESULT.durationSeconds} seconds
|
|
320
|
+
<p>Please address these issues as soon as possible.</p>
|
|
244
321
|
|
|
245
|
-
|
|
246
|
-
`;
|
|
322
|
+
${yamlSection}
|
|
323
|
+
`;
|
|
324
|
+
logger_1.logger.trace(result);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// Success template
|
|
329
|
+
return `<h1>✅ Success!</h1>
|
|
330
|
+
|
|
331
|
+
<p>Your codebase passed all X-Fidelity checks.</p>
|
|
332
|
+
|
|
333
|
+
<h2>📊 Summary</h2>
|
|
334
|
+
<ul>
|
|
335
|
+
<li><strong>Archetype:</strong> <code>${results.XFI_RESULT.archetype}</code></li>
|
|
336
|
+
<li><strong>Files analyzed:</strong> ${results.XFI_RESULT.fileCount}</li>
|
|
337
|
+
<li><strong>Execution time:</strong> ${results.XFI_RESULT.durationSeconds} seconds</li>
|
|
338
|
+
</ul>
|
|
339
|
+
|
|
340
|
+
<p>🎉 Great job keeping the code clean!</p>`;
|
|
341
|
+
}
|
|
247
342
|
}
|
|
248
343
|
}
|
|
249
344
|
exports.NotificationManager = NotificationManager;
|
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.EmailProvider = void 0;
|
|
16
16
|
const logger_1 = require("../../utils/logger");
|
|
17
17
|
const nodemailer_1 = __importDefault(require("nodemailer"));
|
|
18
|
+
const yaml_1 = require("yaml");
|
|
18
19
|
class EmailProvider {
|
|
19
20
|
constructor(config) {
|
|
20
21
|
this.name = 'email';
|
|
@@ -22,6 +23,7 @@ class EmailProvider {
|
|
|
22
23
|
}
|
|
23
24
|
send(notification) {
|
|
24
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
var _a;
|
|
25
27
|
try {
|
|
26
28
|
logger_1.logger.debug({
|
|
27
29
|
config: Object.assign(Object.assign({}, this.config), { auth: {
|
|
@@ -52,11 +54,17 @@ class EmailProvider {
|
|
|
52
54
|
logger_1.logger.error(verifyError, 'Failed to verify SMTP connection');
|
|
53
55
|
return false;
|
|
54
56
|
}
|
|
57
|
+
// Generate plain text version with YAML
|
|
58
|
+
const plainTextContent = `${notification.content.replace(/<[^>]*>/g, '')}
|
|
59
|
+
|
|
60
|
+
--- Full Results ---
|
|
61
|
+
${(0, yaml_1.stringify)((_a = notification.metadata) === null || _a === void 0 ? void 0 : _a.results.XFI_RESULT)}`;
|
|
55
62
|
const info = yield transporter.sendMail({
|
|
56
63
|
from: this.config.from,
|
|
57
64
|
to: notification.recipients.join(', '),
|
|
58
65
|
subject: notification.subject,
|
|
59
|
-
|
|
66
|
+
html: notification.content,
|
|
67
|
+
text: plainTextContent,
|
|
60
68
|
});
|
|
61
69
|
logger_1.logger.info({
|
|
62
70
|
messageId: info.messageId,
|
package/dist/types/typeDefs.d.ts
CHANGED
package/dist/xfidelity
CHANGED
|
@@ -123,14 +123,11 @@ function main() {
|
|
|
123
123
|
// Send notifications if enabled
|
|
124
124
|
if (notificationConfig.enabled) {
|
|
125
125
|
logger_1.logger.debug('Notifications are enabled, preparing to send report');
|
|
126
|
-
// Get list of affected files (those with issues)
|
|
127
|
-
const affectedFiles = getAffectedFiles(resultMetadata);
|
|
128
126
|
logger_1.logger.debug({
|
|
129
|
-
affectedFilesCount:
|
|
130
|
-
hasRepoConfig: !!resultMetadata.XFI_RESULT.repoXFIConfig
|
|
127
|
+
affectedFilesCount: resultMetadata.XFI_RESULT.issueDetails.length
|
|
131
128
|
}, 'Preparing notification data');
|
|
132
129
|
// Pass the repo config to the notification manager
|
|
133
|
-
yield notificationManager.sendReport(resultMetadata
|
|
130
|
+
yield notificationManager.sendReport(resultMetadata);
|
|
134
131
|
}
|
|
135
132
|
// if results are found, there were issues found in the codebase
|
|
136
133
|
if (resultMetadata.XFI_RESULT.totalIssues > 0) {
|
|
@@ -171,18 +168,6 @@ function main() {
|
|
|
171
168
|
}
|
|
172
169
|
});
|
|
173
170
|
}
|
|
174
|
-
// Helper function to extract affected files from results
|
|
175
|
-
function getAffectedFiles(resultMetadata) {
|
|
176
|
-
const affectedFiles = [];
|
|
177
|
-
if (resultMetadata.XFI_RESULT.issueDetails) {
|
|
178
|
-
for (const issue of resultMetadata.XFI_RESULT.issueDetails) {
|
|
179
|
-
if (issue.filePath && !affectedFiles.includes(issue.filePath)) {
|
|
180
|
-
affectedFiles.push(issue.filePath);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return affectedFiles;
|
|
185
|
-
}
|
|
186
171
|
var configManager_1 = require("./core/configManager");
|
|
187
172
|
Object.defineProperty(exports, "repoDir", { enumerable: true, get: function () { return configManager_1.repoDir; } });
|
|
188
173
|
__exportStar(require("./utils/logger"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x-fidelity",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "cli for opinionated framework adherence checks",
|
|
5
5
|
"main": "dist/index",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
"prettyjson": "^1.2.5",
|
|
102
102
|
"sanitize-filename": "^1.6.3",
|
|
103
103
|
"semver": "^7.7.1",
|
|
104
|
+
"yaml": "^2.7.0",
|
|
104
105
|
"yarn": "^1.22.22"
|
|
105
106
|
},
|
|
106
107
|
"peerDependencies": {
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"fact": "globalFileAnalysis",
|
|
13
13
|
"params": {
|
|
14
14
|
"newPatterns": [
|
|
15
|
-
"
|
|
15
|
+
"const plugin: XFiPlugin = {"
|
|
16
16
|
],
|
|
17
17
|
"legacyPatterns": [
|
|
18
|
-
"
|
|
18
|
+
"import {.*FactDefn.*} from '\\.\\./types/typeDefs';"
|
|
19
19
|
],
|
|
20
20
|
"fileFilter": ".*\\.(ts|js)$",
|
|
21
21
|
"resultFact": "sdkUsageAnalysis"
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
{
|
|
12
12
|
"fact": "repoFileAnalysis",
|
|
13
13
|
"params": {
|
|
14
|
-
"checkPattern": [
|
|
14
|
+
"checkPattern": [
|
|
15
|
+
"[\\s\\'\\\"\\.](oracle)[\\s\\'\\\"\\.]",
|
|
16
|
+
"[\\s\\'\\\"\\.](postgres)[\\s\\'\\\"\\.]",
|
|
17
|
+
"[\\s\\'\\\"\\.](mongodb)[\\s\\'\\\"\\.]"
|
|
18
|
+
],
|
|
15
19
|
"resultFact": "fileResultsDB"
|
|
16
20
|
},
|
|
17
21
|
"operator": "fileContains",
|
|
@@ -23,11 +23,9 @@
|
|
|
23
23
|
"checkPattern": [
|
|
24
24
|
"(api[_-]?key|auth[_-]?token|access[_-]?token|secret[_-]?key)",
|
|
25
25
|
"(aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key)",
|
|
26
|
-
"(password|passphrase)",
|
|
27
26
|
"(private[_-]?key|ssh[_-]?key)",
|
|
28
27
|
"(oauth[_-]?token|jwt[_-]?token)",
|
|
29
|
-
"db[_-]?password"
|
|
30
|
-
"(declare)"
|
|
28
|
+
"db[_-]?password"
|
|
31
29
|
],
|
|
32
30
|
"resultFact": "fileResults"
|
|
33
31
|
},
|
package/src/index.test.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -81,18 +81,13 @@ export async function main() {
|
|
|
81
81
|
// Send notifications if enabled
|
|
82
82
|
if (notificationConfig.enabled) {
|
|
83
83
|
logger.debug('Notifications are enabled, preparing to send report');
|
|
84
|
-
// Get list of affected files (those with issues)
|
|
85
|
-
const affectedFiles = getAffectedFiles(resultMetadata);
|
|
86
84
|
logger.debug({
|
|
87
|
-
affectedFilesCount:
|
|
88
|
-
hasRepoConfig: !!resultMetadata.XFI_RESULT.repoXFIConfig
|
|
85
|
+
affectedFilesCount: resultMetadata.XFI_RESULT.issueDetails.length
|
|
89
86
|
}, 'Preparing notification data');
|
|
90
87
|
|
|
91
88
|
// Pass the repo config to the notification manager
|
|
92
89
|
await notificationManager.sendReport(
|
|
93
|
-
resultMetadata
|
|
94
|
-
affectedFiles,
|
|
95
|
-
resultMetadata.XFI_RESULT.repoXFIConfig
|
|
90
|
+
resultMetadata
|
|
96
91
|
);
|
|
97
92
|
}
|
|
98
93
|
|
|
@@ -135,21 +130,6 @@ export async function main() {
|
|
|
135
130
|
}
|
|
136
131
|
}
|
|
137
132
|
|
|
138
|
-
// Helper function to extract affected files from results
|
|
139
|
-
function getAffectedFiles(resultMetadata: ResultMetadata): string[] {
|
|
140
|
-
const affectedFiles: string[] = [];
|
|
141
|
-
|
|
142
|
-
if (resultMetadata.XFI_RESULT.issueDetails) {
|
|
143
|
-
for (const issue of resultMetadata.XFI_RESULT.issueDetails) {
|
|
144
|
-
if (issue.filePath && !affectedFiles.includes(issue.filePath)) {
|
|
145
|
-
affectedFiles.push(issue.filePath);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return affectedFiles;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
133
|
export { repoDir } from './core/configManager';
|
|
154
134
|
export * from './utils/logger';
|
|
155
135
|
export * from './types';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { logger } from '../utils/logger';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
2
3
|
import { ResultMetadata, RepoXFIConfig } from '../types/typeDefs';
|
|
3
4
|
import { Notification, NotificationProvider, NotificationConfig, CodeOwner } from '../types/notificationTypes';
|
|
4
5
|
import fs from 'fs';
|
|
5
|
-
import
|
|
6
|
+
import { stringify as yamlStringify } from 'yaml';
|
|
6
7
|
|
|
7
8
|
export class NotificationManager {
|
|
8
9
|
private static instance: NotificationManager;
|
|
@@ -33,17 +34,17 @@ export class NotificationManager {
|
|
|
33
34
|
logger.info(`Registered notification provider: ${provider.name}`);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
public async sendReport(results: ResultMetadata
|
|
37
|
+
public async sendReport(results: ResultMetadata): Promise<void> {
|
|
37
38
|
if (!this.config.enabled) {
|
|
38
39
|
logger.debug('Notifications are disabled');
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// Merge global config with repo-specific config
|
|
43
|
-
const notifyConfig = this.mergeNotificationConfig(repoXFIConfig);
|
|
44
|
+
const notifyConfig = this.mergeNotificationConfig(results.XFI_RESULT.repoXFIConfig);
|
|
44
45
|
|
|
45
46
|
// Determine if we should send notification based on results
|
|
46
|
-
const hasIssues = results.XFI_RESULT.
|
|
47
|
+
const hasIssues = results.XFI_RESULT.issueDetails.length > 0;
|
|
47
48
|
if (hasIssues && !notifyConfig.notifyOnFailure) {
|
|
48
49
|
logger.debug('Skipping notification for failure as notifyOnFailure is disabled');
|
|
49
50
|
return;
|
|
@@ -54,24 +55,18 @@ export class NotificationManager {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
// Get recipients from multiple sources
|
|
57
|
-
const recipients = this.getRecipients(
|
|
58
|
+
const recipients = this.getRecipients(this.getAffectedFiles(results), results.XFI_RESULT.repoXFIConfig, notifyConfig);
|
|
58
59
|
if (Object.values(recipients).every(list => list.length === 0)) {
|
|
59
60
|
logger.warn('No recipients found for notification');
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// Generate report content
|
|
64
|
-
const subject = hasIssues
|
|
65
|
+
const subject = hasIssues
|
|
65
66
|
? `[X-Fidelity] Issues found in your codebase (${results.XFI_RESULT.totalIssues})`
|
|
66
67
|
: '[X-Fidelity] Your codebase passed all checks';
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const templateKey = hasIssues ? 'failure' : 'success';
|
|
70
|
-
const customTemplate = repoXFIConfig?.notifications?.customTemplates?.[templateKey];
|
|
71
|
-
|
|
72
|
-
const content = customTemplate
|
|
73
|
-
? this.applyTemplate(customTemplate, results, affectedFiles)
|
|
74
|
-
: this.generateReportContent(results, affectedFiles);
|
|
68
|
+
|
|
69
|
+
const content = this.generateReportContent(results);
|
|
75
70
|
|
|
76
71
|
// Send through all configured providers
|
|
77
72
|
for (const providerName of this.config.providers) {
|
|
@@ -89,9 +84,9 @@ export class NotificationManager {
|
|
|
89
84
|
recipients: providerRecipients,
|
|
90
85
|
subject,
|
|
91
86
|
content,
|
|
92
|
-
metadata: {
|
|
87
|
+
metadata: {
|
|
93
88
|
results,
|
|
94
|
-
ciRunUrl: process.env.CI_RUN_URL || process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
|
|
89
|
+
ciRunUrl: process.env.CI_RUN_URL || process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
|
|
95
90
|
? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
|
|
96
91
|
: undefined
|
|
97
92
|
}
|
|
@@ -115,22 +110,22 @@ export class NotificationManager {
|
|
|
115
110
|
|
|
116
111
|
return {
|
|
117
112
|
...this.config,
|
|
118
|
-
notifyOnSuccess: repoXFIConfig.notifications.notifyOnSuccess !== undefined
|
|
119
|
-
? repoXFIConfig.notifications.notifyOnSuccess
|
|
113
|
+
notifyOnSuccess: repoXFIConfig.notifications.notifyOnSuccess !== undefined
|
|
114
|
+
? repoXFIConfig.notifications.notifyOnSuccess
|
|
120
115
|
: this.config.notifyOnSuccess,
|
|
121
|
-
notifyOnFailure: repoXFIConfig.notifications.notifyOnFailure !== undefined
|
|
122
|
-
? repoXFIConfig.notifications.notifyOnFailure
|
|
116
|
+
notifyOnFailure: repoXFIConfig.notifications.notifyOnFailure !== undefined
|
|
117
|
+
? repoXFIConfig.notifications.notifyOnFailure
|
|
123
118
|
: this.config.notifyOnFailure,
|
|
124
119
|
// Use codeOwners setting from repo config if specified
|
|
125
|
-
codeOwnersEnabled: repoXFIConfig.notifications.codeOwners !== undefined
|
|
126
|
-
? repoXFIConfig.notifications.codeOwners
|
|
120
|
+
codeOwnersEnabled: repoXFIConfig.notifications.codeOwners !== undefined
|
|
121
|
+
? repoXFIConfig.notifications.codeOwners
|
|
127
122
|
: this.config.codeOwnersEnabled
|
|
128
123
|
};
|
|
129
124
|
}
|
|
130
125
|
|
|
131
126
|
private getRecipients(
|
|
132
|
-
affectedFiles: string[],
|
|
133
|
-
repoXFIConfig?: RepoXFIConfig,
|
|
127
|
+
affectedFiles: string[],
|
|
128
|
+
repoXFIConfig?: RepoXFIConfig,
|
|
134
129
|
notifyConfig?: NotificationConfig
|
|
135
130
|
): Record<string, string[]> {
|
|
136
131
|
const result: Record<string, string[]> = {
|
|
@@ -179,11 +174,11 @@ export class NotificationManager {
|
|
|
179
174
|
try {
|
|
180
175
|
const codeOwnersContent = fs.readFileSync(this.config.codeOwnersPath, 'utf8');
|
|
181
176
|
const lines = codeOwnersContent.split('\n');
|
|
182
|
-
|
|
177
|
+
|
|
183
178
|
for (const line of lines) {
|
|
184
179
|
// Skip comments and empty lines
|
|
185
180
|
if (line.trim().startsWith('#') || line.trim() === '') continue;
|
|
186
|
-
|
|
181
|
+
|
|
187
182
|
const parts = line.trim().split(/\s+/);
|
|
188
183
|
if (parts.length >= 2) {
|
|
189
184
|
const path = parts[0];
|
|
@@ -191,11 +186,11 @@ export class NotificationManager {
|
|
|
191
186
|
// Remove @ from GitHub usernames if present
|
|
192
187
|
return owner.startsWith('@') ? owner.substring(1) : owner;
|
|
193
188
|
});
|
|
194
|
-
|
|
189
|
+
|
|
195
190
|
this.codeOwners.push({ path, owners });
|
|
196
191
|
}
|
|
197
192
|
}
|
|
198
|
-
|
|
193
|
+
|
|
199
194
|
logger.info(`Loaded ${this.codeOwners.length} code owner entries`);
|
|
200
195
|
} catch (error) {
|
|
201
196
|
logger.error(error, `Failed to load CODEOWNERS file from ${this.config.codeOwnersPath}`);
|
|
@@ -204,7 +199,7 @@ export class NotificationManager {
|
|
|
204
199
|
|
|
205
200
|
private getCodeOwnersForFiles(files: string[]): string[] {
|
|
206
201
|
const owners = new Set<string>();
|
|
207
|
-
|
|
202
|
+
|
|
208
203
|
for (const file of files) {
|
|
209
204
|
for (const codeOwner of this.codeOwners) {
|
|
210
205
|
// Simple glob matching (can be enhanced with proper glob matching)
|
|
@@ -213,7 +208,7 @@ export class NotificationManager {
|
|
|
213
208
|
}
|
|
214
209
|
}
|
|
215
210
|
}
|
|
216
|
-
|
|
211
|
+
|
|
217
212
|
return Array.from(owners);
|
|
218
213
|
}
|
|
219
214
|
|
|
@@ -231,42 +226,156 @@ export class NotificationManager {
|
|
|
231
226
|
return filePath === pattern;
|
|
232
227
|
}
|
|
233
228
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
229
|
+
// Helper function to extract affected files from results
|
|
230
|
+
private getAffectedFiles(results: ResultMetadata): string[] {
|
|
231
|
+
const affectedFiles: string[] = [];
|
|
232
|
+
|
|
233
|
+
if (results.XFI_RESULT.issueDetails) {
|
|
234
|
+
for (const issue of results.XFI_RESULT.issueDetails) {
|
|
235
|
+
if (issue.filePath && !affectedFiles.includes(issue.filePath)) {
|
|
236
|
+
affectedFiles.push(issue.filePath);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return affectedFiles;
|
|
247
242
|
}
|
|
248
243
|
|
|
249
|
-
private generateReportContent(results: ResultMetadata
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
244
|
+
private generateReportContent(results: ResultMetadata): string {
|
|
245
|
+
// Get GitHub details from git commands with fallbacks to env vars
|
|
246
|
+
let githubServer = '';
|
|
247
|
+
let githubRepo = '';
|
|
248
|
+
let githubBranch = '';
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
// Get remote URL and extract server/repo
|
|
252
|
+
const remoteUrl = execSync('git config --get remote.origin.url', { encoding: 'utf8', cwd: results.XFI_RESULT.repoPath })?.toString().trim();
|
|
253
|
+
if (remoteUrl) {
|
|
254
|
+
const match = remoteUrl.match(/^(?:https?:\/\/|git@)([^/:]+)[/:]([^/]+\/[^/.]+)(?:\.git)?$/);
|
|
255
|
+
if (match) {
|
|
256
|
+
githubServer = process.env.GITHUB_SERVER_URL || `https://${match[1]}`;
|
|
257
|
+
githubRepo = process.env.GITHUB_REPOSITORY || match[2];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get current branch
|
|
262
|
+
githubBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', cwd: results.XFI_RESULT.repoPath })?.toString().trim();
|
|
263
|
+
} catch (error) {
|
|
264
|
+
logger.warn('Failed to get git details, falling back to defaults', { error });
|
|
265
|
+
githubServer = process.env.GITHUB_SERVER_URL || 'https://github.com';
|
|
266
|
+
githubRepo = process.env.GITHUB_REPOSITORY || results.XFI_RESULT.repoUrl
|
|
267
|
+
.replace(/\.git$/, '')
|
|
268
|
+
.replace(/^git@([^:]+):/, 'https://$1/')
|
|
269
|
+
.replace(/^https?:\/\/[^\/]+\//, '');
|
|
270
|
+
githubBranch = process.env.GITHUB_REF_NAME || 'main';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const githubUrl = `${githubServer}/${githubRepo}/blob/${githubBranch}`;
|
|
274
|
+
|
|
275
|
+
let fileDetails = '';
|
|
276
|
+
|
|
277
|
+
if (results.XFI_RESULT.issueDetails.length > 0) {
|
|
253
278
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
- Exemptions: ${results.XFI_RESULT.exemptCount}
|
|
279
|
+
try {
|
|
280
|
+
// Failure template
|
|
281
|
+
fileDetails = results.XFI_RESULT.issueDetails.map(issue => {
|
|
282
|
+
logger.debug(issue, `generating report content for issue: ${issue.filePath}`);
|
|
283
|
+
// Remove local path prefix from file paths
|
|
284
|
+
const relativePath = issue.filePath.replace(results.XFI_RESULT.repoPath + '/', '');
|
|
285
|
+
const fileIssues = issue.errors;
|
|
262
286
|
|
|
263
|
-
|
|
264
|
-
|
|
287
|
+
// Start with file name as bold list item with link
|
|
288
|
+
let output = `<li><strong><a href="${githubUrl}/${relativePath}">${relativePath}</a></strong><ul>`;
|
|
265
289
|
|
|
266
|
-
|
|
267
|
-
|
|
290
|
+
// Add rule failures with severity-based colors and line links
|
|
291
|
+
const ruleDetails = fileIssues?.map(error => {
|
|
292
|
+
const ruleName = error.ruleFailure;
|
|
293
|
+
const level = error.level;
|
|
294
|
+
const resultDetails = error?.details;
|
|
295
|
+
const message = resultDetails?.message;
|
|
268
296
|
|
|
269
|
-
|
|
270
|
-
|
|
297
|
+
let output = `<li>
|
|
298
|
+
<span>${ruleName}: ${level}</span><br>
|
|
299
|
+
<span>${message}</span><ul>
|
|
300
|
+
`;
|
|
301
|
+
|
|
302
|
+
if (!Array.isArray(resultDetails?.details || !resultDetails?.details?.details[0].lineNumber)) {
|
|
303
|
+
return '</li>' + output + '<pre>' + yamlStringify(resultDetails?.details) + '</pre></li></ul></li>';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const errorDetails = resultDetails?.details?.map((errorInstance: any) => {
|
|
307
|
+
const lineNumber = errorInstance?.lineNumber;
|
|
308
|
+
const lineLink = lineNumber ? `${githubUrl}/${relativePath}#L${lineNumber}` : `${githubUrl}/${relativePath}`;
|
|
309
|
+
const color = level === 'fatality' ? '#f20707' :
|
|
310
|
+
level === 'error' ? '#53046c' :
|
|
311
|
+
level === 'warning' ? '#944801' : '#1a1818';
|
|
312
|
+
|
|
313
|
+
return `<li><span style="color: ${color};font-weight:bold">${ruleName}</span>${lineNumber ? ` - <a href="${lineLink}">Line ${lineNumber}</a>` : ''}</li>`;
|
|
314
|
+
}).join('');
|
|
315
|
+
|
|
316
|
+
return output + errorDetails + '</ul></li>';
|
|
317
|
+
}).join('');
|
|
318
|
+
|
|
319
|
+
logger.debug(`fileDetails: ${output + ruleDetails}</ul></li>`);
|
|
320
|
+
return output + ruleDetails + '</ul></li>';
|
|
321
|
+
}).join('');
|
|
322
|
+
|
|
323
|
+
} catch (error: any) {
|
|
324
|
+
logger.error(error, 'Failed to generate report content');
|
|
325
|
+
logger.error(JSON.stringify(error))
|
|
326
|
+
logger.error(error.message)
|
|
327
|
+
logger.error(error.stack)
|
|
328
|
+
return '';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
// Generate YAML attachment
|
|
333
|
+
const yamlAttachment = yamlStringify(results.XFI_RESULT);
|
|
334
|
+
const yamlSection = `
|
|
335
|
+
<h2>📎 Full Results (YAML)</h2>
|
|
336
|
+
<pre style="background-color: #d1e3f6; padding: 16px; border-radius: 6px; overflow-x: auto;">
|
|
337
|
+
${yamlAttachment}
|
|
338
|
+
</pre>`;
|
|
339
|
+
|
|
340
|
+
const result = `<h1>🚨 Issues Detected</h1>
|
|
341
|
+
|
|
342
|
+
<p>X-Fidelity found issues in your codebase:</p>
|
|
343
|
+
|
|
344
|
+
<h2>📊 Summary</h2>
|
|
345
|
+
<ul>
|
|
346
|
+
<li><strong>Archetype:</strong> <code>${results.XFI_RESULT.archetype}</code></li>
|
|
347
|
+
<li><strong>Files analyzed:</strong> ${results.XFI_RESULT.fileCount}</li>
|
|
348
|
+
<li><strong>Total issues:</strong> ${results.XFI_RESULT.totalIssues}</li>
|
|
349
|
+
<li>⚠️ Warnings: ${results.XFI_RESULT.warningCount}</li>
|
|
350
|
+
<li>❌ Errors: ${results.XFI_RESULT.errorCount}</li>
|
|
351
|
+
<li>🔥 Fatalities: ${results.XFI_RESULT.fatalityCount}</li>
|
|
352
|
+
</ul>
|
|
353
|
+
|
|
354
|
+
<h2>📝 Issues by File</h2>
|
|
355
|
+
<ul>
|
|
356
|
+
${fileDetails}
|
|
357
|
+
</ul>
|
|
358
|
+
|
|
359
|
+
<p>Please address these issues as soon as possible.</p>
|
|
360
|
+
|
|
361
|
+
${yamlSection}
|
|
362
|
+
`
|
|
363
|
+
logger.trace(result)
|
|
364
|
+
return result
|
|
365
|
+
} else {
|
|
366
|
+
// Success template
|
|
367
|
+
return `<h1>✅ Success!</h1>
|
|
368
|
+
|
|
369
|
+
<p>Your codebase passed all X-Fidelity checks.</p>
|
|
370
|
+
|
|
371
|
+
<h2>📊 Summary</h2>
|
|
372
|
+
<ul>
|
|
373
|
+
<li><strong>Archetype:</strong> <code>${results.XFI_RESULT.archetype}</code></li>
|
|
374
|
+
<li><strong>Files analyzed:</strong> ${results.XFI_RESULT.fileCount}</li>
|
|
375
|
+
<li><strong>Execution time:</strong> ${results.XFI_RESULT.durationSeconds} seconds</li>
|
|
376
|
+
</ul>
|
|
377
|
+
|
|
378
|
+
<p>🎉 Great job keeping the code clean!</p>`;
|
|
379
|
+
}
|
|
271
380
|
}
|
|
272
381
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NotificationProvider, Notification } from '../../types/notificationTypes';
|
|
2
2
|
import { logger } from '../../utils/logger';
|
|
3
3
|
import nodemailer from 'nodemailer';
|
|
4
|
+
import { stringify as yamlStringify } from 'yaml';
|
|
4
5
|
|
|
5
6
|
export interface EmailProviderConfig {
|
|
6
7
|
host: string;
|
|
@@ -57,11 +58,18 @@ export class EmailProvider implements NotificationProvider {
|
|
|
57
58
|
return false;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
// Generate plain text version with YAML
|
|
62
|
+
const plainTextContent = `${notification.content.replace(/<[^>]*>/g, '')}
|
|
63
|
+
|
|
64
|
+
--- Full Results ---
|
|
65
|
+
${yamlStringify(notification.metadata?.results.XFI_RESULT)}`;
|
|
66
|
+
|
|
60
67
|
const info = await transporter.sendMail({
|
|
61
68
|
from: this.config.from,
|
|
62
69
|
to: notification.recipients.join(', '),
|
|
63
70
|
subject: notification.subject,
|
|
64
|
-
|
|
71
|
+
html: notification.content,
|
|
72
|
+
text: plainTextContent,
|
|
65
73
|
});
|
|
66
74
|
|
|
67
75
|
logger.info({
|
package/src/types/typeDefs.ts
CHANGED
|
@@ -289,10 +289,6 @@ export interface RepoXFIConfig {
|
|
|
289
289
|
codeOwners?: boolean;
|
|
290
290
|
notifyOnSuccess?: boolean;
|
|
291
291
|
notifyOnFailure?: boolean;
|
|
292
|
-
customTemplates?: {
|
|
293
|
-
success?: string;
|
|
294
|
-
failure?: string;
|
|
295
|
-
};
|
|
296
292
|
};
|
|
297
293
|
[key: string]: any; // Allow for additional properties
|
|
298
294
|
}
|