x-fidelity 3.3.0 → 3.4.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/core/cli.js +30 -14
  3. package/dist/core/engine/analyzer.js +6 -2
  4. package/dist/core/engine/analyzer.test.js +11 -3
  5. package/dist/core/engine/engineRunner.js +4 -2
  6. package/dist/demoConfig/node-fullstack.json +4 -2
  7. package/dist/demoConfig/rules/regexMatch-example-iterative-rule.json +28 -0
  8. package/dist/demoConfig/rules/sensitiveLogging-iterative-rule.json +2 -1
  9. package/dist/facts/repoDependencyFacts.js +0 -1
  10. package/dist/index.js +11 -8
  11. package/dist/operators/index.js +3 -1
  12. package/dist/operators/nonStandardDirectoryStructure.js +1 -1
  13. package/dist/operators/regexMatch.d.ts +10 -0
  14. package/dist/operators/regexMatch.js +46 -0
  15. package/dist/operators/regexMatch.test.d.ts +1 -0
  16. package/dist/operators/regexMatch.test.js +52 -0
  17. package/dist/types/typeDefs.d.ts +3 -0
  18. package/dist/utils/logger.js +4 -9
  19. package/dist/xfidelity +11 -8
  20. package/package.json +2 -2
  21. package/src/core/cli.ts +31 -15
  22. package/src/core/engine/analyzer.test.ts +11 -3
  23. package/src/core/engine/analyzer.ts +7 -2
  24. package/src/core/engine/engineRunner.ts +4 -2
  25. package/src/demoConfig/node-fullstack.json +4 -2
  26. package/src/demoConfig/rules/regexMatch-example-iterative-rule.json +28 -0
  27. package/src/demoConfig/rules/sensitiveLogging-iterative-rule.json +2 -1
  28. package/src/facts/repoDependencyFacts.ts +0 -1
  29. package/src/index.ts +15 -11
  30. package/src/operators/index.ts +2 -0
  31. package/src/operators/nonStandardDirectoryStructure.ts +1 -1
  32. package/src/operators/regexMatch.test.ts +59 -0
  33. package/src/operators/regexMatch.ts +51 -0
  34. package/src/types/typeDefs.ts +4 -1
  35. package/src/utils/logger.ts +3 -9
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ # [3.4.0](https://github.com/zotoio/x-fidelity/compare/v3.3.1...v3.4.0) (2025-02-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add support for all modern regex flags in pattern matching ([847a320](https://github.com/zotoio/x-fidelity/commit/847a32068b68db66c1a9d84090bae4bc918f36d0))
7
+ * correct regex test case to use case-insensitive flag ([96d906c](https://github.com/zotoio/x-fidelity/commit/96d906c23833f482d649df866ca36f28f609bbd6))
8
+
9
+
10
+ ### Features
11
+
12
+ * add regex pattern matching operator with tests ([8ac216f](https://github.com/zotoio/x-fidelity/commit/8ac216f222a28367eba971f0f187fad4173167b3))
13
+ * **operators:** add regexMatch general purpose operator ([8270696](https://github.com/zotoio/x-fidelity/commit/82706966e9a52c089848f785007f415e614b67da))
14
+
15
+ ## [3.3.1](https://github.com/zotoio/x-fidelity/compare/v3.3.0...v3.3.1) (2025-02-26)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **engine:** node 18 ([6c3e157](https://github.com/zotoio/x-fidelity/commit/6c3e157d0873dbd6148c568f213ee7ad997744bd))
21
+
1
22
  # [3.3.0](https://github.com/zotoio/x-fidelity/compare/v3.2.0...v3.3.0) (2025-02-22)
2
23
 
3
24
 
package/dist/core/cli.js CHANGED
@@ -15,6 +15,18 @@ const prettyjson_1 = __importDefault(require("prettyjson"));
15
15
  exports.DEMO_CONFIG_PATH = path_1.default.resolve(__dirname, '../demoConfig');
16
16
  exports.options = commander_1.program.opts();
17
17
  function initCLI() {
18
+ const bannerArt = `\n
19
+ =====================================
20
+ __ __ ________ ______
21
+ | ## | ## | ######## \\######
22
+ \\##\\/ ## ______ | ##__ | ##
23
+ >## ## | \\| ## \\ | ##
24
+ / ####\\ \\######| ###### | ##
25
+ | ## \\##\\ | ## _| ##_
26
+ | ## | ## | ## | ## \\
27
+ \\## \\## \\## \\######
28
+ -------------------------------------
29
+ `;
18
30
  // Ensure logger is initialized
19
31
  if (!logger_1.logger || typeof logger_1.logger.info !== 'function') {
20
32
  console.error({ msg: 'Logger is not properly initialized' });
@@ -40,7 +52,7 @@ function initCLI() {
40
52
  }
41
53
  });
42
54
  commander_1.program
43
- .option("-d, --dir <directory>", "code directory to analyze. equivalent of directory argument")
55
+ .option("-d, --dir <directory>", "local git repo directory path to analyze. equivalent of directory argument")
44
56
  .option("-a, --archetype <archetype>", "The archetype to use for analysis", "node-fullstack")
45
57
  .option("-c, --configServer <configServer>", "The config server URL for fetching remote archetype configurations and rules. This takes precedence over localConfigPath.")
46
58
  .option("-o, --openaiEnabled <boolean>", "Enable OpenAI analysis", false)
@@ -53,7 +65,11 @@ function initCLI() {
53
65
  .option("-x, --examine <archetype>", "Examine the archetype configuration and rules")
54
66
  .version(package_json_1.version, "-v, --version", "Output the version number of xfidelity")
55
67
  .helpOption("-h, --help", "Display help for command")
56
- .argument('[directory]', 'code directory to analyze');
68
+ .summary("CLI for analyzing codebases for architectural fidelity")
69
+ .usage("[directory] [options]")
70
+ .argument('[directory]', 'local git repo directory path to analyze')
71
+ .addHelpText('before', bannerArt)
72
+ .addHelpText('after', '-------------------------------------');
57
73
  commander_1.program.parse(process.argv);
58
74
  // Resolve paths
59
75
  const resolvePath = (inputPath) => {
@@ -90,18 +106,18 @@ function initCLI() {
90
106
  if (process.env.NODE_ENV !== 'test')
91
107
  commander_1.program.error(`LocalConfigPath does not exist or is invalid: ${error}`);
92
108
  }
93
- const bannerArt = `\n
94
- =====================================
95
- __ __ ________ ______
96
- | ## | ## | ######## \\######
97
- \\##\\/ ## ______ | ##__ | ##
98
- >## ## | \\| ## \\ | ##
99
- / ####\\ \\######| ###### | ##
100
- | ## \\##\\ | ## _| ##_
101
- | ## | ## | ## | ## \\
102
- \\## \\## \\## \\######
103
- -------------------------------------
104
- `;
109
+ // const bannerArt = `\n
110
+ // =====================================
111
+ // __ __ ________ ______
112
+ // | ## | ## | ######## \\######
113
+ // \\##\\/ ## ______ | ##__ | ##
114
+ // >## ## | \\| ## \\ | ##
115
+ // / ####\\ \\######| ###### | ##
116
+ // | ## \\##\\ | ## _| ##_
117
+ // | ## | ## | ## | ## \\
118
+ // \\## \\## \\## \\######
119
+ // -------------------------------------
120
+ // `;
105
121
  logger_1.logger.info(bannerArt);
106
122
  logger_1.logger.info(`\n${prettyjson_1.default.render({
107
123
  version: package_json_1.version,
@@ -87,22 +87,25 @@ function analyzeCodebase(params) {
87
87
  const finishMsg = `\n==========================\nCHECKS COMPLETED..\n==========================`;
88
88
  logger_1.logger.info(finishMsg);
89
89
  const totalFailureCount = (0, utils_1.countRuleFailures)(failures);
90
- logger_1.logger.info(`${fileData.length} files analyzed. ${totalFailureCount} rule failures.`);
90
+ logger_1.logger.info(`${fileData.length - 1} files analyzed. ${totalFailureCount} rule failures.`);
91
91
  const fatalityCount = (0, utils_1.countRuleFailures)(failures, 'fatality');
92
92
  const warningCount = (0, utils_1.countRuleFailures)(failures, 'warning');
93
93
  const exemptCount = (0, utils_1.countRuleFailures)(failures, 'exempt');
94
94
  const errorCount = (0, utils_1.countRuleFailures)(failures, 'error');
95
95
  const finishTime = new Date().getTime();
96
+ const memoryUsage = process.memoryUsage();
97
+ logger_1.logger.info('Assemblying result metadata..');
96
98
  const resultMetadata = {
97
99
  XFI_RESULT: {
98
100
  archetype,
99
101
  telemetryData,
102
+ memoryUsage,
100
103
  repoXFIConfig: repoXFIConfig,
101
104
  issueDetails: failures,
102
105
  startTime: telemetryData.startTime,
103
106
  finishTime: finishTime,
104
107
  durationSeconds: (finishTime - telemetryData.startTime) / 1000,
105
- fileCount: fileData.length,
108
+ fileCount: fileData.length - 1,
106
109
  totalIssues: totalFailureCount,
107
110
  warningCount: warningCount,
108
111
  fatalityCount: fatalityCount,
@@ -110,6 +113,7 @@ function analyzeCodebase(params) {
110
113
  exemptCount: exemptCount,
111
114
  options: cli_1.options,
112
115
  repoPath,
116
+ repoUrl
113
117
  }
114
118
  };
115
119
  // Send telemetry for analysis end
@@ -106,15 +106,17 @@ describe('analyzeCodebase', () => {
106
106
  XFI_RESULT: expect.objectContaining({
107
107
  archetype: 'node-fullstack',
108
108
  repoPath: 'mockRepoPath',
109
- fileCount: 3,
109
+ fileCount: expect.any(Number),
110
110
  totalIssues: expect.any(Number),
111
111
  warningCount: expect.any(Number),
112
112
  fatalityCount: expect.any(Number),
113
+ errorCount: expect.any(Number),
113
114
  exemptCount: expect.any(Number),
114
115
  issueDetails: expect.any(Array),
115
116
  durationSeconds: expect.any(Number),
116
117
  finishTime: expect.any(Number),
117
118
  startTime: expect.any(Number),
119
+ memoryUsage: expect.any(Object),
118
120
  options: expect.objectContaining({
119
121
  archetype: 'node-fullstack',
120
122
  configServer: '',
@@ -132,6 +134,7 @@ describe('analyzeCodebase', () => {
132
134
  startTime: expect.any(Number),
133
135
  userInfo: expect.any(Object),
134
136
  }),
137
+ repoUrl: expect.any(String),
135
138
  repoXFIConfig: expect.objectContaining({
136
139
  sensitiveFileFalsePositives: expect.any(Array),
137
140
  })
@@ -177,7 +180,7 @@ describe('analyzeCodebase', () => {
177
180
  XFI_RESULT: expect.objectContaining({
178
181
  archetype: 'node-fullstack',
179
182
  repoPath: 'mockRepoPath',
180
- fileCount: 3,
183
+ fileCount: expect.any(Number),
181
184
  totalIssues: 3,
182
185
  warningCount: 0,
183
186
  fatalityCount: 0,
@@ -202,8 +205,10 @@ describe('analyzeCodebase', () => {
202
205
  durationSeconds: expect.any(Number),
203
206
  finishTime: expect.any(Number),
204
207
  startTime: expect.any(Number),
208
+ memoryUsage: expect.any(Object),
205
209
  options: expect.any(Object),
206
210
  telemetryData: expect.any(Object),
211
+ repoUrl: expect.any(String),
207
212
  repoXFIConfig: expect.objectContaining({
208
213
  sensitiveFileFalsePositives: expect.any(Array)
209
214
  })
@@ -305,7 +310,7 @@ describe('analyzeCodebase', () => {
305
310
  XFI_RESULT: expect.objectContaining({
306
311
  archetype: 'node-fullstack',
307
312
  repoPath: 'mockRepoPath',
308
- fileCount: 3,
313
+ fileCount: expect.any(Number),
309
314
  totalIssues: 3,
310
315
  warningCount: 0,
311
316
  fatalityCount: 3,
@@ -321,8 +326,11 @@ describe('analyzeCodebase', () => {
321
326
  durationSeconds: expect.any(Number),
322
327
  finishTime: expect.any(Number),
323
328
  startTime: expect.any(Number),
329
+ memoryUsage: expect.any(Object),
324
330
  options: expect.any(Object),
325
331
  telemetryData: expect.any(Object),
332
+ repoUrl: expect.any(String),
333
+ repoXFIConfig: expect.any(Object)
326
334
  })
327
335
  });
328
336
  expect(telemetry_1.sendTelemetry).toHaveBeenCalledTimes(2); // Start and end
@@ -20,13 +20,15 @@ function runEngineOnFiles(params) {
20
20
  const msg = `\n==========================\nRUNNING FILE CHECKS..\n==========================`;
21
21
  logger_1.logger.info(msg);
22
22
  const failures = [];
23
- for (const file of fileData) {
23
+ const fileCount = fileData.length;
24
+ for (let i = 0; i < fileCount; i++) {
25
+ const file = fileData[i];
24
26
  if (file.fileName === configManager_1.REPO_GLOBAL_CHECK) {
25
27
  const msg = `\n==========================\nRUNNING GLOBAL REPO CHECKS..\n==========================`;
26
28
  logger_1.logger.info(msg);
27
29
  }
28
30
  else {
29
- const msg = `analysing ${file.filePath} ...`;
31
+ const msg = `analysing (${i + 1} of ${fileCount - 1}) ${file.filePath} ...`;
30
32
  logger_1.logger.info(msg);
31
33
  }
32
34
  const facts = {
@@ -8,7 +8,8 @@
8
8
  "openaiAnalysisTop5-global",
9
9
  "openaiAnalysisA11y-global",
10
10
  "invalidSystemIdConfigured-iterative",
11
- "missingRequiredFiles-global"
11
+ "missingRequiredFiles-global",
12
+ "regexMatch-example-iterative"
12
13
  ],
13
14
  "operators": [
14
15
  "fileContains",
@@ -16,7 +17,8 @@
16
17
  "nonStandardDirectoryStructure",
17
18
  "openaiAnalysisHighSeverity",
18
19
  "invalidRemoteValidation",
19
- "missingRequiredFiles"
20
+ "missingRequiredFiles",
21
+ "regexMatch"
20
22
  ],
21
23
  "facts": [
22
24
  "repoFilesystemFacts",
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "regexMatch-example",
3
+ "conditions": {
4
+ "all": [
5
+ {
6
+ "fact": "fileData",
7
+ "path": "$.filePath",
8
+ "operator": "regexMatch",
9
+ "value": ".*/facts/.*"
10
+ },
11
+ {
12
+ "fact": "fileData",
13
+ "path": "$.fileContent",
14
+ "operator": "regexMatch",
15
+ "value": "OpenAI"
16
+ }
17
+ ]
18
+ },
19
+ "event": {
20
+ "type": "warning",
21
+ "params": {
22
+ "message": "I'm seeing a pattern here..",
23
+ "details": {
24
+ "suggestion": "wake up and smell the coffee"
25
+ }
26
+ }
27
+ }
28
+ }
@@ -26,7 +26,8 @@
26
26
  "(password|passphrase)",
27
27
  "(private[_-]?key|ssh[_-]?key)",
28
28
  "(oauth[_-]?token|jwt[_-]?token)",
29
- "db[_-]?password"
29
+ "db[_-]?password",
30
+ "(declare)"
30
31
  ],
31
32
  "resultFact": "fileResults"
32
33
  },
@@ -76,7 +76,6 @@ function collectLocalDependencies() {
76
76
  else {
77
77
  logger_1.logger.error('No yarn.lock or package-lock.json found');
78
78
  process.exit(1);
79
- throw new Error('Unsupported package manager');
80
79
  }
81
80
  logger_1.logger.trace(`collectLocalDependencies: ${(0, utils_1.safeStringify)(result)}`);
82
81
  return result;
package/dist/index.js CHANGED
@@ -54,7 +54,6 @@ exports.main = main;
54
54
  const logger_1 = require("./utils/logger");
55
55
  const cli_1 = require("./core/cli");
56
56
  if (require.main === module) {
57
- (0, logger_1.initializeLogger)();
58
57
  (0, logger_1.setLogLevel)(process.env.XFI_LOG_LEVEL || 'info');
59
58
  (0, cli_1.initCLI)();
60
59
  }
@@ -71,7 +70,8 @@ const handleError = (error) => __awaiter(void 0, void 0, void 0, function* () {
71
70
  archetype: cli_1.options.archetype,
72
71
  repoPath: cli_1.options.dir,
73
72
  options: cli_1.options,
74
- errorMessage: error.message
73
+ errorMessage: error.message,
74
+ errorStack: error.stack
75
75
  },
76
76
  timestamp: new Date().toISOString()
77
77
  }, executionLogPrefix);
@@ -101,38 +101,41 @@ function main() {
101
101
  localConfigPath: cli_1.options.localConfigPath,
102
102
  executionLogPrefix
103
103
  });
104
+ const resultString = JSON.stringify(resultMetadata);
105
+ const prettyResult = prettyjson_1.default.render(resultMetadata.XFI_RESULT);
104
106
  // if results are found, there were issues found in the codebase
105
107
  if (resultMetadata.XFI_RESULT.totalIssues > 0) {
106
108
  logger_1.logger.warn(`WARNING: lo-fi attributes detected in codebase. ${resultMetadata.XFI_RESULT.warningCount} are warnings, ${resultMetadata.XFI_RESULT.fatalityCount} are fatal.`);
107
- logger_1.logger.warn(JSON.stringify(resultMetadata));
109
+ logger_1.logger.warn(resultString);
108
110
  if (resultMetadata.XFI_RESULT.errorCount > 0) {
109
111
  logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.errorCount} UNEXPECTED ERRORS!`));
110
- logger_1.logger.error(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
112
+ logger_1.logger.error(`\n${prettyResult}\n\n`);
111
113
  logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.errorCount} UNEXPECTED ERRORS!`));
112
114
  process.exit(1);
113
115
  }
114
116
  if (resultMetadata.XFI_RESULT.fatalityCount > 0) {
115
117
  logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
116
- logger_1.logger.error(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
118
+ logger_1.logger.error(`\n${prettyResult}\n\n`);
117
119
  logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
118
120
  process.exit(1);
119
121
  }
120
122
  else {
121
123
  logger_1.logger.warn(outcomeMessage('No fatal errors were found, however please review the following warnings.'));
122
- logger_1.logger.warn(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
124
+ logger_1.logger.warn(`\n${prettyResult}\n\n`);
123
125
  logger_1.logger.warn(outcomeMessage('No fatal errors were found, however please review the above warnings.'));
124
126
  }
125
127
  }
126
128
  else {
127
129
  logger_1.logger.info(outcomeMessage('SUCCESS! hi-fi codebase detected.'));
128
- logger_1.logger.info(JSON.stringify(resultMetadata));
129
- logger_1.logger.info(`\n${prettyjson_1.default.render(resultMetadata)}\n\n`);
130
+ logger_1.logger.info(resultString);
131
+ logger_1.logger.info(`\n${prettyResult}\n\n`);
130
132
  logger_1.logger.info(outcomeMessage('SUCCESS! hi-fi codebase detected.'));
131
133
  }
132
134
  }
133
135
  }
134
136
  }
135
137
  catch (e) {
138
+ logger_1.logger.error(e, `Error during execution ${JSON.stringify(e.message)} \n ${e.stack}`);
136
139
  yield handleError(e).then(() => {
137
140
  // give some time async ops to finish if not handled directly
138
141
  if (process.env.NODE_ENV !== 'test') {
@@ -14,6 +14,7 @@ const outdatedFramework_1 = require("./outdatedFramework");
14
14
  const fileContains_1 = require("./fileContains");
15
15
  const nonStandardDirectoryStructure_1 = require("./nonStandardDirectoryStructure");
16
16
  const openaiAnalysisHighSeverity_1 = require("./openaiAnalysisHighSeverity");
17
+ const regexMatch_1 = require("./regexMatch");
17
18
  const openaiUtils_1 = require("../utils/openaiUtils");
18
19
  const logger_1 = require("../utils/logger");
19
20
  const pluginRegistry_1 = require("../core/pluginRegistry");
@@ -23,7 +24,8 @@ function loadOperators(operatorNames) {
23
24
  const allAvailableOperators = Object.assign({ outdatedFramework: outdatedFramework_1.outdatedFramework,
24
25
  fileContains: fileContains_1.fileContains,
25
26
  nonStandardDirectoryStructure: nonStandardDirectoryStructure_1.nonStandardDirectoryStructure,
26
- openaiAnalysisHighSeverity: openaiAnalysisHighSeverity_1.openaiAnalysisHighSeverity }, Object.fromEntries(pluginRegistry_1.pluginRegistry.getPluginOperators().map(op => [op.name, op])));
27
+ openaiAnalysisHighSeverity: openaiAnalysisHighSeverity_1.openaiAnalysisHighSeverity,
28
+ regexMatch: regexMatch_1.regexMatch }, Object.fromEntries(pluginRegistry_1.pluginRegistry.getPluginOperators().map(op => [op.name, op])));
27
29
  const openAIStatus = (0, openaiUtils_1.getOpenAIStatus)();
28
30
  logger_1.logger.info(`Loading operators: ${operatorNames.join(', ')}`);
29
31
  const loadedOperators = [];
@@ -16,7 +16,7 @@ const nonStandardDirectoryStructure = {
16
16
  if (filePath !== configManager_1.REPO_GLOBAL_CHECK) {
17
17
  return false;
18
18
  }
19
- logger_1.logger.debug(`running global directory structure analysis..`);
19
+ logger_1.logger.info(`running global directory structure analysis..`);
20
20
  const repoPath = cli_1.options.dir || process.cwd();
21
21
  let result = false;
22
22
  // check the directory structure of the repo against the standard structure
@@ -0,0 +1,10 @@
1
+ import { OperatorDefn } from '../types/typeDefs';
2
+ /**
3
+ * Operator that tests if a string matches a regular expression pattern
4
+ *
5
+ * @param factValue - The string to test against the regex pattern
6
+ * @param regexPattern - The regex pattern to test against (as a string)
7
+ * @returns true if the string matches the pattern, false otherwise
8
+ */
9
+ declare const regexMatch: OperatorDefn;
10
+ export { regexMatch };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.regexMatch = void 0;
4
+ const logger_1 = require("../utils/logger");
5
+ /**
6
+ * Operator that tests if a string matches a regular expression pattern
7
+ *
8
+ * @param factValue - The string to test against the regex pattern
9
+ * @param regexPattern - The regex pattern to test against (as a string)
10
+ * @returns true if the string matches the pattern, false otherwise
11
+ */
12
+ const regexMatch = {
13
+ 'name': 'regexMatch',
14
+ 'fn': (factValue, regexPattern) => {
15
+ try {
16
+ logger_1.logger.debug(`regexMatch: testing ${factValue} against pattern ${regexPattern}`);
17
+ if (factValue === undefined || factValue === null) {
18
+ logger_1.logger.debug('regexMatch: factValue is undefined or null');
19
+ return false;
20
+ }
21
+ if (typeof regexPattern !== 'string') {
22
+ logger_1.logger.debug(`regexMatch: regexPattern is not a string: ${typeof regexPattern}`);
23
+ return false;
24
+ }
25
+ // Extract flags if they exist (pattern/flags format)
26
+ let flags = '';
27
+ let pattern = regexPattern;
28
+ // Check if the pattern is in /pattern/flags format
29
+ const regexFormatMatch = /^\/(.+)\/([gimsuyd]*)$/.exec(regexPattern);
30
+ if (regexFormatMatch) {
31
+ pattern = regexFormatMatch[1];
32
+ flags = regexFormatMatch[2];
33
+ logger_1.logger.debug(`regexMatch: extracted pattern "${pattern}" with flags "${flags}"`);
34
+ }
35
+ const regex = new RegExp(pattern, flags);
36
+ const result = regex.test(String(factValue));
37
+ logger_1.logger.debug(`regexMatch: result is ${result}`);
38
+ return result;
39
+ }
40
+ catch (e) {
41
+ logger_1.logger.error(`regexMatch error: ${e}`);
42
+ return false;
43
+ }
44
+ }
45
+ };
46
+ exports.regexMatch = regexMatch;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const regexMatch_1 = require("./regexMatch");
4
+ const logger_1 = require("../utils/logger");
5
+ jest.mock('../utils/logger', () => ({
6
+ logger: {
7
+ debug: jest.fn(),
8
+ error: jest.fn()
9
+ },
10
+ }));
11
+ describe('regexMatch', () => {
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+ it('should return true when the string matches the pattern', () => {
16
+ expect(regexMatch_1.regexMatch.fn('hello world', 'hello')).toBe(true);
17
+ expect(regexMatch_1.regexMatch.fn('hello world', 'world$')).toBe(true);
18
+ expect(regexMatch_1.regexMatch.fn('hello world', '^hello')).toBe(true);
19
+ expect(logger_1.logger.debug).toHaveBeenCalledWith(expect.stringContaining('result is true'));
20
+ });
21
+ it('should return false when the string does not match the pattern', () => {
22
+ expect(regexMatch_1.regexMatch.fn('hello world', 'goodbye')).toBe(false);
23
+ expect(regexMatch_1.regexMatch.fn('hello world', '^world')).toBe(false);
24
+ expect(regexMatch_1.regexMatch.fn('hello world', 'hello$')).toBe(false);
25
+ expect(logger_1.logger.debug).toHaveBeenCalledWith(expect.stringContaining('result is false'));
26
+ });
27
+ it('should handle regex flags correctly', () => {
28
+ expect(regexMatch_1.regexMatch.fn('Hello World', '/hello/i')).toBe(true);
29
+ expect(regexMatch_1.regexMatch.fn('Hello\nWorld', '/hello.*world/is')).toBe(true);
30
+ expect(regexMatch_1.regexMatch.fn('Hello World', '/hello world/i')).toBe(true);
31
+ });
32
+ it('should handle undefined or null factValue', () => {
33
+ expect(regexMatch_1.regexMatch.fn(undefined, 'test')).toBe(false);
34
+ expect(regexMatch_1.regexMatch.fn(null, 'test')).toBe(false);
35
+ expect(logger_1.logger.debug).toHaveBeenCalledWith('regexMatch: factValue is undefined or null');
36
+ });
37
+ it('should handle non-string regexPattern', () => {
38
+ expect(regexMatch_1.regexMatch.fn('test', null)).toBe(false);
39
+ expect(regexMatch_1.regexMatch.fn('test', undefined)).toBe(false);
40
+ expect(regexMatch_1.regexMatch.fn('test', 123)).toBe(false);
41
+ expect(logger_1.logger.debug).toHaveBeenCalledWith(expect.stringContaining('regexPattern is not a string'));
42
+ });
43
+ it('should handle invalid regex patterns gracefully', () => {
44
+ expect(regexMatch_1.regexMatch.fn('test', '[')).toBe(false);
45
+ expect(logger_1.logger.error).toHaveBeenCalledWith(expect.stringContaining('regexMatch error'));
46
+ });
47
+ it('should handle non-string factValues by converting them to strings', () => {
48
+ expect(regexMatch_1.regexMatch.fn(123, '123')).toBe(true);
49
+ expect(regexMatch_1.regexMatch.fn(true, 'true')).toBe(true);
50
+ expect(regexMatch_1.regexMatch.fn({ toString: () => 'custom' }, 'custom')).toBe(true);
51
+ });
52
+ });
@@ -128,6 +128,7 @@ export interface BasicTelemetryMetadata {
128
128
  telemetryData?: TelemetryData;
129
129
  errorMessage?: string;
130
130
  options?: any;
131
+ errorStack?: string;
131
132
  }
132
133
  export interface ExemptionTelemetryMetadata {
133
134
  repoUrl: string;
@@ -168,6 +169,8 @@ export interface ResultMetadata {
168
169
  telemetryData: TelemetryData;
169
170
  options: any;
170
171
  repoXFIConfig: RepoXFIConfig;
172
+ memoryUsage: any;
173
+ repoUrl: string;
171
174
  };
172
175
  }
173
176
  export interface TelemetryData {
@@ -18,7 +18,7 @@ const maskSensitiveData_1 = require("./maskSensitiveData");
18
18
  let loggerInstance;
19
19
  let loglevel = process.env.XFI_LOG_LEVEL ||
20
20
  (process.env.NODE_ENV === 'test' ? 'silent' : 'info');
21
- let logPrefix;
21
+ let logPrefix = generateLogPrefix();
22
22
  // Initialize logger and prefix immediately
23
23
  function initializeLogger() {
24
24
  logPrefix = generateLogPrefix();
@@ -29,15 +29,9 @@ function generateLogPrefix() {
29
29
  }
30
30
  function resetLogPrefix() {
31
31
  logPrefix = generateLogPrefix();
32
- if (loggerInstance) {
33
- loggerInstance = loggerInstance.child({ prefix: logPrefix });
34
- }
35
32
  }
36
33
  function setLogPrefix(prefix) {
37
34
  logPrefix = prefix;
38
- if (loggerInstance) {
39
- loggerInstance = loggerInstance.child({ prefix: logPrefix });
40
- }
41
35
  }
42
36
  function getLogPrefix() {
43
37
  return logPrefix;
@@ -54,21 +48,22 @@ function getLogger(force) {
54
48
  target: 'pino-pretty',
55
49
  options: {
56
50
  loglevel: loglevel,
51
+ sync: false,
57
52
  colorize: true,
58
53
  translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l o',
59
54
  ignore: 'pid,hostname',
60
- messageFormat: '{prefix} - {msg}',
61
55
  singleLine: true,
62
56
  errorProps: '*'
63
57
  }
64
58
  });
65
59
  const loggerOptions = {
66
60
  timestamp: pino_1.default.stdTimeFunctions.isoTime,
61
+ msgPrefix: `${logPrefix} - `,
67
62
  level: 'info',
68
63
  formatters: {
69
64
  level: (label) => ({ level: label }),
70
65
  bindings: (bindings) => bindings,
71
- log: (object) => (Object.assign({ prefix: logPrefix }, object))
66
+ log: (object) => (Object.assign({}, object))
72
67
  },
73
68
  serializers: {
74
69
  err: (err) => pino_1.default.stdSerializers.err,
package/dist/xfidelity CHANGED
@@ -54,7 +54,6 @@ exports.main = main;
54
54
  const logger_1 = require("./utils/logger");
55
55
  const cli_1 = require("./core/cli");
56
56
  if (require.main === module) {
57
- (0, logger_1.initializeLogger)();
58
57
  (0, logger_1.setLogLevel)(process.env.XFI_LOG_LEVEL || 'info');
59
58
  (0, cli_1.initCLI)();
60
59
  }
@@ -71,7 +70,8 @@ const handleError = (error) => __awaiter(void 0, void 0, void 0, function* () {
71
70
  archetype: cli_1.options.archetype,
72
71
  repoPath: cli_1.options.dir,
73
72
  options: cli_1.options,
74
- errorMessage: error.message
73
+ errorMessage: error.message,
74
+ errorStack: error.stack
75
75
  },
76
76
  timestamp: new Date().toISOString()
77
77
  }, executionLogPrefix);
@@ -101,38 +101,41 @@ function main() {
101
101
  localConfigPath: cli_1.options.localConfigPath,
102
102
  executionLogPrefix
103
103
  });
104
+ const resultString = JSON.stringify(resultMetadata);
105
+ const prettyResult = prettyjson_1.default.render(resultMetadata.XFI_RESULT);
104
106
  // if results are found, there were issues found in the codebase
105
107
  if (resultMetadata.XFI_RESULT.totalIssues > 0) {
106
108
  logger_1.logger.warn(`WARNING: lo-fi attributes detected in codebase. ${resultMetadata.XFI_RESULT.warningCount} are warnings, ${resultMetadata.XFI_RESULT.fatalityCount} are fatal.`);
107
- logger_1.logger.warn(JSON.stringify(resultMetadata));
109
+ logger_1.logger.warn(resultString);
108
110
  if (resultMetadata.XFI_RESULT.errorCount > 0) {
109
111
  logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.errorCount} UNEXPECTED ERRORS!`));
110
- logger_1.logger.error(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
112
+ logger_1.logger.error(`\n${prettyResult}\n\n`);
111
113
  logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.errorCount} UNEXPECTED ERRORS!`));
112
114
  process.exit(1);
113
115
  }
114
116
  if (resultMetadata.XFI_RESULT.fatalityCount > 0) {
115
117
  logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
116
- logger_1.logger.error(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
118
+ logger_1.logger.error(`\n${prettyResult}\n\n`);
117
119
  logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
118
120
  process.exit(1);
119
121
  }
120
122
  else {
121
123
  logger_1.logger.warn(outcomeMessage('No fatal errors were found, however please review the following warnings.'));
122
- logger_1.logger.warn(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
124
+ logger_1.logger.warn(`\n${prettyResult}\n\n`);
123
125
  logger_1.logger.warn(outcomeMessage('No fatal errors were found, however please review the above warnings.'));
124
126
  }
125
127
  }
126
128
  else {
127
129
  logger_1.logger.info(outcomeMessage('SUCCESS! hi-fi codebase detected.'));
128
- logger_1.logger.info(JSON.stringify(resultMetadata));
129
- logger_1.logger.info(`\n${prettyjson_1.default.render(resultMetadata)}\n\n`);
130
+ logger_1.logger.info(resultString);
131
+ logger_1.logger.info(`\n${prettyResult}\n\n`);
130
132
  logger_1.logger.info(outcomeMessage('SUCCESS! hi-fi codebase detected.'));
131
133
  }
132
134
  }
133
135
  }
134
136
  }
135
137
  catch (e) {
138
+ logger_1.logger.error(e, `Error during execution ${JSON.stringify(e.message)} \n ${e.stack}`);
136
139
  yield handleError(e).then(() => {
137
140
  // give some time async ops to finish if not handled directly
138
141
  if (process.env.NODE_ENV !== 'test') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x-fidelity",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "cli for opinionated framework adherence checks",
5
5
  "main": "dist/index",
6
6
  "types": "dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "author": "wyvern8 <io@zoto.io>",
41
41
  "license": "MIT",
42
42
  "engines": {
43
- "node": ">=20.0.0",
43
+ "node": ">=18.0.0",
44
44
  "yarn": ">=1.22.0"
45
45
  },
46
46
  "devDependencies": {
package/src/core/cli.ts CHANGED
@@ -11,6 +11,19 @@ export const options = program.opts();
11
11
 
12
12
  export function initCLI() {
13
13
 
14
+ const bannerArt = `\n
15
+ =====================================
16
+ __ __ ________ ______
17
+ | ## | ## | ######## \\######
18
+ \\##\\/ ## ______ | ##__ | ##
19
+ >## ## | \\| ## \\ | ##
20
+ / ####\\ \\######| ###### | ##
21
+ | ## \\##\\ | ## _| ##_
22
+ | ## | ## | ## | ## \\
23
+ \\## \\## \\## \\######
24
+ -------------------------------------
25
+ `;
26
+
14
27
  // Ensure logger is initialized
15
28
  if (!logger || typeof logger.info !== 'function') {
16
29
  console.error({ msg: 'Logger is not properly initialized' });
@@ -37,7 +50,7 @@ export function initCLI() {
37
50
  });
38
51
 
39
52
  program
40
- .option("-d, --dir <directory>", "code directory to analyze. equivalent of directory argument")
53
+ .option("-d, --dir <directory>", "local git repo directory path to analyze. equivalent of directory argument")
41
54
  .option("-a, --archetype <archetype>", "The archetype to use for analysis", "node-fullstack")
42
55
  .option("-c, --configServer <configServer>", "The config server URL for fetching remote archetype configurations and rules. This takes precedence over localConfigPath.")
43
56
  .option("-o, --openaiEnabled <boolean>", "Enable OpenAI analysis", false)
@@ -50,8 +63,11 @@ export function initCLI() {
50
63
  .option("-x, --examine <archetype>", "Examine the archetype configuration and rules")
51
64
  .version(version, "-v, --version", "Output the version number of xfidelity")
52
65
  .helpOption("-h, --help", "Display help for command")
53
- .argument('[directory]', 'code directory to analyze');
54
-
66
+ .summary("CLI for analyzing codebases for architectural fidelity")
67
+ .usage("[directory] [options]")
68
+ .argument('[directory]', 'local git repo directory path to analyze')
69
+ .addHelpText('before', bannerArt)
70
+ .addHelpText('after', '-------------------------------------');
55
71
 
56
72
 
57
73
  program.parse(process.argv);
@@ -88,18 +104,18 @@ export function initCLI() {
88
104
  if (process.env.NODE_ENV !== 'test') program.error(`LocalConfigPath does not exist or is invalid: ${error}`);
89
105
  }
90
106
 
91
- const bannerArt = `\n
92
- =====================================
93
- __ __ ________ ______
94
- | ## | ## | ######## \\######
95
- \\##\\/ ## ______ | ##__ | ##
96
- >## ## | \\| ## \\ | ##
97
- / ####\\ \\######| ###### | ##
98
- | ## \\##\\ | ## _| ##_
99
- | ## | ## | ## | ## \\
100
- \\## \\## \\## \\######
101
- -------------------------------------
102
- `;
107
+ // const bannerArt = `\n
108
+ // =====================================
109
+ // __ __ ________ ______
110
+ // | ## | ## | ######## \\######
111
+ // \\##\\/ ## ______ | ##__ | ##
112
+ // >## ## | \\| ## \\ | ##
113
+ // / ####\\ \\######| ###### | ##
114
+ // | ## \\##\\ | ## _| ##_
115
+ // | ## | ## | ## | ## \\
116
+ // \\## \\## \\## \\######
117
+ // -------------------------------------
118
+ // `;
103
119
 
104
120
  logger.info(bannerArt);
105
121
 
@@ -108,15 +108,17 @@ describe('analyzeCodebase', () => {
108
108
  XFI_RESULT: expect.objectContaining({
109
109
  archetype: 'node-fullstack',
110
110
  repoPath: 'mockRepoPath',
111
- fileCount: 3,
111
+ fileCount: expect.any(Number),
112
112
  totalIssues: expect.any(Number),
113
113
  warningCount: expect.any(Number),
114
114
  fatalityCount: expect.any(Number),
115
+ errorCount: expect.any(Number),
115
116
  exemptCount: expect.any(Number),
116
117
  issueDetails: expect.any(Array),
117
118
  durationSeconds: expect.any(Number),
118
119
  finishTime: expect.any(Number),
119
120
  startTime: expect.any(Number),
121
+ memoryUsage: expect.any(Object),
120
122
  options: expect.objectContaining({
121
123
  archetype: 'node-fullstack',
122
124
  configServer: '',
@@ -134,6 +136,7 @@ describe('analyzeCodebase', () => {
134
136
  startTime: expect.any(Number),
135
137
  userInfo: expect.any(Object),
136
138
  }),
139
+ repoUrl: expect.any(String),
137
140
  repoXFIConfig: expect.objectContaining({
138
141
  sensitiveFileFalsePositives: expect.any(Array),
139
142
  })
@@ -184,7 +187,7 @@ describe('analyzeCodebase', () => {
184
187
  XFI_RESULT: expect.objectContaining({
185
188
  archetype: 'node-fullstack',
186
189
  repoPath: 'mockRepoPath',
187
- fileCount: 3,
190
+ fileCount: expect.any(Number),
188
191
  totalIssues: 3,
189
192
  warningCount: 0,
190
193
  fatalityCount: 0,
@@ -209,8 +212,10 @@ describe('analyzeCodebase', () => {
209
212
  durationSeconds: expect.any(Number),
210
213
  finishTime: expect.any(Number),
211
214
  startTime: expect.any(Number),
215
+ memoryUsage: expect.any(Object),
212
216
  options: expect.any(Object),
213
217
  telemetryData: expect.any(Object),
218
+ repoUrl: expect.any(String),
214
219
  repoXFIConfig: expect.objectContaining({
215
220
  sensitiveFileFalsePositives: expect.any(Array)
216
221
  })
@@ -326,7 +331,7 @@ describe('analyzeCodebase', () => {
326
331
  XFI_RESULT: expect.objectContaining({
327
332
  archetype: 'node-fullstack',
328
333
  repoPath: 'mockRepoPath',
329
- fileCount: 3,
334
+ fileCount: expect.any(Number),
330
335
  totalIssues: 3,
331
336
  warningCount: 0,
332
337
  fatalityCount: 3,
@@ -342,8 +347,11 @@ describe('analyzeCodebase', () => {
342
347
  durationSeconds: expect.any(Number),
343
348
  finishTime: expect.any(Number),
344
349
  startTime: expect.any(Number),
350
+ memoryUsage: expect.any(Object),
345
351
  options: expect.any(Object),
346
352
  telemetryData: expect.any(Object),
353
+ repoUrl: expect.any(String),
354
+ repoXFIConfig: expect.any(Object)
347
355
  })
348
356
  });
349
357
  expect(sendTelemetry).toHaveBeenCalledTimes(2); // Start and end
@@ -95,7 +95,7 @@ export async function analyzeCodebase(params: AnalyzeCodebaseParams): Promise<Re
95
95
  logger.info(finishMsg);
96
96
 
97
97
  const totalFailureCount = countRuleFailures(failures);
98
- logger.info(`${fileData.length} files analyzed. ${totalFailureCount} rule failures.`);
98
+ logger.info(`${fileData.length -1} files analyzed. ${totalFailureCount} rule failures.`);
99
99
 
100
100
  const fatalityCount = countRuleFailures(failures, 'fatality');
101
101
  const warningCount = countRuleFailures(failures, 'warning');
@@ -103,17 +103,21 @@ export async function analyzeCodebase(params: AnalyzeCodebaseParams): Promise<Re
103
103
  const errorCount = countRuleFailures(failures, 'error');
104
104
 
105
105
  const finishTime = new Date().getTime();
106
+ const memoryUsage = process.memoryUsage();
107
+
108
+ logger.info('Assemblying result metadata..');
106
109
 
107
110
  const resultMetadata: ResultMetadata = {
108
111
  XFI_RESULT: {
109
112
  archetype,
110
113
  telemetryData,
114
+ memoryUsage,
111
115
  repoXFIConfig: repoXFIConfig,
112
116
  issueDetails: failures,
113
117
  startTime: telemetryData.startTime,
114
118
  finishTime: finishTime,
115
119
  durationSeconds: (finishTime - telemetryData.startTime) / 1000,
116
- fileCount: fileData.length,
120
+ fileCount: fileData.length -1,
117
121
  totalIssues: totalFailureCount,
118
122
  warningCount: warningCount,
119
123
  fatalityCount: fatalityCount,
@@ -121,6 +125,7 @@ export async function analyzeCodebase(params: AnalyzeCodebaseParams): Promise<Re
121
125
  exemptCount: exemptCount,
122
126
  options,
123
127
  repoPath,
128
+ repoUrl
124
129
  }
125
130
  }
126
131
 
@@ -10,13 +10,15 @@ export async function runEngineOnFiles(params: RunEngineOnFilesParams): Promise<
10
10
  const msg = `\n==========================\nRUNNING FILE CHECKS..\n==========================`;
11
11
  logger.info(msg);
12
12
  const failures: ScanResult[] = [];
13
+ const fileCount = fileData.length;
13
14
 
14
- for (const file of fileData) {
15
+ for (let i=0; i < fileCount; i++) {
16
+ const file = fileData[i]
15
17
  if (file.fileName === REPO_GLOBAL_CHECK) {
16
18
  const msg = `\n==========================\nRUNNING GLOBAL REPO CHECKS..\n==========================`;
17
19
  logger.info(msg);
18
20
  } else {
19
- const msg = `analysing ${file.filePath} ...`
21
+ const msg = `analysing (${i+1} of ${fileCount-1}) ${file.filePath} ...`
20
22
  logger.info(msg);
21
23
  }
22
24
  const facts = {
@@ -8,7 +8,8 @@
8
8
  "openaiAnalysisTop5-global",
9
9
  "openaiAnalysisA11y-global",
10
10
  "invalidSystemIdConfigured-iterative",
11
- "missingRequiredFiles-global"
11
+ "missingRequiredFiles-global",
12
+ "regexMatch-example-iterative"
12
13
  ],
13
14
  "operators": [
14
15
  "fileContains",
@@ -16,7 +17,8 @@
16
17
  "nonStandardDirectoryStructure",
17
18
  "openaiAnalysisHighSeverity",
18
19
  "invalidRemoteValidation",
19
- "missingRequiredFiles"
20
+ "missingRequiredFiles",
21
+ "regexMatch"
20
22
  ],
21
23
  "facts": [
22
24
  "repoFilesystemFacts",
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "regexMatch-example",
3
+ "conditions": {
4
+ "all": [
5
+ {
6
+ "fact": "fileData",
7
+ "path": "$.filePath",
8
+ "operator": "regexMatch",
9
+ "value": ".*/facts/.*"
10
+ },
11
+ {
12
+ "fact": "fileData",
13
+ "path": "$.fileContent",
14
+ "operator": "regexMatch",
15
+ "value": "OpenAI"
16
+ }
17
+ ]
18
+ },
19
+ "event": {
20
+ "type": "warning",
21
+ "params": {
22
+ "message": "I'm seeing a pattern here..",
23
+ "details": {
24
+ "suggestion": "wake up and smell the coffee"
25
+ }
26
+ }
27
+ }
28
+ }
@@ -26,7 +26,8 @@
26
26
  "(password|passphrase)",
27
27
  "(private[_-]?key|ssh[_-]?key)",
28
28
  "(oauth[_-]?token|jwt[_-]?token)",
29
- "db[_-]?password"
29
+ "db[_-]?password",
30
+ "(declare)"
30
31
  ],
31
32
  "resultFact": "fileResults"
32
33
  },
@@ -25,7 +25,6 @@ export async function collectLocalDependencies(): Promise<LocalDependencies[]> {
25
25
  } else {
26
26
  logger.error('No yarn.lock or package-lock.json found');
27
27
  process.exit(1);
28
- throw new Error('Unsupported package manager');
29
28
  }
30
29
  logger.trace(`collectLocalDependencies: ${safeStringify(result)}`);
31
30
  return result;
package/src/index.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { logger, initializeLogger, getLogPrefix, setLogLevel } from './utils/logger';
2
+ import { logger, getLogPrefix, setLogLevel } from './utils/logger';
3
3
  import { options, initCLI } from "./core/cli";
4
4
  if (require.main === module) {
5
- initializeLogger()
6
5
  setLogLevel(process.env.XFI_LOG_LEVEL || 'info');
7
6
  initCLI();
8
7
  }
@@ -23,7 +22,8 @@ const handleError = async (error: Error) => {
23
22
  archetype: options.archetype,
24
23
  repoPath: options.dir,
25
24
  options,
26
- errorMessage: error.message
25
+ errorMessage: error.message,
26
+ errorStack: error.stack
27
27
  },
28
28
  timestamp: new Date().toISOString()
29
29
  }, executionLogPrefix);
@@ -54,43 +54,47 @@ export async function main() {
54
54
  executionLogPrefix
55
55
  });
56
56
 
57
+ const resultString = JSON.stringify(resultMetadata);
58
+ const prettyResult = json.render(resultMetadata.XFI_RESULT);
59
+
57
60
  // if results are found, there were issues found in the codebase
58
61
  if (resultMetadata.XFI_RESULT.totalIssues > 0) {
59
62
  logger.warn(`WARNING: lo-fi attributes detected in codebase. ${resultMetadata.XFI_RESULT.warningCount} are warnings, ${resultMetadata.XFI_RESULT.fatalityCount} are fatal.`);
60
- logger.warn(JSON.stringify(resultMetadata));
63
+ logger.warn(resultString);
61
64
 
62
65
  if (resultMetadata.XFI_RESULT.errorCount > 0) {
63
66
  logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.errorCount} UNEXPECTED ERRORS!`));
64
- logger.error(`\n${json.render(resultMetadata.XFI_RESULT)}\n\n`);
67
+ logger.error(`\n${prettyResult}\n\n`);
65
68
  logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.errorCount} UNEXPECTED ERRORS!`));
66
69
  process.exit(1);
67
70
  }
68
71
 
69
72
  if (resultMetadata.XFI_RESULT.fatalityCount > 0) {
70
73
  logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
71
- logger.error(`\n${json.render(resultMetadata.XFI_RESULT)}\n\n`);
74
+ logger.error(`\n${prettyResult}\n\n`);
72
75
  logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
73
76
  process.exit(1);
74
77
  } else {
75
78
  logger.warn(outcomeMessage('No fatal errors were found, however please review the following warnings.'));
76
- logger.warn(`\n${json.render(resultMetadata.XFI_RESULT)}\n\n`);
79
+ logger.warn(`\n${prettyResult}\n\n`);
77
80
  logger.warn(outcomeMessage('No fatal errors were found, however please review the above warnings.'));
78
81
  }
79
82
  } else {
80
83
  logger.info(outcomeMessage('SUCCESS! hi-fi codebase detected.'));
81
- logger.info(JSON.stringify(resultMetadata));
82
- logger.info(`\n${json.render(resultMetadata)}\n\n`);
84
+ logger.info(resultString);
85
+ logger.info(`\n${prettyResult}\n\n`);
83
86
  logger.info(outcomeMessage('SUCCESS! hi-fi codebase detected.'));
84
87
  }
85
88
  }
86
89
  }
87
90
  } catch (e: any) {
91
+ logger.error(e, `Error during execution ${JSON.stringify(e.message)} \n ${e.stack}`);
88
92
  await handleError(e).then(() => {
89
93
  // give some time async ops to finish if not handled directly
90
94
  if (process.env.NODE_ENV !== 'test') {
91
- setTimeout(() => {
95
+ setTimeout(() => { //todo fix this
92
96
  process.exit(1);
93
- }, 3000);
97
+ }, 3000);
94
98
  }
95
99
  });
96
100
  }
@@ -3,6 +3,7 @@ import { outdatedFramework } from './outdatedFramework';
3
3
  import { fileContains } from './fileContains';
4
4
  import { nonStandardDirectoryStructure } from './nonStandardDirectoryStructure';
5
5
  import { openaiAnalysisHighSeverity } from './openaiAnalysisHighSeverity';
6
+ import { regexMatch } from './regexMatch';
6
7
  import { getOpenAIStatus } from '../utils/openaiUtils';
7
8
  import { logger } from '../utils/logger';
8
9
  import { pluginRegistry } from '../core/pluginRegistry';
@@ -14,6 +15,7 @@ async function loadOperators(operatorNames: string[]): Promise<OperatorDefn[]> {
14
15
  fileContains,
15
16
  nonStandardDirectoryStructure,
16
17
  openaiAnalysisHighSeverity,
18
+ regexMatch,
17
19
  ...Object.fromEntries(
18
20
  pluginRegistry.getPluginOperators().map(op => [op.name, op])
19
21
  )
@@ -13,7 +13,7 @@ const nonStandardDirectoryStructure: OperatorDefn = {
13
13
  return false;
14
14
  }
15
15
 
16
- logger.debug(`running global directory structure analysis..`);
16
+ logger.info(`running global directory structure analysis..`);
17
17
 
18
18
  const repoPath = options.dir || process.cwd();
19
19
  let result = false;
@@ -0,0 +1,59 @@
1
+ import { regexMatch } from './regexMatch';
2
+ import { logger } from '../utils/logger';
3
+
4
+ jest.mock('../utils/logger', () => ({
5
+ logger: {
6
+ debug: jest.fn(),
7
+ error: jest.fn()
8
+ },
9
+ }));
10
+
11
+ describe('regexMatch', () => {
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ it('should return true when the string matches the pattern', () => {
17
+ expect(regexMatch.fn('hello world', 'hello')).toBe(true);
18
+ expect(regexMatch.fn('hello world', 'world$')).toBe(true);
19
+ expect(regexMatch.fn('hello world', '^hello')).toBe(true);
20
+ expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('result is true'));
21
+ });
22
+
23
+ it('should return false when the string does not match the pattern', () => {
24
+ expect(regexMatch.fn('hello world', 'goodbye')).toBe(false);
25
+ expect(regexMatch.fn('hello world', '^world')).toBe(false);
26
+ expect(regexMatch.fn('hello world', 'hello$')).toBe(false);
27
+ expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('result is false'));
28
+ });
29
+
30
+ it('should handle regex flags correctly', () => {
31
+ expect(regexMatch.fn('Hello World', '/hello/i')).toBe(true);
32
+ expect(regexMatch.fn('Hello\nWorld', '/hello.*world/is')).toBe(true);
33
+ expect(regexMatch.fn('Hello World', '/hello world/i')).toBe(true);
34
+ });
35
+
36
+ it('should handle undefined or null factValue', () => {
37
+ expect(regexMatch.fn(undefined, 'test')).toBe(false);
38
+ expect(regexMatch.fn(null, 'test')).toBe(false);
39
+ expect(logger.debug).toHaveBeenCalledWith('regexMatch: factValue is undefined or null');
40
+ });
41
+
42
+ it('should handle non-string regexPattern', () => {
43
+ expect(regexMatch.fn('test', null as any)).toBe(false);
44
+ expect(regexMatch.fn('test', undefined as any)).toBe(false);
45
+ expect(regexMatch.fn('test', 123 as any)).toBe(false);
46
+ expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('regexPattern is not a string'));
47
+ });
48
+
49
+ it('should handle invalid regex patterns gracefully', () => {
50
+ expect(regexMatch.fn('test', '[')).toBe(false);
51
+ expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('regexMatch error'));
52
+ });
53
+
54
+ it('should handle non-string factValues by converting them to strings', () => {
55
+ expect(regexMatch.fn(123, '123')).toBe(true);
56
+ expect(regexMatch.fn(true, 'true')).toBe(true);
57
+ expect(regexMatch.fn({ toString: () => 'custom' }, 'custom')).toBe(true);
58
+ });
59
+ });
@@ -0,0 +1,51 @@
1
+ import { logger } from '../utils/logger';
2
+ import { OperatorDefn } from '../types/typeDefs';
3
+
4
+ /**
5
+ * Operator that tests if a string matches a regular expression pattern
6
+ *
7
+ * @param factValue - The string to test against the regex pattern
8
+ * @param regexPattern - The regex pattern to test against (as a string)
9
+ * @returns true if the string matches the pattern, false otherwise
10
+ */
11
+ const regexMatch: OperatorDefn = {
12
+ 'name': 'regexMatch',
13
+ 'fn': (factValue: any, regexPattern: string) => {
14
+ try {
15
+ logger.debug(`regexMatch: testing ${factValue} against pattern ${regexPattern}`);
16
+
17
+ if (factValue === undefined || factValue === null) {
18
+ logger.debug('regexMatch: factValue is undefined or null');
19
+ return false;
20
+ }
21
+
22
+ if (typeof regexPattern !== 'string') {
23
+ logger.debug(`regexMatch: regexPattern is not a string: ${typeof regexPattern}`);
24
+ return false;
25
+ }
26
+
27
+ // Extract flags if they exist (pattern/flags format)
28
+ let flags = '';
29
+ let pattern = regexPattern;
30
+
31
+ // Check if the pattern is in /pattern/flags format
32
+ const regexFormatMatch = /^\/(.+)\/([gimsuyd]*)$/.exec(regexPattern);
33
+ if (regexFormatMatch) {
34
+ pattern = regexFormatMatch[1];
35
+ flags = regexFormatMatch[2];
36
+ logger.debug(`regexMatch: extracted pattern "${pattern}" with flags "${flags}"`);
37
+ }
38
+
39
+ const regex = new RegExp(pattern, flags);
40
+ const result = regex.test(String(factValue));
41
+
42
+ logger.debug(`regexMatch: result is ${result}`);
43
+ return result;
44
+ } catch (e) {
45
+ logger.error(`regexMatch error: ${e}`);
46
+ return false;
47
+ }
48
+ }
49
+ };
50
+
51
+ export { regexMatch };
@@ -144,7 +144,8 @@ export interface BasicTelemetryMetadata {
144
144
  repoPath: string;
145
145
  telemetryData?: TelemetryData;
146
146
  errorMessage?: string;
147
- options?: any
147
+ options?: any;
148
+ errorStack?: string;
148
149
  }
149
150
 
150
151
  export interface ExemptionTelemetryMetadata {
@@ -191,6 +192,8 @@ export interface ResultMetadata {
191
192
  telemetryData: TelemetryData;
192
193
  options: any;
193
194
  repoXFIConfig: RepoXFIConfig;
195
+ memoryUsage: any;
196
+ repoUrl: string;
194
197
  };
195
198
  }
196
199
 
@@ -6,7 +6,7 @@ import { maskSensitiveData } from './maskSensitiveData';
6
6
  let loggerInstance: pino.Logger | undefined;
7
7
  let loglevel = process.env.XFI_LOG_LEVEL ||
8
8
  (process.env.NODE_ENV === 'test' ? 'silent' : 'info');
9
- let logPrefix: string;
9
+ let logPrefix: string = generateLogPrefix();
10
10
 
11
11
  // Initialize logger and prefix immediately
12
12
  export function initializeLogger() {
@@ -20,16 +20,10 @@ export function generateLogPrefix(): string {
20
20
 
21
21
  export function resetLogPrefix(): void {
22
22
  logPrefix = generateLogPrefix();
23
- if (loggerInstance) {
24
- loggerInstance = loggerInstance.child({ prefix: logPrefix });
25
- }
26
23
  }
27
24
 
28
25
  export function setLogPrefix(prefix: string): void {
29
26
  logPrefix = prefix;
30
- if (loggerInstance) {
31
- loggerInstance = loggerInstance.child({ prefix: logPrefix });
32
- }
33
27
  }
34
28
 
35
29
  export function getLogPrefix(): string {
@@ -49,10 +43,10 @@ function getLogger(force?: boolean): pino.Logger {
49
43
  target: 'pino-pretty',
50
44
  options: {
51
45
  loglevel: loglevel,
46
+ sync: false,
52
47
  colorize: true,
53
48
  translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l o',
54
49
  ignore: 'pid,hostname',
55
- messageFormat: '{prefix} - {msg}',
56
50
  singleLine: true,
57
51
  errorProps: '*'
58
52
  }
@@ -60,12 +54,12 @@ function getLogger(force?: boolean): pino.Logger {
60
54
 
61
55
  const loggerOptions: pino.LoggerOptions = {
62
56
  timestamp: pino.stdTimeFunctions.isoTime,
57
+ msgPrefix: `${logPrefix} - `,
63
58
  level: 'info',
64
59
  formatters: {
65
60
  level: (label) => ({ level: label }),
66
61
  bindings: (bindings) => bindings,
67
62
  log: (object) => ({
68
- prefix: logPrefix,
69
63
  ...object
70
64
  })
71
65
  },