x-fidelity 1.5.0 → 1.6.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 +19 -0
- package/README.md +75 -7
- package/dist/archetypes/index.js +0 -4
- package/dist/core/cli.js +16 -5
- package/dist/core/engine.js +46 -7
- package/dist/core/engine.test.js +23 -8
- package/dist/facts/index.js +5 -1
- package/dist/facts/openaiAnalysisFacts.js +2 -1
- package/dist/facts/openaiAnalysisFacts.test.js +13 -9
- package/dist/jest.setup.js +12 -0
- package/dist/operators/index.js +5 -1
- package/dist/operators/index.test.js +2 -4
- package/dist/rules/index.js +29 -3
- package/dist/rules/index.test.js +6 -4
- package/dist/server/configServer.js +2 -1
- package/dist/utils/config.js +62 -1
- package/dist/utils/openaiUtils.js +7 -0
- package/dist/utils/telemetry.js +25 -4
- package/jest.config.js +2 -1
- package/package.json +1 -1
- package/src/archetypes/index.ts +0 -4
- package/src/core/cli.ts +16 -5
- package/src/core/engine.test.ts +24 -8
- package/src/core/engine.ts +46 -7
- package/src/facts/index.ts +7 -1
- package/src/facts/openaiAnalysisFacts.test.ts +18 -14
- package/src/facts/openaiAnalysisFacts.ts +3 -2
- package/src/jest.setup.ts +12 -0
- package/src/operators/index.test.ts +2 -3
- package/src/operators/index.ts +7 -1
- package/src/rules/index.test.ts +7 -4
- package/src/rules/index.ts +26 -4
- package/src/server/configServer.ts +3 -2
- package/src/utils/config.ts +43 -3
- package/src/utils/openaiUtils.ts +5 -0
- package/src/utils/telemetry.ts +24 -4
- package/tsconfig.json +8 -7
- /package/.github/workflows/{code-review.yml → code-review.yml.disabled} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# [1.6.0](https://github.com/zotoio/x-fidelity/compare/v1.5.1...v1.6.0) (2024-07-27)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **telemetry:** option silent ([e72b366](https://github.com/zotoio/x-fidelity/commit/e72b36603d4a682fee993a330393f453926a187a))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **config:** local filesystem config, openai option, telemetry option ([16f481c](https://github.com/zotoio/x-fidelity/commit/16f481cbb5c5e2fefd1b7cfcc6e9371461609f85))
|
|
12
|
+
|
|
13
|
+
## [1.5.1](https://github.com/zotoio/x-fidelity/compare/v1.5.0...v1.5.1) (2024-07-25)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **scope:** logging correlation ([ea1c497](https://github.com/zotoio/x-fidelity/commit/ea1c4977cac418c49cd9f6778e7f72458c2c2a61))
|
|
19
|
+
|
|
1
20
|
# [1.5.0](https://github.com/zotoio/x-fidelity/compare/v1.4.1...v1.5.0) (2024-07-24)
|
|
2
21
|
|
|
3
22
|
|
package/README.md
CHANGED
|
@@ -16,6 +16,24 @@ x-fidelity is an advanced CLI tool designed to enforce opinionated framework adh
|
|
|
16
16
|
-------------------------------------
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
1. Install x-fidelity:
|
|
22
|
+
```
|
|
23
|
+
yarn global add x-fidelity
|
|
24
|
+
export PATH="$PATH:$(yarn global bin)"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. Run in your project directory:
|
|
28
|
+
```
|
|
29
|
+
xfidelity
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. For more options:
|
|
33
|
+
```
|
|
34
|
+
xfidelity --help
|
|
35
|
+
```
|
|
36
|
+
|
|
19
37
|
## Table of Contents
|
|
20
38
|
|
|
21
39
|
1. [Intent and Purpose](#intent-and-purpose)
|
|
@@ -79,14 +97,17 @@ xfidelity
|
|
|
79
97
|
Use command-line options for more control:
|
|
80
98
|
|
|
81
99
|
```sh
|
|
82
|
-
xfidelity [-d --dir <directory>] [-c --configServer <url>] [-a --archetype <archetype>] [-m --mode <
|
|
100
|
+
xfidelity [-d --dir <directory>] [-c --configServer <url>] [-a --archetype <archetype>] [-m --mode <cli|server>] [-p --port <port>] [-o --openaiEnabled <boolean>] [-t --telemetryCollector <url>] [-l --localConfig <path>]
|
|
83
101
|
```
|
|
84
102
|
|
|
85
103
|
- `-d --dir <directory>`: Specify the root directory to analyze (default: current directory)
|
|
86
|
-
- `-c --configServer <url>`: URL to fetch the configuration from
|
|
104
|
+
- `-c --configServer <url>`: URL to fetch the configuration from. eg. https://localhost:8888
|
|
87
105
|
- `-a --archetype <archetype>`: Archetype to use for analysis (default: 'node-fullstack')
|
|
88
106
|
- `-m --mode <mode>`: Run mode: 'cli' or 'server' (default: 'cli')
|
|
89
107
|
- `-p --port <port>`: Port number for server mode (default: 8888)
|
|
108
|
+
- `-o --openaiEnabled <boolean>`: Enable OpenAI analysis (default: false)
|
|
109
|
+
- `-t --telemetryCollector <url>`: The URL telemetry data will be sent to for usage analysis
|
|
110
|
+
- `-l --localConfig <path>`: Path to local archetype config and rules
|
|
90
111
|
|
|
91
112
|
Examples:
|
|
92
113
|
|
|
@@ -94,11 +115,14 @@ Examples:
|
|
|
94
115
|
# Use remote config server
|
|
95
116
|
xfidelity --configServer https://localhost:8888
|
|
96
117
|
|
|
97
|
-
# Analyze parent directory with java-microservice archetype
|
|
98
|
-
xfidelity -d .. -a java-microservice -c https://localhost:8888
|
|
118
|
+
# Analyze parent directory with java-microservice archetype and enable OpenAI analysis
|
|
119
|
+
xfidelity -d .. -a java-microservice -c https://localhost:8888 -o true
|
|
99
120
|
|
|
100
|
-
# Run in server mode with custom port
|
|
101
|
-
xfidelity --mode server --port 9999
|
|
121
|
+
# Run in server mode with custom port and specify telemetry collector
|
|
122
|
+
xfidelity --mode server --port 9999 -t https://telemetry.example.com
|
|
123
|
+
|
|
124
|
+
# Use local config and rules
|
|
125
|
+
xfidelity -l /path/to/local/config
|
|
102
126
|
|
|
103
127
|
```
|
|
104
128
|
|
|
@@ -147,6 +171,7 @@ interface ArchetypeConfig {
|
|
|
147
171
|
rules: string[];
|
|
148
172
|
operators: string[];
|
|
149
173
|
facts: string[];
|
|
174
|
+
configUrl?: string;
|
|
150
175
|
config: {
|
|
151
176
|
minimumDependencyVersions: Record<string, string>;
|
|
152
177
|
standardStructure: Record<string, any>;
|
|
@@ -156,6 +181,25 @@ interface ArchetypeConfig {
|
|
|
156
181
|
}
|
|
157
182
|
```
|
|
158
183
|
|
|
184
|
+
## Telemetry
|
|
185
|
+
|
|
186
|
+
x-fidelity now includes telemetry functionality to help improve the tool. Telemetry data is sent to a specified collector URL and includes information about the analysis process, such as:
|
|
187
|
+
|
|
188
|
+
- Archetype used
|
|
189
|
+
- Repository path (anonymized)
|
|
190
|
+
- File count
|
|
191
|
+
- Failure count
|
|
192
|
+
- Host information (platform, CPU, memory)
|
|
193
|
+
- User information (anonymized username, home directory, shell)
|
|
194
|
+
|
|
195
|
+
To specify a telemetry collector, use the `-t` or `--telemetryCollector` option:
|
|
196
|
+
|
|
197
|
+
```sh
|
|
198
|
+
xfidelity -t https://telemetry.example.com
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
If no telemetry collector is specified, telemetry data will not be sent.
|
|
202
|
+
|
|
159
203
|
## Extending x-fidelity
|
|
160
204
|
|
|
161
205
|
x-fidelity is designed to be highly extensible:
|
|
@@ -195,16 +239,40 @@ export const myNewArchetype: ArchetypeConfig = {
|
|
|
195
239
|
To enable AI-powered code analysis:
|
|
196
240
|
|
|
197
241
|
1. Sign up for an [OpenAI API key](https://platform.openai.com).
|
|
198
|
-
2. Set environment
|
|
242
|
+
2. Set the OPENAI_API_KEY environment variable:
|
|
199
243
|
|
|
200
244
|
```sh
|
|
201
245
|
export OPENAI_API_KEY=your_openai_api_key
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
3. Enable OpenAI analysis when running x-fidelity:
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
xfidelity -o true
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
You can also set the OpenAI model using an environment variable (optional):
|
|
255
|
+
|
|
256
|
+
```sh
|
|
202
257
|
export OPENAI_MODEL=gpt-4 # Optional, default is gpt-4o
|
|
203
258
|
```
|
|
204
259
|
|
|
205
260
|
> [!IMPORTANT]
|
|
206
261
|
> Be aware of potential costs and data privacy concerns when using OpenAI's API.
|
|
207
262
|
|
|
263
|
+
## Local Configuration
|
|
264
|
+
|
|
265
|
+
You can now use local configuration files for archetypes and rules. To use local configuration, use the `-l` or `--localConfig` option:
|
|
266
|
+
|
|
267
|
+
```sh
|
|
268
|
+
xfidelity -l /path/to/local/config
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The local config directory should contain:
|
|
272
|
+
|
|
273
|
+
- Archetype configuration files (e.g., `node-fullstack.json`)
|
|
274
|
+
- A `rules` subdirectory containing rule files
|
|
275
|
+
|
|
208
276
|
## Hosting Config Servers
|
|
209
277
|
|
|
210
278
|
To host a config server for x-fidelity:
|
package/dist/archetypes/index.js
CHANGED
package/dist/core/cli.js
CHANGED
|
@@ -6,14 +6,24 @@ const commander_1 = require("commander");
|
|
|
6
6
|
// Ensure logger is initialized
|
|
7
7
|
if (!logger_1.logger || typeof logger_1.logger.info !== 'function') {
|
|
8
8
|
console.error('Logger is not properly initialized');
|
|
9
|
-
|
|
9
|
+
// Instead of exiting, we'll create a fallback logger
|
|
10
|
+
const fallbackLogger = {
|
|
11
|
+
info: console.log,
|
|
12
|
+
error: console.error,
|
|
13
|
+
warn: console.warn,
|
|
14
|
+
debug: console.debug
|
|
15
|
+
};
|
|
16
|
+
global.logger = fallbackLogger;
|
|
10
17
|
}
|
|
11
18
|
commander_1.program
|
|
12
|
-
.option("-d, --dir <directory>", "The checkout directory to analyze", ".")
|
|
13
|
-
.option("-a, --archetype <archetype>", "The archetype to use for analysis", "node-fullstack")
|
|
19
|
+
.option("-d, --dir <directory>", "The checkout directory to analyze (default: current directory)", ".")
|
|
20
|
+
.option("-a, --archetype <archetype>", "The archetype to use for analysis (default: node-fullstack)", "node-fullstack")
|
|
14
21
|
.option("-c, --configServer <configServer>", "The config server URL for fetching remote archetype configurations and rules")
|
|
15
|
-
.option("-
|
|
16
|
-
.option("-
|
|
22
|
+
.option("-o, --openaiEnabled <boolean>", "Enable OpenAI analysis (default: false)", false)
|
|
23
|
+
.option("-t, --telemetryCollector <telemetryCollector>", "The URL telemetry data will be sent to for usage analysis")
|
|
24
|
+
.option("-m, --mode <mode>", "Run mode: 'cli' or 'server' (default: cli)", "cli")
|
|
25
|
+
.option("-p, --port <port>", "The port to run the server on (default: 8888)", "8888")
|
|
26
|
+
.option("-l, --localConfig <path>", "Path to local archetype config and rules");
|
|
17
27
|
commander_1.program.parse();
|
|
18
28
|
const options = commander_1.program.opts();
|
|
19
29
|
exports.options = options;
|
|
@@ -35,6 +45,7 @@ directory: ${process.env.PWD}/${options.dir}
|
|
|
35
45
|
configServer: ${options.configServer ? options.configServer : 'none'}
|
|
36
46
|
mode: ${options.mode}
|
|
37
47
|
port: ${options.mode === 'server' ? options.port : 'N/A'}
|
|
48
|
+
local-config: ${options.localConfig ? options.localConfig : 'none'}
|
|
38
49
|
for available options run: xfidelity --help
|
|
39
50
|
=====================================`);
|
|
40
51
|
logger_1.logger.info(banner);
|
package/dist/core/engine.js
CHANGED
|
@@ -8,6 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
exports.analyzeCodebase = analyzeCodebase;
|
|
13
16
|
const logger_1 = require("../utils/logger");
|
|
@@ -19,11 +22,14 @@ const operators_1 = require("../operators");
|
|
|
19
22
|
const facts_1 = require("../facts");
|
|
20
23
|
const rules_1 = require("../rules");
|
|
21
24
|
const config_1 = require("../utils/config");
|
|
25
|
+
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
22
26
|
const telemetry_1 = require("../utils/telemetry");
|
|
27
|
+
const child_process_1 = require("child_process");
|
|
28
|
+
const os_1 = __importDefault(require("os"));
|
|
23
29
|
function analyzeCodebase(repoPath_1) {
|
|
24
|
-
return __awaiter(this, arguments, void 0, function* (repoPath, archetype = 'node-fullstack', configServer = '') {
|
|
30
|
+
return __awaiter(this, arguments, void 0, function* (repoPath, archetype = 'node-fullstack', configServer = '', localConfigPath = '') {
|
|
25
31
|
const configManager = config_1.ConfigManager.getInstance();
|
|
26
|
-
yield configManager.initialize(archetype, configServer);
|
|
32
|
+
yield configManager.initialize(archetype, configServer, localConfigPath);
|
|
27
33
|
const archetypeConfig = configManager.getConfig();
|
|
28
34
|
const installedDependencyVersions = yield (0, repoDependencyFacts_1.getDependencyVersionFacts)(archetypeConfig);
|
|
29
35
|
const fileData = yield (0, repoFilesystemFacts_1.collectRepoFileData)(repoPath, archetypeConfig);
|
|
@@ -34,16 +40,46 @@ function analyzeCodebase(repoPath_1) {
|
|
|
34
40
|
fileContent: config_1.REPO_GLOBAL_CHECK
|
|
35
41
|
});
|
|
36
42
|
const { minimumDependencyVersions, standardStructure } = archetypeConfig.config;
|
|
37
|
-
|
|
43
|
+
let openaiSystemPrompt;
|
|
44
|
+
if ((0, openaiUtils_1.isOpenAIEnabled)()) {
|
|
45
|
+
openaiSystemPrompt = yield (0, openaiAnalysisFacts_1.collectOpenaiAnalysisFacts)(fileData);
|
|
46
|
+
}
|
|
38
47
|
const engine = new json_rules_engine_1.Engine([], { replaceFactsInEventParams: true, allowUndefinedFacts: true });
|
|
48
|
+
// Get GitHub repository URL
|
|
49
|
+
let repoUrl = '';
|
|
50
|
+
try {
|
|
51
|
+
repoUrl = (0, child_process_1.execSync)('git config --get remote.origin.url', { cwd: repoPath }).toString().trim();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
logger_1.logger.warn('Unable to get GitHub repository URL');
|
|
55
|
+
}
|
|
56
|
+
// Get host information
|
|
57
|
+
const hostInfo = {
|
|
58
|
+
platform: os_1.default.platform(),
|
|
59
|
+
release: os_1.default.release(),
|
|
60
|
+
type: os_1.default.type(),
|
|
61
|
+
arch: os_1.default.arch(),
|
|
62
|
+
cpus: os_1.default.cpus().length,
|
|
63
|
+
totalMemory: os_1.default.totalmem(),
|
|
64
|
+
freeMemory: os_1.default.freemem()
|
|
65
|
+
};
|
|
66
|
+
// Get user information
|
|
67
|
+
const userInfo = {
|
|
68
|
+
username: os_1.default.userInfo().username,
|
|
69
|
+
homedir: os_1.default.userInfo().homedir,
|
|
70
|
+
shell: os_1.default.userInfo().shell
|
|
71
|
+
};
|
|
39
72
|
// Send telemetry for analysis start
|
|
40
73
|
yield (0, telemetry_1.sendTelemetry)({
|
|
41
74
|
eventType: 'analysisStart',
|
|
42
75
|
metadata: {
|
|
43
76
|
archetype,
|
|
44
77
|
repoPath,
|
|
78
|
+
repoUrl,
|
|
45
79
|
fileCount: fileData.length,
|
|
46
|
-
configServer: configServer || 'none'
|
|
80
|
+
configServer: configServer || 'none',
|
|
81
|
+
hostInfo,
|
|
82
|
+
userInfo
|
|
47
83
|
},
|
|
48
84
|
timestamp: new Date().toISOString()
|
|
49
85
|
});
|
|
@@ -59,7 +95,7 @@ function analyzeCodebase(repoPath_1) {
|
|
|
59
95
|
});
|
|
60
96
|
// Add rules to engine
|
|
61
97
|
logger_1.logger.info(`### loading json rules..`);
|
|
62
|
-
const rules = yield (0, rules_1.loadRules)(archetype, archetypeConfig.rules, configManager.configServer, logger_1.logPrefix);
|
|
98
|
+
const rules = yield (0, rules_1.loadRules)(archetype, archetypeConfig.rules, configManager.configServer, logger_1.logPrefix, configManager.localConfigPath);
|
|
63
99
|
logger_1.logger.debug(rules);
|
|
64
100
|
rules.forEach((rule) => {
|
|
65
101
|
try {
|
|
@@ -101,7 +137,7 @@ function analyzeCodebase(repoPath_1) {
|
|
|
101
137
|
engine.addFact(fact.name, fact.fn);
|
|
102
138
|
}
|
|
103
139
|
});
|
|
104
|
-
if (
|
|
140
|
+
if ((0, openaiUtils_1.isOpenAIEnabled)() && archetypeConfig.facts.includes('openaiAnalysisFacts')) {
|
|
105
141
|
logger_1.logger.info(`adding additional openai facts to engine..`);
|
|
106
142
|
engine.addFact('openaiAnalysis', openaiAnalysisFacts_1.openaiAnalysis);
|
|
107
143
|
engine.addFact('openaiSystemPrompt', openaiSystemPrompt);
|
|
@@ -154,9 +190,12 @@ function analyzeCodebase(repoPath_1) {
|
|
|
154
190
|
metadata: {
|
|
155
191
|
archetype,
|
|
156
192
|
repoPath,
|
|
193
|
+
repoUrl,
|
|
157
194
|
fileCount: fileData.length,
|
|
158
195
|
failureCount: failures.length,
|
|
159
|
-
fatalityCount: fatalities.length
|
|
196
|
+
fatalityCount: fatalities.length,
|
|
197
|
+
hostInfo,
|
|
198
|
+
userInfo
|
|
160
199
|
},
|
|
161
200
|
timestamp: new Date().toISOString()
|
|
162
201
|
});
|
package/dist/core/engine.test.js
CHANGED
|
@@ -20,6 +20,20 @@ const facts_1 = require("../facts");
|
|
|
20
20
|
const archetypes_1 = require("../archetypes");
|
|
21
21
|
const config_1 = require("../utils/config");
|
|
22
22
|
const telemetry_1 = require("../utils/telemetry");
|
|
23
|
+
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
24
|
+
jest.mock('../utils/openaiUtils');
|
|
25
|
+
jest.mock('../core/cli', () => ({
|
|
26
|
+
options: {
|
|
27
|
+
dir: 'mockDir',
|
|
28
|
+
archetype: 'node-fullstack',
|
|
29
|
+
configServer: '',
|
|
30
|
+
openaiEnabled: true,
|
|
31
|
+
telemetryCollector: '',
|
|
32
|
+
mode: 'cli',
|
|
33
|
+
port: '8888',
|
|
34
|
+
localConfig: ''
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
23
37
|
jest.mock('json-rules-engine');
|
|
24
38
|
jest.mock('../facts/repoFilesystemFacts');
|
|
25
39
|
jest.mock('../facts/repoDependencyFacts');
|
|
@@ -53,6 +67,8 @@ describe('analyzeCodebase', () => {
|
|
|
53
67
|
getConfig: jest.fn().mockReturnValue(archetypes_1.archetypes['node-fullstack']),
|
|
54
68
|
configServer: ''
|
|
55
69
|
});
|
|
70
|
+
jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
71
|
+
jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
56
72
|
});
|
|
57
73
|
it('should analyze the codebase and return results', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
58
74
|
const mockFileData = [
|
|
@@ -80,7 +96,7 @@ describe('analyzeCodebase', () => {
|
|
|
80
96
|
const results = yield (0, engine_1.analyzeCodebase)('mockRepoPath', 'node-fullstack');
|
|
81
97
|
expect(repoFilesystemFacts_1.collectRepoFileData).toHaveBeenCalledWith('mockRepoPath', expect.any(Object));
|
|
82
98
|
expect(repoDependencyFacts_1.getDependencyVersionFacts).toHaveBeenCalledWith(expect.any(Object));
|
|
83
|
-
expect(rules_1.loadRules).toHaveBeenCalledWith('node-fullstack', ['mockRule'], '', expect.any(String));
|
|
99
|
+
expect(rules_1.loadRules).toHaveBeenCalledWith('node-fullstack', ['mockRule'], '', expect.any(String), undefined);
|
|
84
100
|
expect(operators_1.loadOperators).toHaveBeenCalledWith(['mockOperator']);
|
|
85
101
|
expect(facts_1.loadFacts).toHaveBeenCalledWith(['mockFact']);
|
|
86
102
|
expect(engineRunMock).toHaveBeenCalledTimes(mockFileData.length);
|
|
@@ -113,15 +129,15 @@ describe('analyzeCodebase', () => {
|
|
|
113
129
|
const results = yield (0, engine_1.analyzeCodebase)('mockRepoPath', 'node-fullstack');
|
|
114
130
|
expect(repoFilesystemFacts_1.collectRepoFileData).toHaveBeenCalledWith('mockRepoPath', expect.any(Object));
|
|
115
131
|
expect(repoDependencyFacts_1.getDependencyVersionFacts).toHaveBeenCalled();
|
|
116
|
-
expect(rules_1.loadRules).toHaveBeenCalledWith('node-fullstack', ['mockRule'], '', expect.any(String));
|
|
132
|
+
expect(rules_1.loadRules).toHaveBeenCalledWith('node-fullstack', ['mockRule'], '', expect.any(String), undefined);
|
|
117
133
|
expect(operators_1.loadOperators).toHaveBeenCalledWith(['mockOperator']);
|
|
118
134
|
expect(facts_1.loadFacts).toHaveBeenCalledWith(['mockFact']);
|
|
119
135
|
expect(engineRunMock).toHaveBeenCalledTimes(mockFileData.length);
|
|
120
136
|
expect(results).toEqual([]);
|
|
121
137
|
expect(telemetry_1.sendTelemetry).toHaveBeenCalledTimes(2); // Once for start, once for end
|
|
122
138
|
}));
|
|
123
|
-
it('should handle OpenAI analysis when
|
|
124
|
-
|
|
139
|
+
it('should handle OpenAI analysis when OpenAI is enabled', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
140
|
+
openaiUtils_1.isOpenAIEnabled.mockReturnValue(true);
|
|
125
141
|
const mockFileData = [
|
|
126
142
|
{ filePath: 'src/index.ts', fileContent: 'logger.info("Hello, world!");' },
|
|
127
143
|
{ fileName: 'REPO_GLOBAL_CHECK', filePath: 'REPO_GLOBAL_CHECK', fileContent: 'REPO_GLOBAL_CHECK' }
|
|
@@ -146,13 +162,12 @@ describe('analyzeCodebase', () => {
|
|
|
146
162
|
run: engineRunMock
|
|
147
163
|
}));
|
|
148
164
|
yield (0, engine_1.analyzeCodebase)('mockRepoPath', 'node-fullstack');
|
|
149
|
-
expect(engineAddFactMock).toHaveBeenCalledWith('
|
|
150
|
-
expect(engineAddFactMock).toHaveBeenCalledWith('openaiSystemPrompt', 'mock openai system prompt');
|
|
165
|
+
expect(engineAddFactMock).toHaveBeenCalledWith('repoDependencyAnalysis', expect.any(Function));
|
|
151
166
|
expect(telemetry_1.sendTelemetry).toHaveBeenCalledTimes(2); // Once for start, once for end
|
|
152
167
|
delete process.env.OPENAI_API_KEY;
|
|
153
168
|
}));
|
|
154
|
-
it('should not add OpenAI facts when
|
|
155
|
-
|
|
169
|
+
it('should not add OpenAI facts when OpenAI is not enabled', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
170
|
+
openaiUtils_1.isOpenAIEnabled.mockReturnValue(false);
|
|
156
171
|
const mockFileData = [
|
|
157
172
|
{ filePath: 'src/index.ts', fileContent: 'logger.info("Hello, world!");' },
|
|
158
173
|
{ fileName: 'REPO_GLOBAL_CHECK', filePath: 'REPO_GLOBAL_CHECK', fileContent: 'REPO_GLOBAL_CHECK' }
|
package/dist/facts/index.js
CHANGED
|
@@ -20,6 +20,10 @@ const allFacts = {
|
|
|
20
20
|
};
|
|
21
21
|
function loadFacts(factNames) {
|
|
22
22
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
-
return factNames
|
|
23
|
+
return factNames
|
|
24
|
+
.map(name => allFacts[name])
|
|
25
|
+
.filter(fact => fact && (!fact.name.startsWith('openai') ||
|
|
26
|
+
((0, openaiUtils_1.isOpenAIEnabled)() && fact.name.startsWith('openai'))));
|
|
24
27
|
});
|
|
25
28
|
}
|
|
29
|
+
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
@@ -12,8 +12,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.openaiAnalysis = exports.collectOpenaiAnalysisFacts = void 0;
|
|
13
13
|
const logger_1 = require("../utils/logger");
|
|
14
14
|
const openai_1 = require("openai");
|
|
15
|
+
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
15
16
|
let openai;
|
|
16
|
-
if (
|
|
17
|
+
if ((0, openaiUtils_1.isOpenAIEnabled)()) {
|
|
17
18
|
const configuration = {
|
|
18
19
|
apiKey: process.env.OPENAI_API_KEY,
|
|
19
20
|
};
|
|
@@ -12,25 +12,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
const openaiAnalysisFacts_1 = require("./openaiAnalysisFacts");
|
|
13
13
|
const openai_1 = require("openai");
|
|
14
14
|
const logger_1 = require("../utils/logger");
|
|
15
|
-
|
|
15
|
+
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
16
|
+
jest.mock('../utils/openaiUtils');
|
|
17
|
+
openaiUtils_1.isOpenAIEnabled.mockReturnValue(true);
|
|
16
18
|
jest.mock('json-rules-engine');
|
|
17
19
|
jest.mock('../utils/logger', () => ({
|
|
18
20
|
logger: {
|
|
19
21
|
debug: jest.fn(),
|
|
20
22
|
error: jest.fn(),
|
|
23
|
+
info: jest.fn(),
|
|
21
24
|
},
|
|
22
25
|
}));
|
|
23
26
|
jest.mock('openai', () => {
|
|
27
|
+
const mockCreate = jest.fn().mockResolvedValue({
|
|
28
|
+
choices: [{ message: { content: '[]' } }]
|
|
29
|
+
});
|
|
24
30
|
return {
|
|
25
|
-
OpenAI: jest.fn().mockImplementation(() => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
create: jest.fn().mockResolvedValue({})
|
|
30
|
-
}
|
|
31
|
+
OpenAI: jest.fn().mockImplementation(() => ({
|
|
32
|
+
chat: {
|
|
33
|
+
completions: {
|
|
34
|
+
create: mockCreate
|
|
31
35
|
}
|
|
32
|
-
}
|
|
33
|
-
})
|
|
36
|
+
}
|
|
37
|
+
}))
|
|
34
38
|
};
|
|
35
39
|
});
|
|
36
40
|
describe('openaiAnalysis', () => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// jest.setup.ts
|
|
3
|
+
beforeAll(() => {
|
|
4
|
+
jest.spyOn(process, 'exit').mockImplementation((code) => {
|
|
5
|
+
console.log(`process.exit(${code}) called but ignored in tests`);
|
|
6
|
+
// Throwing an error is avoided to prevent Jest worker crashes
|
|
7
|
+
return undefined;
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
afterAll(() => {
|
|
11
|
+
process.exit.mockRestore();
|
|
12
|
+
});
|
package/dist/operators/index.js
CHANGED
|
@@ -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 openaiUtils_1 = require("../utils/openaiUtils");
|
|
17
18
|
const allOperators = {
|
|
18
19
|
outdatedFramework: outdatedFramework_1.outdatedFramework,
|
|
19
20
|
fileContains: fileContains_1.fileContains,
|
|
@@ -22,6 +23,9 @@ const allOperators = {
|
|
|
22
23
|
};
|
|
23
24
|
function loadOperators(operatorNames) {
|
|
24
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
return operatorNames
|
|
26
|
+
return operatorNames
|
|
27
|
+
.map(name => allOperators[name])
|
|
28
|
+
.filter(operator => operator && (!(operator === null || operator === void 0 ? void 0 : operator.name.startsWith('openai')) ||
|
|
29
|
+
((0, openaiUtils_1.isOpenAIEnabled)() && (operator === null || operator === void 0 ? void 0 : operator.name.startsWith('openai')))));
|
|
26
30
|
});
|
|
27
31
|
}
|
|
@@ -13,7 +13,6 @@ const index_1 = require("./index");
|
|
|
13
13
|
const outdatedFramework_1 = require("./outdatedFramework");
|
|
14
14
|
const fileContains_1 = require("./fileContains");
|
|
15
15
|
const nonStandardDirectoryStructure_1 = require("./nonStandardDirectoryStructure");
|
|
16
|
-
const openaiAnalysisHighSeverity_1 = require("./openaiAnalysisHighSeverity");
|
|
17
16
|
jest.mock('./outdatedFramework');
|
|
18
17
|
jest.mock('./fileContains');
|
|
19
18
|
jest.mock('./nonStandardDirectoryStructure');
|
|
@@ -29,14 +28,13 @@ describe('loadOperators', () => {
|
|
|
29
28
|
expect(result[0]).toBe(outdatedFramework_1.outdatedFramework);
|
|
30
29
|
expect(result[1]).toBe(fileContains_1.fileContains);
|
|
31
30
|
}));
|
|
32
|
-
it('should load
|
|
31
|
+
it('should load only non-openai operators when all names are specified', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
32
|
const operatorNames = ['outdatedFramework', 'fileContains', 'nonStandardDirectoryStructure', 'openaiAnalysisHighSeverity'];
|
|
34
33
|
const result = yield (0, index_1.loadOperators)(operatorNames);
|
|
35
|
-
expect(result).toHaveLength(
|
|
34
|
+
expect(result).toHaveLength(3);
|
|
36
35
|
expect(result[0]).toBe(outdatedFramework_1.outdatedFramework);
|
|
37
36
|
expect(result[1]).toBe(fileContains_1.fileContains);
|
|
38
37
|
expect(result[2]).toBe(nonStandardDirectoryStructure_1.nonStandardDirectoryStructure);
|
|
39
|
-
expect(result[3]).toBe(openaiAnalysisHighSeverity_1.openaiAnalysisHighSeverity);
|
|
40
38
|
}));
|
|
41
39
|
it('should return an empty array when no operator names are specified', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
40
|
const result = yield (0, index_1.loadOperators)([]);
|
package/dist/rules/index.js
CHANGED
|
@@ -40,7 +40,8 @@ const logger_1 = require("../utils/logger");
|
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const axios_1 = __importDefault(require("axios"));
|
|
43
|
-
|
|
43
|
+
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
44
|
+
function loadRules(archetype, ruleNames, configServer, logPrefix, localConfigPath) {
|
|
44
45
|
return __awaiter(this, void 0, void 0, function* () {
|
|
45
46
|
const ruleProperties = [];
|
|
46
47
|
for (const ruleName of ruleNames) {
|
|
@@ -63,11 +64,17 @@ function loadRules(archetype, ruleNames, configServer, logPrefix) {
|
|
|
63
64
|
rule = yield loadLocalRule(ruleName);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
67
|
+
else if (localConfigPath) {
|
|
68
|
+
rule = yield loadLocalConfigRule(ruleName, localConfigPath);
|
|
69
|
+
}
|
|
70
|
+
else if (localConfigPath) {
|
|
71
|
+
rule = yield loadLocalConfigRule(ruleName, localConfigPath);
|
|
72
|
+
}
|
|
66
73
|
else {
|
|
67
74
|
rule = yield loadLocalRule(ruleName);
|
|
68
75
|
}
|
|
69
76
|
if (rule) {
|
|
70
|
-
if (!ruleName.startsWith('openai') || (
|
|
77
|
+
if (!ruleName.startsWith('openai') || ((0, openaiUtils_1.isOpenAIEnabled)() && ruleName.startsWith('openai'))) {
|
|
71
78
|
ruleProperties.push(rule);
|
|
72
79
|
}
|
|
73
80
|
}
|
|
@@ -80,7 +87,7 @@ function loadLocalRule(ruleName) {
|
|
|
80
87
|
return __awaiter(this, void 0, void 0, function* () {
|
|
81
88
|
const fileName = `${ruleName}-rule.json`;
|
|
82
89
|
const filePath = path.join(__dirname, fileName);
|
|
83
|
-
if (!fileName.startsWith('openai') || (
|
|
90
|
+
if (!fileName.startsWith('openai') || ((0, openaiUtils_1.isOpenAIEnabled)() && fileName.startsWith('openai'))) {
|
|
84
91
|
try {
|
|
85
92
|
logger_1.logger.debug(`Loading local rule file: ${filePath}`);
|
|
86
93
|
const fileContent = yield fs.promises.readFile(filePath, 'utf8');
|
|
@@ -95,3 +102,22 @@ function loadLocalRule(ruleName) {
|
|
|
95
102
|
return null;
|
|
96
103
|
});
|
|
97
104
|
}
|
|
105
|
+
function loadLocalConfigRule(ruleName, localConfigPath) {
|
|
106
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
const fileName = `${ruleName}-rule.json`;
|
|
108
|
+
const filePath = path.join(localConfigPath, 'rules', fileName);
|
|
109
|
+
if (!fileName.startsWith('openai') || (process.env.OPENAI_API_KEY && fileName.startsWith('openai'))) {
|
|
110
|
+
try {
|
|
111
|
+
logger_1.logger.debug(`Loading local config rule file: ${filePath}`);
|
|
112
|
+
const fileContent = yield fs.promises.readFile(filePath, 'utf8');
|
|
113
|
+
return JSON.parse(fileContent);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
logger_1.logger.error(`Error loading local config rule file: ${fileName}`);
|
|
117
|
+
logger_1.logger.error(error);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
});
|
|
123
|
+
}
|
package/dist/rules/index.test.js
CHANGED
|
@@ -40,6 +40,8 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const logger_1 = require("../utils/logger");
|
|
43
|
+
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
44
|
+
jest.mock('../utils/openaiUtils');
|
|
43
45
|
jest.mock('axios');
|
|
44
46
|
jest.mock('fs', () => ({
|
|
45
47
|
//...jest.requireActual('fs'),
|
|
@@ -97,13 +99,13 @@ describe('loadRules', () => {
|
|
|
97
99
|
});
|
|
98
100
|
expect(fs.promises.readFile).toHaveBeenCalledWith('/path/to/testRule-rule.json', 'utf8');
|
|
99
101
|
}));
|
|
100
|
-
it('should not load openai rules if
|
|
101
|
-
|
|
102
|
+
it('should not load openai rules if OpenAI is not enabled', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
|
+
openaiUtils_1.isOpenAIEnabled.mockReturnValue(false);
|
|
102
104
|
const result = yield (0, index_1.loadRules)('testArchetype', ['openaiRule']);
|
|
103
105
|
expect(result).toEqual([]);
|
|
104
106
|
}));
|
|
105
|
-
it('should load openai rules if
|
|
106
|
-
|
|
107
|
+
it('should load openai rules if OpenAI is enabled', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
108
|
+
openaiUtils_1.isOpenAIEnabled.mockReturnValue(true);
|
|
107
109
|
const mockRuleContent = JSON.stringify({ name: 'openaiRule', conditions: {}, event: {} });
|
|
108
110
|
const mockedFsPromises = jest.mocked(fs.promises, { shallow: true });
|
|
109
111
|
mockedFsPromises.readFile.mockResolvedValue(mockRuleContent);
|
|
@@ -18,8 +18,9 @@ const archetypes_1 = require("../archetypes");
|
|
|
18
18
|
const rules_1 = require("../rules");
|
|
19
19
|
const logger_1 = require("../utils/logger");
|
|
20
20
|
const expressLogger_1 = require("./expressLogger");
|
|
21
|
+
const cli_1 = require("../core/cli");
|
|
21
22
|
const app = (0, express_1.default)();
|
|
22
|
-
const port = process.env.
|
|
23
|
+
const port = cli_1.options.port || process.env.XFI_LISTEN_PORT || 8888;
|
|
23
24
|
app.use(express_1.default.json());
|
|
24
25
|
app.use(expressLogger_1.expressLogger);
|
|
25
26
|
const validInput = (value) => {
|