x-fidelity 2.11.0 → 2.12.1
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 +21 -0
- package/dist/archetypes/node-fullstack-exemptions.json +2 -2
- package/dist/archetypes/node-fullstack.json +3 -0
- package/dist/facts/repoDependencyFacts.js +21 -5
- package/dist/facts/repoFilesystemFacts.js +37 -5
- package/dist/index.js +2 -3
- package/dist/server/configServer.js +4 -2
- package/dist/server/configServer.test.js +5 -4
- package/dist/server/routes/{githubWebhookRoute.js → githubWebhookConfigUpdateRoute.js} +2 -2
- package/dist/server/routes/githubWebhookPullRequestCheckRoute.js +45 -0
- package/dist/xfidelity +2 -3
- package/package.json +2 -2
- package/src/archetypes/node-fullstack-exemptions.json +2 -2
- package/src/archetypes/node-fullstack.json +3 -0
- package/src/facts/repoDependencyFacts.ts +22 -7
- package/src/facts/repoFilesystemFacts.ts +38 -5
- package/src/index.ts +2 -3
- package/src/server/configServer.test.ts +5 -4
- package/src/server/configServer.ts +4 -2
- package/src/server/routes/{githubWebhookRoute.ts → githubWebhookConfigUpdateRoute.ts} +1 -1
- package/src/server/routes/githubWebhookPullRequestCheckRoute.ts +40 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## [2.12.1](https://github.com/zotoio/x-fidelity/compare/v2.12.0...v2.12.1) (2024-08-25)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** reduce noise in dependency checks ([88f3ecb](https://github.com/zotoio/x-fidelity/commit/88f3ecb74a29624ee91594a06900c54a2d9fd7f9))
|
|
7
|
+
|
|
8
|
+
# [2.12.0](https://github.com/zotoio/x-fidelity/compare/v2.11.0...v2.12.0) (2024-08-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **analysis:** ensure long single-line files are catered for and npm namespaces ([509b4db](https://github.com/zotoio/x-fidelity/commit/509b4db1c3bb3f2cb487e043f5d23689bc8ed42d))
|
|
14
|
+
* Handle [@namespace](https://github.com/namespace) packages in dependency analysis ([b5314ac](https://github.com/zotoio/x-fidelity/commit/b5314ac2b67f1bf241cbad3229c0ce3498bd9175))
|
|
15
|
+
* Refactor repoFileAnalysis function to improve performance ([ae725bb](https://github.com/zotoio/x-fidelity/commit/ae725bbab307008424bb7494444e66eaca684aad))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* Implement file content splitting for analysis ([4d5f049](https://github.com/zotoio/x-fidelity/commit/4d5f04938d7fd78425e79ac74143439be1552879))
|
|
21
|
+
|
|
1
22
|
# [2.11.0](https://github.com/zotoio/x-fidelity/compare/v2.10.0...v2.11.0) (2024-08-24)
|
|
2
23
|
|
|
3
24
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
|
-
"repoUrl": "
|
|
3
|
+
"repoUrl": "git@github.com:zotoio/x-fidelity.git",
|
|
4
4
|
"rule": "outdatedFramework-global",
|
|
5
|
-
"expirationDate": "
|
|
5
|
+
"expirationDate": "2023-12-31",
|
|
6
6
|
"reason": "Upgrading dependencies is scheduled for Q4 2024"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
@@ -40,6 +40,7 @@ exports.getDependencyVersionFacts = getDependencyVersionFacts;
|
|
|
40
40
|
exports.findPropertiesInTree = findPropertiesInTree;
|
|
41
41
|
exports.repoDependencyAnalysis = repoDependencyAnalysis;
|
|
42
42
|
exports.semverValid = semverValid;
|
|
43
|
+
exports.normalizePackageName = normalizePackageName;
|
|
43
44
|
const logger_1 = require("../utils/logger");
|
|
44
45
|
const child_process_1 = require("child_process");
|
|
45
46
|
const semver = __importStar(require("semver"));
|
|
@@ -105,6 +106,13 @@ function processYarnDependencies(yarnOutput) {
|
|
|
105
106
|
return newDep;
|
|
106
107
|
};
|
|
107
108
|
const extractNameAndVersion = (nameAndVersion) => {
|
|
109
|
+
const lastAtIndex = nameAndVersion.lastIndexOf('@');
|
|
110
|
+
if (nameAndVersion.startsWith('@') && lastAtIndex > 0) {
|
|
111
|
+
return {
|
|
112
|
+
name: nameAndVersion.substring(0, lastAtIndex),
|
|
113
|
+
version: nameAndVersion.substring(lastAtIndex + 1)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
108
116
|
const parts = nameAndVersion.split('@');
|
|
109
117
|
return { name: parts[0], version: parts[1] };
|
|
110
118
|
};
|
|
@@ -160,8 +168,10 @@ function findPropertiesInTree(depGraph, minVersions) {
|
|
|
160
168
|
logger_1.logger.debug(`depGraph: ${JSON.stringify(depGraph)}`);
|
|
161
169
|
function walk(dep, parentName = '') {
|
|
162
170
|
const fullName = parentName ? `${parentName}/${dep.name}` : dep.name;
|
|
163
|
-
if (Object.keys(minVersions).
|
|
164
|
-
|
|
171
|
+
if (Object.keys(minVersions).some(key => key === dep.name || `@${key}` === dep.name)) {
|
|
172
|
+
const minVersionKey = Object.keys(minVersions).find(key => key === dep.name || `@${key}` === dep.name);
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
174
|
+
results.push({ dep: fullName, ver: dep.version, min: minVersions[minVersionKey] });
|
|
165
175
|
}
|
|
166
176
|
if (dep.dependencies) {
|
|
167
177
|
dep.dependencies.forEach(childDep => {
|
|
@@ -186,7 +196,7 @@ function repoDependencyAnalysis(params, almanac) {
|
|
|
186
196
|
logger_1.logger.debug(`outdatedFramework: checking ${versionData.dep}`);
|
|
187
197
|
// Check if the installed version satisfies the required version, supporting both ranges and specific versions
|
|
188
198
|
const isValid = semverValid(versionData.ver, versionData.min);
|
|
189
|
-
if (!isValid) {
|
|
199
|
+
if (!isValid && semver.valid(versionData.ver)) {
|
|
190
200
|
const dependencyFailure = {
|
|
191
201
|
'dependency': versionData.dep,
|
|
192
202
|
'currentVersion': versionData.ver,
|
|
@@ -201,8 +211,11 @@ function repoDependencyAnalysis(params, almanac) {
|
|
|
201
211
|
return result;
|
|
202
212
|
});
|
|
203
213
|
}
|
|
204
|
-
function semverValid(
|
|
205
|
-
|
|
214
|
+
function semverValid(installed, required) {
|
|
215
|
+
// Remove potential @namespace from installed version
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
217
|
+
const installedVersion = installed.includes('@') ? installed.split('@').pop() : installed;
|
|
218
|
+
if (!required || !installedVersion) {
|
|
206
219
|
return true;
|
|
207
220
|
}
|
|
208
221
|
// If 'installed' is a single version and 'required' is a range
|
|
@@ -227,3 +240,6 @@ function semverValid(required, installed) {
|
|
|
227
240
|
}
|
|
228
241
|
return false;
|
|
229
242
|
}
|
|
243
|
+
function normalizePackageName(name) {
|
|
244
|
+
return name.startsWith('@') ? name : `@${name}`;
|
|
245
|
+
}
|
|
@@ -89,17 +89,49 @@ function repoFileAnalysis(params, almanac) {
|
|
|
89
89
|
const analysis = [];
|
|
90
90
|
const lines = fileContent.split('\n');
|
|
91
91
|
logger_1.logger.debug(`lines: ${lines.length}`);
|
|
92
|
-
|
|
92
|
+
const processedLines = [];
|
|
93
|
+
let splitOccurred = false;
|
|
93
94
|
for (const line of lines) {
|
|
94
|
-
|
|
95
|
+
if (line.length > 200) {
|
|
96
|
+
let startIndex = 0;
|
|
97
|
+
while (startIndex < line.length) {
|
|
98
|
+
let endIndex = startIndex + 200;
|
|
99
|
+
if (endIndex < line.length) {
|
|
100
|
+
// Find the nearest space or end of token
|
|
101
|
+
while (endIndex > startIndex && !(/\s/.test(line[endIndex]) || /\W/.test(line[endIndex]))) {
|
|
102
|
+
endIndex--;
|
|
103
|
+
}
|
|
104
|
+
// If no suitable break point found, use the full 200 characters
|
|
105
|
+
if (endIndex === startIndex) {
|
|
106
|
+
endIndex = startIndex + 200;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
endIndex = line.length;
|
|
111
|
+
}
|
|
112
|
+
processedLines.push(line.substring(startIndex, endIndex));
|
|
113
|
+
startIndex = endIndex;
|
|
114
|
+
}
|
|
115
|
+
splitOccurred = true;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
processedLines.push(line);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (lines.length === 1 || splitOccurred) {
|
|
122
|
+
logger_1.logger.debug(`File content was split for analysis`);
|
|
123
|
+
}
|
|
124
|
+
for (let i = 0; i < processedLines.length; i++) {
|
|
125
|
+
const line = processedLines[i];
|
|
95
126
|
for (const pattern of checkPatterns) {
|
|
96
127
|
const regex = new RegExp(pattern, 'g');
|
|
97
128
|
if (regex.test(line)) {
|
|
98
|
-
logger_1.logger.debug(`match on line ${
|
|
129
|
+
logger_1.logger.debug(`match on line ${i + 1} for pattern ${pattern}`);
|
|
99
130
|
const match = {
|
|
100
131
|
'match': pattern,
|
|
101
|
-
'lineNumber':
|
|
102
|
-
'line': line
|
|
132
|
+
'lineNumber': i + 1,
|
|
133
|
+
'line': line,
|
|
134
|
+
'splitOccurred': splitOccurred
|
|
103
135
|
};
|
|
104
136
|
analysis.push(match);
|
|
105
137
|
}
|
package/dist/index.js
CHANGED
|
@@ -59,19 +59,18 @@ function main() {
|
|
|
59
59
|
if (resultMetadata.XFI_RESULT.totalIssues > 0) {
|
|
60
60
|
logger_1.logger.warn(`WARNING: lo-fi attributes detected in codebase. ${resultMetadata.XFI_RESULT.warningCount} are warnings, ${resultMetadata.XFI_RESULT.fatalityCount} are fatal.`);
|
|
61
61
|
logger_1.logger.warn(JSON.stringify(resultMetadata));
|
|
62
|
-
logger_1.logger.warn(`\n${prettyjson_1.default.render(resultMetadata)}\n\n`);
|
|
63
62
|
if (resultMetadata.XFI_RESULT.fatalityCount > 0) {
|
|
64
63
|
logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
|
|
65
64
|
logger_1.logger.on('finish', function () {
|
|
66
65
|
process.exit(1);
|
|
67
66
|
});
|
|
68
|
-
logger_1.logger.error(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT
|
|
67
|
+
logger_1.logger.error(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
|
|
69
68
|
logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
|
|
70
69
|
logger_1.logger.end();
|
|
71
70
|
}
|
|
72
71
|
else {
|
|
73
72
|
logger_1.logger.warn(outcomeMessage('No fatal errors were found, however please review the following warnings.'));
|
|
74
|
-
logger_1.logger.warn(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT
|
|
73
|
+
logger_1.logger.warn(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
|
|
75
74
|
logger_1.logger.warn(outcomeMessage('No fatal errors were found, however please review the above warnings.'));
|
|
76
75
|
}
|
|
77
76
|
}
|
|
@@ -18,7 +18,8 @@ const archetypeRuleRoute_1 = require("./routes/archetypeRuleRoute");
|
|
|
18
18
|
const telemetryRoute_1 = require("./routes/telemetryRoute");
|
|
19
19
|
const clearCacheRoute_1 = require("./routes/clearCacheRoute");
|
|
20
20
|
const viewCacheRoute_1 = require("./routes/viewCacheRoute");
|
|
21
|
-
const
|
|
21
|
+
const githubWebhookConfigUpdateRoute_1 = require("./routes/githubWebhookConfigUpdateRoute");
|
|
22
|
+
const githubWebhookPullRequestCheckRoute_1 = require("./routes/githubWebhookPullRequestCheckRoute");
|
|
22
23
|
const exemptionsRoute_1 = require("./routes/exemptionsRoute");
|
|
23
24
|
const validateUrlInput_1 = require("./middleware/validateUrlInput");
|
|
24
25
|
const validateTelemetryData_1 = require("./middleware/validateTelemetryData");
|
|
@@ -57,7 +58,8 @@ app.post('/telemetry', checkSharedSecret, validateTelemetryData_1.validateTeleme
|
|
|
57
58
|
app.post('/clearcache', checkSharedSecret, clearCacheRoute_1.clearCacheRoute);
|
|
58
59
|
app.get('/viewcache', checkSharedSecret, viewCacheRoute_1.viewCacheRoute);
|
|
59
60
|
app.get('/archetypes/:archetype/exemptions', checkSharedSecret, exemptionsRoute_1.exemptionsRoute);
|
|
60
|
-
app.post('/github-
|
|
61
|
+
app.post('/github-config-update', validateGithubWebhook_1.validateGithubWebhook, githubWebhookConfigUpdateRoute_1.githubWebhookConfigUpdateRoute);
|
|
62
|
+
app.post('/github-pull-request-check', validateGithubWebhook_1.validateGithubWebhook, githubWebhookPullRequestCheckRoute_1.githubWebhookPullRequestCheckRoute);
|
|
61
63
|
function startServer({ customPort, executionLogPrefix }) {
|
|
62
64
|
const serverPort = customPort ? parseInt(customPort) : port;
|
|
63
65
|
executionLogPrefix && (0, logger_1.setLogPrefix)(executionLogPrefix);
|
|
@@ -6,15 +6,16 @@ const archetypeRuleRoute_1 = require("./routes/archetypeRuleRoute");
|
|
|
6
6
|
const telemetryRoute_1 = require("./routes/telemetryRoute");
|
|
7
7
|
const clearCacheRoute_1 = require("./routes/clearCacheRoute");
|
|
8
8
|
const viewCacheRoute_1 = require("./routes/viewCacheRoute");
|
|
9
|
-
const
|
|
9
|
+
const githubWebhookConfigUpdateRoute_1 = require("./routes/githubWebhookConfigUpdateRoute");
|
|
10
10
|
const checkSharedSecret_1 = require("./middleware/checkSharedSecret");
|
|
11
|
+
const validateGithubWebhook_1 = require("./middleware/validateGithubWebhook");
|
|
11
12
|
jest.mock('./routes/archetypeRoute');
|
|
12
13
|
jest.mock('./routes/archetypeRulesRoute');
|
|
13
14
|
jest.mock('./routes/archetypeRuleRoute');
|
|
14
15
|
jest.mock('./routes/telemetryRoute');
|
|
15
16
|
jest.mock('./routes/clearCacheRoute');
|
|
16
17
|
jest.mock('./routes/viewCacheRoute');
|
|
17
|
-
jest.mock('./routes/
|
|
18
|
+
jest.mock('./routes/githubWebhookConfigUpdateRoute');
|
|
18
19
|
jest.mock('./middleware/checkSharedSecret');
|
|
19
20
|
describe('configServer', () => {
|
|
20
21
|
let app;
|
|
@@ -32,7 +33,7 @@ describe('configServer', () => {
|
|
|
32
33
|
app.post('/telemetry', checkSharedSecret_1.checkSharedSecret, telemetryRoute_1.telemetryRoute);
|
|
33
34
|
app.post('/clearcache', checkSharedSecret_1.checkSharedSecret, clearCacheRoute_1.clearCacheRoute);
|
|
34
35
|
app.get('/viewcache', checkSharedSecret_1.checkSharedSecret, viewCacheRoute_1.viewCacheRoute);
|
|
35
|
-
app.post('/github-
|
|
36
|
+
app.post('/github-config-update', validateGithubWebhook_1.validateGithubWebhook, githubWebhookConfigUpdateRoute_1.githubWebhookConfigUpdateRoute);
|
|
36
37
|
// Verify routes are set up correctly
|
|
37
38
|
expect(app.get).toHaveBeenCalledWith('/archetypes/:archetype', archetypeRoute_1.archetypeRoute);
|
|
38
39
|
expect(app.get).toHaveBeenCalledWith('/archetypes/:archetype/rules', archetypeRulesRoute_1.archetypeRulesRoute);
|
|
@@ -40,7 +41,7 @@ describe('configServer', () => {
|
|
|
40
41
|
expect(app.post).toHaveBeenCalledWith('/telemetry', checkSharedSecret_1.checkSharedSecret, telemetryRoute_1.telemetryRoute);
|
|
41
42
|
expect(app.post).toHaveBeenCalledWith('/clearcache', checkSharedSecret_1.checkSharedSecret, clearCacheRoute_1.clearCacheRoute);
|
|
42
43
|
expect(app.get).toHaveBeenCalledWith('/viewcache', checkSharedSecret_1.checkSharedSecret, viewCacheRoute_1.viewCacheRoute);
|
|
43
|
-
expect(app.post).toHaveBeenCalledWith('/github-
|
|
44
|
+
expect(app.post).toHaveBeenCalledWith('/github-config-update', validateGithubWebhook_1.validateGithubWebhook, githubWebhookConfigUpdateRoute_1.githubWebhookConfigUpdateRoute);
|
|
44
45
|
});
|
|
45
46
|
afterEach(() => {
|
|
46
47
|
jest.resetAllMocks();
|
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.
|
|
15
|
+
exports.githubWebhookConfigUpdateRoute = githubWebhookConfigUpdateRoute;
|
|
16
16
|
const logger_1 = require("../../utils/logger");
|
|
17
17
|
const crypto_1 = __importDefault(require("crypto"));
|
|
18
18
|
const axios_1 = __importDefault(require("axios"));
|
|
@@ -22,7 +22,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
22
22
|
const cacheManager_1 = require("../cacheManager");
|
|
23
23
|
const configManager_1 = require("../../utils/configManager");
|
|
24
24
|
const cli_1 = require("../../core/cli");
|
|
25
|
-
function
|
|
25
|
+
function githubWebhookConfigUpdateRoute(req, res) {
|
|
26
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
27
27
|
const requestLogPrefix = req.headers['x-log-prefix'] || '';
|
|
28
28
|
(0, logger_1.setLogPrefix)(requestLogPrefix);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.githubWebhookPullRequestCheckRoute = githubWebhookPullRequestCheckRoute;
|
|
16
|
+
const logger_1 = require("../../utils/logger");
|
|
17
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
18
|
+
function githubWebhookPullRequestCheckRoute(req, res) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const requestLogPrefix = req.headers['x-log-prefix'] || '';
|
|
21
|
+
(0, logger_1.setLogPrefix)(requestLogPrefix);
|
|
22
|
+
const signature = req.headers['x-hub-signature-256'];
|
|
23
|
+
const githubSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
24
|
+
if (!githubSecret) {
|
|
25
|
+
logger_1.logger.error('GitHub webhook secret is not set');
|
|
26
|
+
return res.status(500).send('Server is not configured for webhooks');
|
|
27
|
+
}
|
|
28
|
+
if (!signature) {
|
|
29
|
+
logger_1.logger.error('No X-Hub-Signature-256 found on request');
|
|
30
|
+
return res.status(400).send('No X-Hub-Signature-256 found on request');
|
|
31
|
+
}
|
|
32
|
+
const hmac = crypto_1.default.createHmac('sha256', githubSecret);
|
|
33
|
+
const digest = 'sha256=' + hmac.update(JSON.stringify(req.body)).digest('hex');
|
|
34
|
+
if (signature !== digest) {
|
|
35
|
+
logger_1.logger.error('Request body digest did not match X-Hub-Signature-256');
|
|
36
|
+
return res.status(400).send('Invalid signature');
|
|
37
|
+
}
|
|
38
|
+
const event = req.headers['x-github-event'];
|
|
39
|
+
if (event === 'push') {
|
|
40
|
+
// TODO: Implement pull request check
|
|
41
|
+
return res.status(200).send('Webhook received and processed');
|
|
42
|
+
}
|
|
43
|
+
res.status(200).send('Received');
|
|
44
|
+
});
|
|
45
|
+
}
|
package/dist/xfidelity
CHANGED
|
@@ -59,19 +59,18 @@ function main() {
|
|
|
59
59
|
if (resultMetadata.XFI_RESULT.totalIssues > 0) {
|
|
60
60
|
logger_1.logger.warn(`WARNING: lo-fi attributes detected in codebase. ${resultMetadata.XFI_RESULT.warningCount} are warnings, ${resultMetadata.XFI_RESULT.fatalityCount} are fatal.`);
|
|
61
61
|
logger_1.logger.warn(JSON.stringify(resultMetadata));
|
|
62
|
-
logger_1.logger.warn(`\n${prettyjson_1.default.render(resultMetadata)}\n\n`);
|
|
63
62
|
if (resultMetadata.XFI_RESULT.fatalityCount > 0) {
|
|
64
63
|
logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
|
|
65
64
|
logger_1.logger.on('finish', function () {
|
|
66
65
|
process.exit(1);
|
|
67
66
|
});
|
|
68
|
-
logger_1.logger.error(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT
|
|
67
|
+
logger_1.logger.error(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
|
|
69
68
|
logger_1.logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
|
|
70
69
|
logger_1.logger.end();
|
|
71
70
|
}
|
|
72
71
|
else {
|
|
73
72
|
logger_1.logger.warn(outcomeMessage('No fatal errors were found, however please review the following warnings.'));
|
|
74
|
-
logger_1.logger.warn(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT
|
|
73
|
+
logger_1.logger.warn(`\n${prettyjson_1.default.render(resultMetadata.XFI_RESULT)}\n\n`);
|
|
75
74
|
logger_1.logger.warn(outcomeMessage('No fatal errors were found, however please review the above warnings.'));
|
|
76
75
|
}
|
|
77
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x-fidelity",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.1",
|
|
4
4
|
"description": "cli for opinionated framework adherence checks",
|
|
5
5
|
"main": "dist/xfidelity",
|
|
6
6
|
"bin": {
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"dotenv": "^16.4.5",
|
|
74
74
|
"esprima": "^4.0.1",
|
|
75
75
|
"express": "^4.18.2",
|
|
76
|
-
"express-rate-limit": "^
|
|
76
|
+
"express-rate-limit": "^7.4.0",
|
|
77
77
|
"helmet": "^7.1.0",
|
|
78
78
|
"json-rules-engine": "^6.5.0",
|
|
79
79
|
"lodash": "^4.17.21",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
|
-
"repoUrl": "
|
|
3
|
+
"repoUrl": "git@github.com:zotoio/x-fidelity.git",
|
|
4
4
|
"rule": "outdatedFramework-global",
|
|
5
|
-
"expirationDate": "
|
|
5
|
+
"expirationDate": "2023-12-31",
|
|
6
6
|
"reason": "Upgrading dependencies is scheduled for Q4 2024"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
@@ -64,9 +64,16 @@ function processYarnDependencies(yarnOutput: any): LocalDependencies[] {
|
|
|
64
64
|
return newDep;
|
|
65
65
|
};
|
|
66
66
|
const extractNameAndVersion = (nameAndVersion: string) => {
|
|
67
|
+
const lastAtIndex = nameAndVersion.lastIndexOf('@');
|
|
68
|
+
if (nameAndVersion.startsWith('@') && lastAtIndex > 0) {
|
|
69
|
+
return {
|
|
70
|
+
name: nameAndVersion.substring(0, lastAtIndex),
|
|
71
|
+
version: nameAndVersion.substring(lastAtIndex + 1)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
67
74
|
const parts = nameAndVersion.split('@');
|
|
68
75
|
return { name: parts[0], version: parts[1] };
|
|
69
|
-
}
|
|
76
|
+
};
|
|
70
77
|
yarnOutput.data.trees.forEach((tree: any) => {
|
|
71
78
|
dependencies.push(processDependency(tree));
|
|
72
79
|
});
|
|
@@ -125,8 +132,10 @@ export function findPropertiesInTree(depGraph: LocalDependencies[], minVersions:
|
|
|
125
132
|
|
|
126
133
|
function walk(dep: LocalDependencies, parentName = '') {
|
|
127
134
|
const fullName = parentName ? `${parentName}/${dep.name}` : dep.name;
|
|
128
|
-
if (Object.keys(minVersions).
|
|
129
|
-
|
|
135
|
+
if (Object.keys(minVersions).some(key => key === dep.name || `@${key}` === dep.name)) {
|
|
136
|
+
const minVersionKey = Object.keys(minVersions).find(key => key === dep.name || `@${key}` === dep.name);
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
138
|
+
results.push({ dep: fullName, ver: dep.version, min: minVersions[minVersionKey!] });
|
|
130
139
|
}
|
|
131
140
|
if (dep.dependencies) {
|
|
132
141
|
dep.dependencies.forEach(childDep => {
|
|
@@ -157,7 +166,7 @@ export async function repoDependencyAnalysis(params: any, almanac: Almanac) {
|
|
|
157
166
|
|
|
158
167
|
// Check if the installed version satisfies the required version, supporting both ranges and specific versions
|
|
159
168
|
const isValid = semverValid(versionData.ver, versionData.min);
|
|
160
|
-
if (!isValid) {
|
|
169
|
+
if (!isValid && semver.valid(versionData.ver)) {
|
|
161
170
|
const dependencyFailure = {
|
|
162
171
|
'dependency': versionData.dep,
|
|
163
172
|
'currentVersion': versionData.ver,
|
|
@@ -176,9 +185,12 @@ export async function repoDependencyAnalysis(params: any, almanac: Almanac) {
|
|
|
176
185
|
return result;
|
|
177
186
|
}
|
|
178
187
|
|
|
179
|
-
export function semverValid(
|
|
180
|
-
|
|
181
|
-
|
|
188
|
+
export function semverValid(installed: string, required: string): boolean {
|
|
189
|
+
// Remove potential @namespace from installed version
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
191
|
+
const installedVersion = installed.includes('@') ? installed.split('@').pop()! : installed;
|
|
192
|
+
|
|
193
|
+
if (!required || !installedVersion) {
|
|
182
194
|
return true;
|
|
183
195
|
}
|
|
184
196
|
|
|
@@ -208,3 +220,6 @@ export function semverValid(required: string, installed: string): boolean {
|
|
|
208
220
|
|
|
209
221
|
return false;
|
|
210
222
|
}
|
|
223
|
+
export function normalizePackageName(name: string): string {
|
|
224
|
+
return name.startsWith('@') ? name : `@${name}`;
|
|
225
|
+
}
|
|
@@ -83,17 +83,50 @@ async function repoFileAnalysis(params: any, almanac: any) {
|
|
|
83
83
|
const analysis: any = [];
|
|
84
84
|
const lines = fileContent.split('\n');
|
|
85
85
|
logger.debug(`lines: ${lines.length}`);
|
|
86
|
-
|
|
86
|
+
const processedLines: string[] = [];
|
|
87
|
+
let splitOccurred = false;
|
|
88
|
+
|
|
87
89
|
for (const line of lines) {
|
|
88
|
-
|
|
90
|
+
if (line.length > 200) {
|
|
91
|
+
let startIndex = 0;
|
|
92
|
+
while (startIndex < line.length) {
|
|
93
|
+
let endIndex = startIndex + 200;
|
|
94
|
+
if (endIndex < line.length) {
|
|
95
|
+
// Find the nearest space or end of token
|
|
96
|
+
while (endIndex > startIndex && !(/\s/.test(line[endIndex]) || /\W/.test(line[endIndex]))) {
|
|
97
|
+
endIndex--;
|
|
98
|
+
}
|
|
99
|
+
// If no suitable break point found, use the full 200 characters
|
|
100
|
+
if (endIndex === startIndex) {
|
|
101
|
+
endIndex = startIndex + 200;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
endIndex = line.length;
|
|
105
|
+
}
|
|
106
|
+
processedLines.push(line.substring(startIndex, endIndex));
|
|
107
|
+
startIndex = endIndex;
|
|
108
|
+
}
|
|
109
|
+
splitOccurred = true;
|
|
110
|
+
} else {
|
|
111
|
+
processedLines.push(line);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (lines.length === 1 || splitOccurred) {
|
|
116
|
+
logger.debug(`File content was split for analysis`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < processedLines.length; i++) {
|
|
120
|
+
const line = processedLines[i];
|
|
89
121
|
for (const pattern of checkPatterns) {
|
|
90
122
|
const regex = new RegExp(pattern, 'g');
|
|
91
123
|
if (regex.test(line)) {
|
|
92
|
-
logger.debug(`match on line ${
|
|
124
|
+
logger.debug(`match on line ${i + 1} for pattern ${pattern}`);
|
|
93
125
|
const match = {
|
|
94
126
|
'match': pattern,
|
|
95
|
-
'lineNumber':
|
|
96
|
-
'line': line
|
|
127
|
+
'lineNumber': i + 1,
|
|
128
|
+
'line': line,
|
|
129
|
+
'splitOccurred': splitOccurred
|
|
97
130
|
};
|
|
98
131
|
analysis.push(match);
|
|
99
132
|
}
|
package/src/index.ts
CHANGED
|
@@ -49,19 +49,18 @@ export async function main() {
|
|
|
49
49
|
if (resultMetadata.XFI_RESULT.totalIssues > 0) {
|
|
50
50
|
logger.warn(`WARNING: lo-fi attributes detected in codebase. ${resultMetadata.XFI_RESULT.warningCount} are warnings, ${resultMetadata.XFI_RESULT.fatalityCount} are fatal.`);
|
|
51
51
|
logger.warn(JSON.stringify(resultMetadata));
|
|
52
|
-
logger.warn(`\n${json.render(resultMetadata)}\n\n`);
|
|
53
52
|
|
|
54
53
|
if (resultMetadata.XFI_RESULT.fatalityCount > 0) {
|
|
55
54
|
logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
|
|
56
55
|
logger.on('finish', function () {
|
|
57
56
|
process.exit(1);
|
|
58
57
|
});
|
|
59
|
-
logger.error(`\n${json.render(resultMetadata.XFI_RESULT
|
|
58
|
+
logger.error(`\n${json.render(resultMetadata.XFI_RESULT)}\n\n`);
|
|
60
59
|
logger.error(outcomeMessage(`THERE WERE ${resultMetadata.XFI_RESULT.fatalityCount} FATAL ERRORS DETECTED TO BE IMMEDIATELY ADDRESSED!`));
|
|
61
60
|
logger.end();
|
|
62
61
|
} else {
|
|
63
62
|
logger.warn(outcomeMessage('No fatal errors were found, however please review the following warnings.'));
|
|
64
|
-
logger.warn(`\n${json.render(resultMetadata.XFI_RESULT
|
|
63
|
+
logger.warn(`\n${json.render(resultMetadata.XFI_RESULT)}\n\n`);
|
|
65
64
|
logger.warn(outcomeMessage('No fatal errors were found, however please review the above warnings.'));
|
|
66
65
|
|
|
67
66
|
}
|
|
@@ -5,8 +5,9 @@ import { archetypeRuleRoute } from './routes/archetypeRuleRoute';
|
|
|
5
5
|
import { telemetryRoute } from './routes/telemetryRoute';
|
|
6
6
|
import { clearCacheRoute } from './routes/clearCacheRoute';
|
|
7
7
|
import { viewCacheRoute } from './routes/viewCacheRoute';
|
|
8
|
-
import {
|
|
8
|
+
import { githubWebhookConfigUpdateRoute } from './routes/githubWebhookConfigUpdateRoute';
|
|
9
9
|
import { checkSharedSecret } from './middleware/checkSharedSecret';
|
|
10
|
+
import { validateGithubWebhook } from './middleware/validateGithubWebhook';
|
|
10
11
|
|
|
11
12
|
jest.mock('./routes/archetypeRoute');
|
|
12
13
|
jest.mock('./routes/archetypeRulesRoute');
|
|
@@ -14,7 +15,7 @@ jest.mock('./routes/archetypeRuleRoute');
|
|
|
14
15
|
jest.mock('./routes/telemetryRoute');
|
|
15
16
|
jest.mock('./routes/clearCacheRoute');
|
|
16
17
|
jest.mock('./routes/viewCacheRoute');
|
|
17
|
-
jest.mock('./routes/
|
|
18
|
+
jest.mock('./routes/githubWebhookConfigUpdateRoute');
|
|
18
19
|
jest.mock('./middleware/checkSharedSecret');
|
|
19
20
|
|
|
20
21
|
describe('configServer', () => {
|
|
@@ -35,7 +36,7 @@ describe('configServer', () => {
|
|
|
35
36
|
app.post('/telemetry', checkSharedSecret, telemetryRoute);
|
|
36
37
|
app.post('/clearcache', checkSharedSecret, clearCacheRoute);
|
|
37
38
|
app.get('/viewcache', checkSharedSecret, viewCacheRoute);
|
|
38
|
-
app.post('/github-
|
|
39
|
+
app.post('/github-config-update', validateGithubWebhook, githubWebhookConfigUpdateRoute);
|
|
39
40
|
|
|
40
41
|
// Verify routes are set up correctly
|
|
41
42
|
expect(app.get).toHaveBeenCalledWith('/archetypes/:archetype', archetypeRoute);
|
|
@@ -44,7 +45,7 @@ describe('configServer', () => {
|
|
|
44
45
|
expect(app.post).toHaveBeenCalledWith('/telemetry', checkSharedSecret, telemetryRoute);
|
|
45
46
|
expect(app.post).toHaveBeenCalledWith('/clearcache', checkSharedSecret, clearCacheRoute);
|
|
46
47
|
expect(app.get).toHaveBeenCalledWith('/viewcache', checkSharedSecret, viewCacheRoute);
|
|
47
|
-
expect(app.post).toHaveBeenCalledWith('/github-
|
|
48
|
+
expect(app.post).toHaveBeenCalledWith('/github-config-update', validateGithubWebhook, githubWebhookConfigUpdateRoute);
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
afterEach(() => {
|
|
@@ -13,7 +13,8 @@ import { archetypeRuleRoute } from './routes/archetypeRuleRoute';
|
|
|
13
13
|
import { telemetryRoute } from './routes/telemetryRoute';
|
|
14
14
|
import { clearCacheRoute } from './routes/clearCacheRoute';
|
|
15
15
|
import { viewCacheRoute } from './routes/viewCacheRoute';
|
|
16
|
-
import {
|
|
16
|
+
import { githubWebhookConfigUpdateRoute } from './routes/githubWebhookConfigUpdateRoute';
|
|
17
|
+
import { githubWebhookPullRequestCheckRoute } from './routes/githubWebhookPullRequestCheckRoute';
|
|
17
18
|
import { exemptionsRoute } from './routes/exemptionsRoute';
|
|
18
19
|
import { validateUrlInput } from './middleware/validateUrlInput';
|
|
19
20
|
import { validateTelemetryData } from './middleware/validateTelemetryData';
|
|
@@ -62,7 +63,8 @@ app.post('/clearcache', checkSharedSecret, clearCacheRoute);
|
|
|
62
63
|
app.get('/viewcache', checkSharedSecret, viewCacheRoute);
|
|
63
64
|
app.get('/archetypes/:archetype/exemptions', checkSharedSecret, exemptionsRoute);
|
|
64
65
|
|
|
65
|
-
app.post('/github-
|
|
66
|
+
app.post('/github-config-update', validateGithubWebhook, githubWebhookConfigUpdateRoute);
|
|
67
|
+
app.post('/github-pull-request-check', validateGithubWebhook, githubWebhookPullRequestCheckRoute);
|
|
66
68
|
|
|
67
69
|
export function startServer({ customPort, executionLogPrefix }: StartServerParams): any {
|
|
68
70
|
const serverPort = customPort ? parseInt(customPort) : port;
|
|
@@ -9,7 +9,7 @@ import { clearCache } from '../cacheManager';
|
|
|
9
9
|
import { ConfigManager } from '../../utils/configManager';
|
|
10
10
|
import { options } from '../../core/cli';
|
|
11
11
|
|
|
12
|
-
export async function
|
|
12
|
+
export async function githubWebhookConfigUpdateRoute(req: Request, res: Response) {
|
|
13
13
|
const requestLogPrefix = req.headers['x-log-prefix'] as string || '';
|
|
14
14
|
setLogPrefix(requestLogPrefix);
|
|
15
15
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import { logger, setLogPrefix } from '../../utils/logger';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
|
|
5
|
+
export async function githubWebhookPullRequestCheckRoute(req: Request, res: Response) {
|
|
6
|
+
const requestLogPrefix = req.headers['x-log-prefix'] as string || '';
|
|
7
|
+
setLogPrefix(requestLogPrefix);
|
|
8
|
+
|
|
9
|
+
const signature = req.headers['x-hub-signature-256'] as string;
|
|
10
|
+
const githubSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
11
|
+
|
|
12
|
+
if (!githubSecret) {
|
|
13
|
+
logger.error('GitHub webhook secret is not set');
|
|
14
|
+
return res.status(500).send('Server is not configured for webhooks');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!signature) {
|
|
18
|
+
logger.error('No X-Hub-Signature-256 found on request');
|
|
19
|
+
return res.status(400).send('No X-Hub-Signature-256 found on request');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const hmac = crypto.createHmac('sha256', githubSecret);
|
|
23
|
+
const digest = 'sha256=' + hmac.update(JSON.stringify(req.body)).digest('hex');
|
|
24
|
+
|
|
25
|
+
if (signature !== digest) {
|
|
26
|
+
logger.error('Request body digest did not match X-Hub-Signature-256');
|
|
27
|
+
return res.status(400).send('Invalid signature');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const event = req.headers['x-github-event'] as string;
|
|
31
|
+
if (event === 'push') {
|
|
32
|
+
// TODO: Implement pull request check
|
|
33
|
+
|
|
34
|
+
return res.status(200).send('Webhook received and processed');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
res.status(200).send('Received');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|