x-fidelity 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [2.7.0](https://github.com/zotoio/x-fidelity/compare/v2.6.0...v2.7.0) (2024-08-22)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Add missing `repoUrl` property to `mockParams` object ([e94114d](https://github.com/zotoio/x-fidelity/commit/e94114dced7c9f6b4f53caa034937d07313abd71))
7
+
8
+
9
+ ### Features
10
+
11
+ * **exemption process:** allow a repo to have a timelimited waiver for a given rule ([42d4b7d](https://github.com/zotoio/x-fidelity/commit/42d4b7de181131bbc3400710fc86876f4f3d8748))
12
+
1
13
  # [2.6.0](https://github.com/zotoio/x-fidelity/compare/v2.5.0...v2.6.0) (2024-08-22)
2
14
 
3
15
 
@@ -26,7 +26,12 @@ function analyzeCodebase(params) {
26
26
  return __awaiter(this, void 0, void 0, function* () {
27
27
  const { repoPath, archetype = 'node-fullstack', configServer = '', localConfigPath = '', executionLogPrefix = '' } = params;
28
28
  logger_1.logger.info(`STARTING..`);
29
+ // Load exemptions
30
+ if (localConfigPath) {
31
+ yield configManager_1.ConfigManager.loadExemptions(localConfigPath);
32
+ }
29
33
  const telemetryData = yield (0, telemetryCollector_1.collectTelemetryData)({ repoPath, configServer });
34
+ const repoUrl = telemetryData.repoUrl;
30
35
  // Send telemetry for analysis start
31
36
  yield (0, telemetry_1.sendTelemetry)({
32
37
  eventType: 'analysisStart',
@@ -72,7 +77,8 @@ function analyzeCodebase(params) {
72
77
  fileData,
73
78
  installedDependencyVersions,
74
79
  minimumDependencyVersions,
75
- standardStructure
80
+ standardStructure,
81
+ repoUrl
76
82
  });
77
83
  const finishMsg = `\n==========================\nCHECKS COMPLETED..\n==========================`;
78
84
  logger_1.logger.info(finishMsg);
@@ -14,7 +14,7 @@ const logger_1 = require("../../utils/logger");
14
14
  const configManager_1 = require("../../utils/configManager");
15
15
  function runEngineOnFiles(params) {
16
16
  return __awaiter(this, void 0, void 0, function* () {
17
- const { engine, fileData, installedDependencyVersions, minimumDependencyVersions, standardStructure } = params;
17
+ const { engine, fileData, installedDependencyVersions, minimumDependencyVersions, standardStructure, repoUrl } = params;
18
18
  const msg = `\n==========================\nRUNNING FILE CHECKS..\n==========================`;
19
19
  logger_1.logger.info(msg);
20
20
  const failures = [];
@@ -41,7 +41,7 @@ function runEngineOnFiles(params) {
41
41
  results.forEach((result) => {
42
42
  var _a, _b;
43
43
  logger_1.logger.debug(JSON.stringify(result));
44
- if (result.result) {
44
+ if (result.result && !configManager_1.ConfigManager.isExempt(repoUrl, result.name)) {
45
45
  fileFailures.push({
46
46
  ruleFailure: result.name,
47
47
  level: (_a = result.event) === null || _a === void 0 ? void 0 : _a.type,
@@ -35,6 +35,7 @@ describe('runEngineOnFiles', () => {
35
35
  installedDependencyVersions: {},
36
36
  minimumDependencyVersions: {},
37
37
  standardStructure: {},
38
+ repoUrl: 'https://github.com/mock/repo',
38
39
  };
39
40
  beforeEach(() => {
40
41
  jest.clearAllMocks();
@@ -63,6 +63,39 @@ class ConfigManager {
63
63
  return ConfigManager.configs[archetype];
64
64
  });
65
65
  }
66
+ static loadExemptions(localConfigPath) {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ const exemptionsPath = path.join(localConfigPath, 'exemptions.json');
69
+ try {
70
+ const exemptionsData = yield fs_1.default.promises.readFile(exemptionsPath, 'utf-8');
71
+ const exemptionsJson = JSON.parse(exemptionsData);
72
+ ConfigManager.exemptions = exemptionsJson.exemptions;
73
+ logger_1.logger.info(`Loaded ${ConfigManager.exemptions.length} exemptions`);
74
+ }
75
+ catch (error) {
76
+ logger_1.logger.warn(`Failed to load exemptions: ${error}`);
77
+ ConfigManager.exemptions = [];
78
+ }
79
+ });
80
+ }
81
+ static getExemptions() {
82
+ return ConfigManager.exemptions;
83
+ }
84
+ static getExemptionExpirationDate(repoUrl, ruleName) {
85
+ const exemption = ConfigManager.exemptions.find(exemption => exemption.repoUrl === repoUrl &&
86
+ exemption.rule === ruleName);
87
+ return exemption ? new Date(exemption.expirationDate) : undefined;
88
+ }
89
+ static isExempt(repoUrl, ruleName) {
90
+ const now = new Date();
91
+ const result = ConfigManager.exemptions.some(exemption => exemption.repoUrl === repoUrl &&
92
+ exemption.rule === ruleName &&
93
+ new Date(exemption.expirationDate) > now);
94
+ if (result) {
95
+ logger_1.logger.error(`Exempting rule ${ruleName} for repo ${repoUrl} until ${ConfigManager.getExemptionExpirationDate(repoUrl, ruleName)}`);
96
+ }
97
+ return result;
98
+ }
66
99
  static initialize(params) {
67
100
  return __awaiter(this, void 0, void 0, function* () {
68
101
  var _a;
@@ -176,3 +209,4 @@ class ConfigManager {
176
209
  }
177
210
  exports.ConfigManager = ConfigManager;
178
211
  ConfigManager.configs = {};
212
+ ConfigManager.exemptions = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x-fidelity",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "cli for opinionated framework adherence checks",
5
5
  "main": "dist/xfidelity",
6
6
  "bin": {
@@ -20,7 +20,13 @@ export async function analyzeCodebase(params: AnalyzeCodebaseParams): Promise<Re
20
20
 
21
21
  logger.info(`STARTING..`);
22
22
 
23
+ // Load exemptions
24
+ if (localConfigPath) {
25
+ await ConfigManager.loadExemptions(localConfigPath);
26
+ }
27
+
23
28
  const telemetryData = await collectTelemetryData({ repoPath, configServer});
29
+ const repoUrl = telemetryData.repoUrl;
24
30
 
25
31
  // Send telemetry for analysis start
26
32
  await sendTelemetry({
@@ -76,7 +82,8 @@ export async function analyzeCodebase(params: AnalyzeCodebaseParams): Promise<Re
76
82
  fileData,
77
83
  installedDependencyVersions,
78
84
  minimumDependencyVersions,
79
- standardStructure
85
+ standardStructure,
86
+ repoUrl
80
87
  });
81
88
 
82
89
  const finishMsg = `\n==========================\nCHECKS COMPLETED..\n==========================`;
@@ -29,6 +29,7 @@ describe('runEngineOnFiles', () => {
29
29
  installedDependencyVersions: {},
30
30
  minimumDependencyVersions: {},
31
31
  standardStructure: {},
32
+ repoUrl: 'https://github.com/mock/repo',
32
33
  };
33
34
 
34
35
  beforeEach(() => {
@@ -1,12 +1,12 @@
1
1
  import { EngineResult, RuleResult } from 'json-rules-engine';
2
2
  import { ScanResult, RuleFailure } from '../../types/typeDefs';
3
3
  import { logger } from '../../utils/logger';
4
- import { REPO_GLOBAL_CHECK } from '../../utils/configManager';
4
+ import { REPO_GLOBAL_CHECK, ConfigManager } from '../../utils/configManager';
5
5
 
6
6
  import { RunEngineOnFilesParams } from '../../types/typeDefs';
7
7
 
8
8
  export async function runEngineOnFiles(params: RunEngineOnFilesParams): Promise<ScanResult[]> {
9
- const { engine, fileData, installedDependencyVersions, minimumDependencyVersions, standardStructure } = params;
9
+ const { engine, fileData, installedDependencyVersions, minimumDependencyVersions, standardStructure, repoUrl } = params;
10
10
  const msg = `\n==========================\nRUNNING FILE CHECKS..\n==========================`;
11
11
  logger.info(msg);
12
12
  const failures: ScanResult[] = [];
@@ -32,7 +32,7 @@ export async function runEngineOnFiles(params: RunEngineOnFilesParams): Promise<
32
32
  const { results }: EngineResult = await engine.run(facts);
33
33
  results.forEach((result: RuleResult) => {
34
34
  logger.debug(JSON.stringify(result));
35
- if (result.result) {
35
+ if (result.result && !ConfigManager.isExempt(repoUrl, result.name)) {
36
36
  fileFailures.push({
37
37
  ruleFailure: result.name,
38
38
  level: result.event?.type,
@@ -53,6 +53,14 @@ export interface RunEngineOnFilesParams {
53
53
  installedDependencyVersions: any;
54
54
  minimumDependencyVersions: any;
55
55
  standardStructure: any;
56
+ repoUrl: string;
57
+ }
58
+
59
+ export interface Exemption {
60
+ repoUrl: string;
61
+ rule: string;
62
+ expirationDate: string;
63
+ reason: string;
56
64
  }
57
65
 
58
66
  export interface AnalyzeCodebaseParams {
@@ -1,6 +1,6 @@
1
1
  import { axiosClient } from "./axiosClient";
2
2
  import { logger, setLogPrefix } from "./logger";
3
- import { ArchetypeConfig, ExecutionConfig, GetConfigParams, InitializeParams, LoadLocalConfigParams, RuleConfig } from "../types/typeDefs";
3
+ import { ArchetypeConfig, ExecutionConfig, GetConfigParams, InitializeParams, LoadLocalConfigParams, RuleConfig, Exemption } from "../types/typeDefs";
4
4
  import { archetypes } from "../archetypes";
5
5
  import { options } from '../core/cli';
6
6
  import fs from 'fs';
@@ -12,6 +12,7 @@ export const REPO_GLOBAL_CHECK = 'REPO_GLOBAL_CHECK';
12
12
 
13
13
  export class ConfigManager {
14
14
  private static configs: { [key: string]: ExecutionConfig } = {};
15
+ private static exemptions: Exemption[] = [];
15
16
 
16
17
  public static getLoadedConfigs(): string[] {
17
18
  return Object.keys(ConfigManager.configs);
@@ -31,6 +32,44 @@ export class ConfigManager {
31
32
  return ConfigManager.configs[archetype];
32
33
  }
33
34
 
35
+ public static async loadExemptions(localConfigPath: string): Promise<void> {
36
+ const exemptionsPath = path.join(localConfigPath, 'exemptions.json');
37
+ try {
38
+ const exemptionsData = await fs.promises.readFile(exemptionsPath, 'utf-8');
39
+ const exemptionsJson = JSON.parse(exemptionsData);
40
+ ConfigManager.exemptions = exemptionsJson.exemptions;
41
+ logger.info(`Loaded ${ConfigManager.exemptions.length} exemptions`);
42
+ } catch (error) {
43
+ logger.warn(`Failed to load exemptions: ${error}`);
44
+ ConfigManager.exemptions = [];
45
+ }
46
+ }
47
+
48
+ public static getExemptions(): Exemption[] {
49
+ return ConfigManager.exemptions;
50
+ }
51
+
52
+ public static getExemptionExpirationDate(repoUrl: string, ruleName: string): Date | undefined {
53
+ const exemption = ConfigManager.exemptions.find(exemption =>
54
+ exemption.repoUrl === repoUrl &&
55
+ exemption.rule === ruleName
56
+ );
57
+ return exemption ? new Date(exemption.expirationDate) : undefined;
58
+ }
59
+
60
+ public static isExempt(repoUrl: string, ruleName: string): boolean {
61
+ const now = new Date();
62
+ const result = ConfigManager.exemptions.some(exemption =>
63
+ exemption.repoUrl === repoUrl &&
64
+ exemption.rule === ruleName &&
65
+ new Date(exemption.expirationDate) > now
66
+ );
67
+ if (result) {
68
+ logger.error(`Exempting rule ${ruleName} for repo ${repoUrl} until ${ConfigManager.getExemptionExpirationDate(repoUrl, ruleName)}`);
69
+ }
70
+ return result;
71
+ }
72
+
34
73
  private static async initialize(params: InitializeParams): Promise<ExecutionConfig> {
35
74
  const { archetype, logPrefix } = params;
36
75
  const configServer = options.configServer;