x-fidelity 1.0.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/.czrc +3 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +28 -0
- package/.github/workflows/release.yml +34 -0
- package/.releaserc +11 -0
- package/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/commitlint.config.js +3 -0
- package/dist/core/cli.js +37 -0
- package/dist/core/engine.js +67 -0
- package/dist/core/engine.test.js +79 -0
- package/dist/facts/repoDependencyFacts.js +105 -0
- package/dist/facts/repoDependencyFacts.test.js +149 -0
- package/dist/facts/repoFilesystemFacts.js +91 -0
- package/dist/index.js +35 -0
- package/dist/operators/currentDependencies.js +58 -0
- package/dist/operators/currentDependencies.test.js +43 -0
- package/dist/operators/directoryStructureMatches.js +63 -0
- package/dist/operators/directoryStructureMatches.test.js +61 -0
- package/dist/operators/fileContains.js +17 -0
- package/dist/operators/fileContains.test.js +25 -0
- package/dist/operators/index.js +11 -0
- package/dist/rules/index.js +61 -0
- package/dist/rules/noDatabases-rule.json +21 -0
- package/dist/rules/standardDirectoryStructure-rule.json +25 -0
- package/dist/rules/supportedCoreDependencies-rule.json +29 -0
- package/dist/typeDefs.js +2 -0
- package/dist/utils/logger.js +14 -0
- package/dist/xfidelity +35 -0
- package/jest.config.js +6 -0
- package/package.json +69 -0
- package/src/core/cli.ts +42 -0
- package/src/core/engine.test.ts +82 -0
- package/src/core/engine.ts +68 -0
- package/src/facts/repoDependencyFacts.test.ts +166 -0
- package/src/facts/repoDependencyFacts.ts +88 -0
- package/src/facts/repoFilesystemFacts.ts +80 -0
- package/src/index.ts +25 -0
- package/src/operators/currentDependencies.test.ts +45 -0
- package/src/operators/currentDependencies.ts +40 -0
- package/src/operators/directoryStructureMatches.test.ts +47 -0
- package/src/operators/directoryStructureMatches.ts +43 -0
- package/src/operators/fileContains.test.ts +27 -0
- package/src/operators/fileContains.ts +19 -0
- package/src/operators/index.ts +12 -0
- package/src/rules/index.ts +29 -0
- package/src/rules/noDatabases-rule.json +21 -0
- package/src/rules/standardDirectoryStructure-rule.json +25 -0
- package/src/rules/supportedCoreDependencies-rule.json +29 -0
- package/src/typeDefs.ts +40 -0
- package/src/utils/logger.ts +20 -0
- package/tsconfig.json +9 -0
package/.czrc
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "npm" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
push:
|
|
8
|
+
branches:
|
|
9
|
+
- master
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v2
|
|
18
|
+
|
|
19
|
+
- name: Set up Node.js
|
|
20
|
+
uses: actions/setup-node@v2
|
|
21
|
+
with:
|
|
22
|
+
node-version: '20'
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: yarn install
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: yarn test
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout code
|
|
14
|
+
uses: actions/checkout@v2
|
|
15
|
+
|
|
16
|
+
- name: Set up Node.js
|
|
17
|
+
uses: actions/setup-node@v2
|
|
18
|
+
with:
|
|
19
|
+
node-version: '20'
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: yarn install
|
|
23
|
+
|
|
24
|
+
- name: Run Build
|
|
25
|
+
run: yarn build
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: yarn test
|
|
29
|
+
|
|
30
|
+
- name: Run semantic-release
|
|
31
|
+
run: npx semantic-release
|
|
32
|
+
env:
|
|
33
|
+
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
34
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/.releaserc
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 1.0.0 (2024-06-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **access:** cd ([0016364](https://github.com/zotoio/x-fidelity/commit/0016364e94ca5175f46c76487363970f4378ada0))
|
|
7
|
+
* **ascii:** readme fix ([86a4448](https://github.com/zotoio/x-fidelity/commit/86a444809f502ac60aad7076fb09b3674a32750a))
|
|
8
|
+
* **ci:** branch name ([858a3cd](https://github.com/zotoio/x-fidelity/commit/858a3cd1f0f0290fed88ce70806e96eff65ecd69))
|
|
9
|
+
* **ci:** node version ([8da6d00](https://github.com/zotoio/x-fidelity/commit/8da6d004eb73109daaad9c867a4aaec35a30a17f))
|
|
10
|
+
* **config:** release ([ccf103c](https://github.com/zotoio/x-fidelity/commit/ccf103cfafdedd1c4bbf17ed2829bcc5392c7ab8))
|
|
11
|
+
* **deps:** missing ([367c43e](https://github.com/zotoio/x-fidelity/commit/367c43e356b50560edfcace2b212c685d4a59033))
|
|
12
|
+
* **pipeline:** test ([ee4a098](https://github.com/zotoio/x-fidelity/commit/ee4a0988ddf7f235fde6a454f86905b6c15ea6cf))
|
|
13
|
+
* **release bin:** local install and cd ([750ab7a](https://github.com/zotoio/x-fidelity/commit/750ab7a7a71163709fde331ca17c8b17895223f2))
|
|
14
|
+
* **test:** merge ([603ebc5](https://github.com/zotoio/x-fidelity/commit/603ebc58c930da0b2d8bdb465a457a02cfdbb28a))
|
|
15
|
+
* **trigger:** cd ([c039356](https://github.com/zotoio/x-fidelity/commit/c039356ecd2d64724411b9333adb7078ab17a2b8))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **pipelines:** setup gh actions ([ceca83a](https://github.com/zotoio/x-fidelity/commit/ceca83a2d0ea604dfb93a71d5a85fef4ffd8c6e7))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 zoto.io
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# x-fidelity
|
|
2
|
+
|
|
3
|
+
cli for opinionated framework adherence checks
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
=====================================
|
|
7
|
+
__ __ ________ ______
|
|
8
|
+
| ## | ## | ######## \######
|
|
9
|
+
\##\/ ## ______ | ##__ | ##
|
|
10
|
+
>## ## | \| ## \ | ##
|
|
11
|
+
/ ####\ \######| ###### | ##
|
|
12
|
+
| ## \##\ | ## _| ##_
|
|
13
|
+
| ## | ## | ## | ## \
|
|
14
|
+
\## \## \## \######
|
|
15
|
+
|
|
16
|
+
-------------------------------------
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## What is x-fidelity?
|
|
20
|
+
|
|
21
|
+
x-fidelity is a CLI tool designed to enforce adherence to a set of opinionated framework rules within a codebase. It checks for various conditions such as directory structure, dependency versions, and the presence of certain strings in files.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Ensures the directory structure matches a predefined standard.
|
|
26
|
+
- Verifies that dependencies are up-to-date according to a specified minimum version.
|
|
27
|
+
- Checks for the presence or absence of specific strings in files.
|
|
28
|
+
- Configurable via a remote JSON configuration file.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
To install x-fidelity, clone the repository and install the dependencies using yarn:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
yarn global add x-fidelity
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
To run x-fidelity, use the following command:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
xfidelity --dir <directory> [--configUrl <url>]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `--dir <directory>`: The directory of the repository to analyze.
|
|
47
|
+
- `--configUrl <url>`: (Optional) The URL to fetch the configuration from.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
xfidelity --dir my-repo --configUrl https://example.com/config.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
The configuration file should be a JSON file containing the following structure:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"minimumDependencyVersions": {
|
|
62
|
+
"commander": "^2.0.0",
|
|
63
|
+
"nodemon": "^3.9.0"
|
|
64
|
+
},
|
|
65
|
+
"standardStructure": {
|
|
66
|
+
"src": {
|
|
67
|
+
"core": null,
|
|
68
|
+
"utils": null,
|
|
69
|
+
"operators": null,
|
|
70
|
+
"rules": null,
|
|
71
|
+
"facts": null
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
TODO REMOTE CONF + LLM
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
This project is licensed under the MIT License.
|
package/dist/core/cli.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.options = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const commander_1 = require("commander");
|
|
6
|
+
const banner = (`
|
|
7
|
+
=====================================
|
|
8
|
+
__ __ ________ ______
|
|
9
|
+
| ## | ## | ######## \\######
|
|
10
|
+
\\##\\/ ## ______ | ##__ | ##
|
|
11
|
+
>## ## | \\| ## \\ | ##
|
|
12
|
+
/ ####\\ \\######| ###### | ##
|
|
13
|
+
| ## \\##\\ | ## _| ##_
|
|
14
|
+
| ## | ## | ## | ## \\
|
|
15
|
+
\\## \\## \\## \\######
|
|
16
|
+
|
|
17
|
+
-------------------------------------
|
|
18
|
+
${new Date().toString()}`);
|
|
19
|
+
console.log(banner);
|
|
20
|
+
logger_1.logger.info([banner]);
|
|
21
|
+
commander_1.program
|
|
22
|
+
.option("-d, --dir <directory>", "The repo checkout directory")
|
|
23
|
+
.option("-c, --configUrl <url>", "The URL used to fetch config");
|
|
24
|
+
commander_1.program.parse();
|
|
25
|
+
const options = commander_1.program.opts();
|
|
26
|
+
exports.options = options;
|
|
27
|
+
// print help if no arguments are passed
|
|
28
|
+
if (commander_1.program.options.length === 0)
|
|
29
|
+
commander_1.program.help();
|
|
30
|
+
if (!options.dir) {
|
|
31
|
+
console.error("Repo directory is required. Please specify the directory using the -d or --dir option.");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
console.log(`Analysis of: ${process.env.PWD}/${options.dir}`);
|
|
35
|
+
logger_1.logger.info(`Analysis of: ${process.env.PWD}/${options.dir}`);
|
|
36
|
+
console.log('=====================================');
|
|
37
|
+
logger_1.logger.info('=====================================');
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.analyzeCodebase = void 0;
|
|
13
|
+
const logger_1 = require("../utils/logger");
|
|
14
|
+
const json_rules_engine_1 = require("json-rules-engine");
|
|
15
|
+
const repoFilesystemFacts_1 = require("../facts/repoFilesystemFacts");
|
|
16
|
+
const rules_1 = require("../rules");
|
|
17
|
+
const operators_1 = require("../operators");
|
|
18
|
+
const repoDependencyFacts_1 = require("../facts/repoDependencyFacts");
|
|
19
|
+
function analyzeCodebase(repoPath, configUrl) {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
const installedDependencyVersions = yield (0, repoDependencyFacts_1.getDependencyVersionFacts)();
|
|
22
|
+
const fileData = yield (0, repoFilesystemFacts_1.collectRepoFileData)(repoPath);
|
|
23
|
+
const minimumDependencyVersions = yield (0, repoDependencyFacts_1.collectMinimumDependencyVersions)(configUrl);
|
|
24
|
+
const standardStructure = yield (0, repoFilesystemFacts_1.collectStandardDirectoryStructure)(configUrl);
|
|
25
|
+
const engine = new json_rules_engine_1.Engine([], { replaceFactsInEventParams: true });
|
|
26
|
+
// Add operators to engine
|
|
27
|
+
operators_1.operators.map((operator) => engine.addOperator(operator.name, operator.fn));
|
|
28
|
+
// Add rules to engine
|
|
29
|
+
const rules = yield (0, rules_1.loadRules)();
|
|
30
|
+
rules.map((rule) => {
|
|
31
|
+
try {
|
|
32
|
+
engine.addRule(rule);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
console.error(`Error loading rule: ${rule === null || rule === void 0 ? void 0 : rule.name}`);
|
|
36
|
+
console.error(e.message);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
engine.on('failure', function (event, almanac, ruleResult) {
|
|
40
|
+
//console.log(event)
|
|
41
|
+
});
|
|
42
|
+
// Run the engine for each file's data
|
|
43
|
+
let results = [];
|
|
44
|
+
for (const file of fileData) {
|
|
45
|
+
logger_1.logger.info(`running engine for ${file.filePath}`);
|
|
46
|
+
const facts = { fileData: file, dependencyData: { installedDependencyVersions, minimumDependencyVersions },
|
|
47
|
+
standardStructure };
|
|
48
|
+
let fileFailures = [];
|
|
49
|
+
yield engine.run(facts)
|
|
50
|
+
.then(({ failureResults }) => {
|
|
51
|
+
failureResults.map((result) => {
|
|
52
|
+
var _a;
|
|
53
|
+
fileFailures.push({
|
|
54
|
+
ruleFailure: result === null || result === void 0 ? void 0 : result.name,
|
|
55
|
+
details: (_a = result === null || result === void 0 ? void 0 : result.event) === null || _a === void 0 ? void 0 : _a.params
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}).catch(e => logger_1.logger.error(e));
|
|
59
|
+
if (fileFailures.length > 0) {
|
|
60
|
+
results.push({ filePath: file.filePath, errors: fileFailures });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
logger_1.logger.info(`${fileData.length} files analyzed. ${results.length} files with errors.`);
|
|
64
|
+
return results;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
exports.analyzeCodebase = analyzeCodebase;
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const engine_1 = require("./engine");
|
|
13
|
+
const json_rules_engine_1 = require("json-rules-engine");
|
|
14
|
+
const repoFilesystemFacts_1 = require("../facts/repoFilesystemFacts");
|
|
15
|
+
const repoDependencyFacts_1 = require("../facts/repoDependencyFacts");
|
|
16
|
+
const rules_1 = require("../rules");
|
|
17
|
+
jest.mock('json-rules-engine');
|
|
18
|
+
jest.mock('../facts/repoFilesystemFacts');
|
|
19
|
+
jest.mock('../facts/repoDependencyFacts');
|
|
20
|
+
jest.mock('../rules');
|
|
21
|
+
jest.mock('../operators');
|
|
22
|
+
jest.mock('../utils/logger');
|
|
23
|
+
describe('analyzeCodebase', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
});
|
|
27
|
+
it('should analyze the codebase and return results', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
|
+
const mockFileData = [{ filePath: 'src/index.ts', fileContent: 'console.log("Hello, world!");' }];
|
|
29
|
+
const mockDependencyData = [{ dep: 'commander', ver: '2.0.0', min: '^2.0.0' }];
|
|
30
|
+
const mockStandardStructure = { src: { core: null, utils: null, operators: null, rules: null, facts: null } };
|
|
31
|
+
const mockRules = [{ name: 'mockRule', conditions: { all: [] }, event: { type: 'mockEvent' } }];
|
|
32
|
+
repoFilesystemFacts_1.collectRepoFileData.mockResolvedValue(mockFileData);
|
|
33
|
+
repoDependencyFacts_1.getDependencyVersionFacts.mockResolvedValue(mockDependencyData);
|
|
34
|
+
repoDependencyFacts_1.collectMinimumDependencyVersions.mockResolvedValue({});
|
|
35
|
+
repoFilesystemFacts_1.collectStandardDirectoryStructure.mockResolvedValue(mockStandardStructure);
|
|
36
|
+
rules_1.loadRules.mockResolvedValue(mockRules);
|
|
37
|
+
const engineRunMock = jest.fn().mockResolvedValue({ failureResults: [] });
|
|
38
|
+
json_rules_engine_1.Engine.mockImplementation(() => ({
|
|
39
|
+
addOperator: jest.fn(),
|
|
40
|
+
addRule: jest.fn(),
|
|
41
|
+
on: jest.fn(),
|
|
42
|
+
run: engineRunMock
|
|
43
|
+
}));
|
|
44
|
+
const results = yield (0, engine_1.analyzeCodebase)('mockRepoPath');
|
|
45
|
+
expect(repoFilesystemFacts_1.collectRepoFileData).toHaveBeenCalledWith('mockRepoPath');
|
|
46
|
+
expect(repoDependencyFacts_1.getDependencyVersionFacts).toHaveBeenCalled();
|
|
47
|
+
expect(repoDependencyFacts_1.collectMinimumDependencyVersions).toHaveBeenCalled();
|
|
48
|
+
expect(repoFilesystemFacts_1.collectStandardDirectoryStructure).toHaveBeenCalled();
|
|
49
|
+
expect(rules_1.loadRules).toHaveBeenCalled();
|
|
50
|
+
expect(engineRunMock).toHaveBeenCalledTimes(mockFileData.length);
|
|
51
|
+
expect(results).toEqual([]);
|
|
52
|
+
}));
|
|
53
|
+
it('should handle errors during analysis', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
const mockFileData = [{ filePath: 'src/index.ts', fileContent: 'console.log("Hello, world!");' }];
|
|
55
|
+
const mockDependencyData = [{ dep: 'commander', ver: '2.0.0', min: '^2.0.0' }];
|
|
56
|
+
const mockStandardStructure = { src: { core: null, utils: null, operators: null, rules: null, facts: null } };
|
|
57
|
+
const mockRules = [{ name: 'mockRule', conditions: { all: [] }, event: { type: 'mockEvent' } }];
|
|
58
|
+
repoFilesystemFacts_1.collectRepoFileData.mockResolvedValue(mockFileData);
|
|
59
|
+
repoDependencyFacts_1.getDependencyVersionFacts.mockResolvedValue(mockDependencyData);
|
|
60
|
+
repoDependencyFacts_1.collectMinimumDependencyVersions.mockResolvedValue({});
|
|
61
|
+
repoFilesystemFacts_1.collectStandardDirectoryStructure.mockResolvedValue(mockStandardStructure);
|
|
62
|
+
rules_1.loadRules.mockResolvedValue(mockRules);
|
|
63
|
+
const engineRunMock = jest.fn().mockRejectedValue(new Error('mock error'));
|
|
64
|
+
json_rules_engine_1.Engine.mockImplementation(() => ({
|
|
65
|
+
addOperator: jest.fn(),
|
|
66
|
+
addRule: jest.fn(),
|
|
67
|
+
on: jest.fn(),
|
|
68
|
+
run: engineRunMock
|
|
69
|
+
}));
|
|
70
|
+
const results = yield (0, engine_1.analyzeCodebase)('mockRepoPath');
|
|
71
|
+
expect(repoFilesystemFacts_1.collectRepoFileData).toHaveBeenCalledWith('mockRepoPath');
|
|
72
|
+
expect(repoDependencyFacts_1.getDependencyVersionFacts).toHaveBeenCalled();
|
|
73
|
+
expect(repoDependencyFacts_1.collectMinimumDependencyVersions).toHaveBeenCalled();
|
|
74
|
+
expect(repoFilesystemFacts_1.collectStandardDirectoryStructure).toHaveBeenCalled();
|
|
75
|
+
expect(rules_1.loadRules).toHaveBeenCalled();
|
|
76
|
+
expect(engineRunMock).toHaveBeenCalledTimes(mockFileData.length);
|
|
77
|
+
expect(results).toEqual([]);
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
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.findPropertiesInTree = exports.getDependencyVersionFacts = exports.collectLocalDependencies = exports.collectMinimumDependencyVersions = void 0;
|
|
16
|
+
const logger_1 = require("../utils/logger");
|
|
17
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
18
|
+
const axios_1 = __importDefault(require("axios"));
|
|
19
|
+
const child_process_1 = require("child_process");
|
|
20
|
+
/**
|
|
21
|
+
* Collects the minimum dependency versions.
|
|
22
|
+
* @returns The minimum dependency versions.
|
|
23
|
+
*/
|
|
24
|
+
function collectMinimumDependencyVersions(configUrl) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
if (configUrl) {
|
|
27
|
+
try {
|
|
28
|
+
const response = yield axios_1.default.get(configUrl);
|
|
29
|
+
return response.data.minimumDependencyVersions;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger_1.logger.error(`Error fetching minimum dependency versions from configUrl: ${error}`);
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return {
|
|
38
|
+
commander: '^2.0.0',
|
|
39
|
+
nodemon: '^3.9.0'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
exports.collectMinimumDependencyVersions = collectMinimumDependencyVersions;
|
|
45
|
+
/**
|
|
46
|
+
* Collects the local dependencies.
|
|
47
|
+
* @returns The local dependencies.
|
|
48
|
+
*/
|
|
49
|
+
function collectLocalDependencies() {
|
|
50
|
+
let result = {};
|
|
51
|
+
try {
|
|
52
|
+
let stdout = (0, child_process_1.execSync)('npm ls -a --json');
|
|
53
|
+
result = JSON.parse(stdout.toString());
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
logger_1.logger.error(`exec error: ${e}`);
|
|
57
|
+
//console.error(`exec error: ${e}`);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
exports.collectLocalDependencies = collectLocalDependencies;
|
|
62
|
+
/**
|
|
63
|
+
* Gets the installed dependency versions.
|
|
64
|
+
* @returns The installed dependency versions.
|
|
65
|
+
*/
|
|
66
|
+
function getDependencyVersionFacts() {
|
|
67
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
68
|
+
const localDependencies = yield collectLocalDependencies();
|
|
69
|
+
const minimumDependencyVersions = yield collectMinimumDependencyVersions();
|
|
70
|
+
//console.log(localDependencies);
|
|
71
|
+
const installedDependencyVersions = findPropertiesInTree(localDependencies, minimumDependencyVersions);
|
|
72
|
+
return installedDependencyVersions;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
exports.getDependencyVersionFacts = getDependencyVersionFacts;
|
|
76
|
+
/**
|
|
77
|
+
* Recursively search for properties in a tree of objects.
|
|
78
|
+
* @param depGraph - The object to search.
|
|
79
|
+
* @param minVersions - The minimum dependency versions to search for.
|
|
80
|
+
* @returns An array of results.
|
|
81
|
+
*/
|
|
82
|
+
function findPropertiesInTree(depGraph, minVersions) {
|
|
83
|
+
let results = [];
|
|
84
|
+
logger_1.logger.debug(`depGraph: ${depGraph}`);
|
|
85
|
+
function walk(depGraph) {
|
|
86
|
+
if (lodash_1.default.isObject(depGraph) && !lodash_1.default.isArray(depGraph)) {
|
|
87
|
+
for (let depName in depGraph) {
|
|
88
|
+
if (Object.keys(minVersions).includes(depName)) {
|
|
89
|
+
results.push({ dep: depName, ver: depGraph[depName].version, min: minVersions[depName] });
|
|
90
|
+
}
|
|
91
|
+
if (lodash_1.default.isObject(depGraph[depName])) {
|
|
92
|
+
walk(depGraph[depName]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (lodash_1.default.isArray(depGraph)) {
|
|
97
|
+
for (let item of depGraph) {
|
|
98
|
+
walk(item);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
walk(depGraph);
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
exports.findPropertiesInTree = findPropertiesInTree;
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const child_process_1 = require("child_process");
|
|
13
|
+
const repoDependencyFacts_1 = require("./repoDependencyFacts");
|
|
14
|
+
const logger_1 = require("../utils/logger");
|
|
15
|
+
jest.mock('child_process', () => ({
|
|
16
|
+
execSync: jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
jest.mock('../utils/logger', () => ({
|
|
19
|
+
logger: {
|
|
20
|
+
error: jest.fn(),
|
|
21
|
+
debug: jest.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
describe('collectMinimumDependencyVersions', () => {
|
|
25
|
+
it('should return the correct minimum dependency versions', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
|
+
const result = yield (0, repoDependencyFacts_1.collectMinimumDependencyVersions)();
|
|
27
|
+
expect(result).toEqual({
|
|
28
|
+
commander: '^2.0.0',
|
|
29
|
+
nodemon: '^3.9.0'
|
|
30
|
+
});
|
|
31
|
+
}));
|
|
32
|
+
});
|
|
33
|
+
describe('collectLocalDependencies', () => {
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
it('should return parsed JSON result when execSync succeeds', () => {
|
|
38
|
+
const mockResult = JSON.stringify({ dependencies: {} });
|
|
39
|
+
child_process_1.execSync.mockReturnValue(Buffer.from(mockResult));
|
|
40
|
+
const result = (0, repoDependencyFacts_1.collectLocalDependencies)();
|
|
41
|
+
expect(result).toEqual({ dependencies: {} });
|
|
42
|
+
});
|
|
43
|
+
it('should log error and return empty object when execSync fails', () => {
|
|
44
|
+
child_process_1.execSync.mockImplementation(() => {
|
|
45
|
+
throw new Error('mock error');
|
|
46
|
+
});
|
|
47
|
+
const result = (0, repoDependencyFacts_1.collectLocalDependencies)();
|
|
48
|
+
expect(logger_1.logger.error).toHaveBeenCalledWith('exec error: Error: mock error');
|
|
49
|
+
//expect(console.error).toHaveBeenCalledWith('exec error: Error: mock error');
|
|
50
|
+
expect(result).toEqual({});
|
|
51
|
+
});
|
|
52
|
+
it('should handle non-JSON response gracefully', () => {
|
|
53
|
+
child_process_1.execSync.mockReturnValue(Buffer.from('invalid json'));
|
|
54
|
+
const result = (0, repoDependencyFacts_1.collectLocalDependencies)();
|
|
55
|
+
expect(result).toEqual({});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('getDependencyVersionFacts', () => {
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
jest.clearAllMocks();
|
|
61
|
+
});
|
|
62
|
+
it('should return installed dependency versions correctly', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
|
+
const mockLocalDependencies = { dependencies: { commander: { version: '2.0.0' }, nodemon: { version: '3.9.0' } } };
|
|
64
|
+
const mockMinimumVersions = { commander: '^2.0.0', nodemon: '^3.9.0' };
|
|
65
|
+
child_process_1.execSync.mockReturnValue(Buffer.from(JSON.stringify(mockLocalDependencies)));
|
|
66
|
+
const result = yield (0, repoDependencyFacts_1.getDependencyVersionFacts)();
|
|
67
|
+
expect(result).toEqual([
|
|
68
|
+
{ dep: 'commander', ver: '2.0.0', min: '^2.0.0' },
|
|
69
|
+
{ dep: 'nodemon', ver: '3.9.0', min: '^3.9.0' }
|
|
70
|
+
]);
|
|
71
|
+
}));
|
|
72
|
+
it('should return an empty array if no dependencies match minimum versions', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
const mockLocalDependencies = { dependencies: { someOtherDep: { version: '1.0.0' } } };
|
|
74
|
+
const mockMinimumVersions = { commander: '^2.0.0', nodemon: '^3.9.0' };
|
|
75
|
+
child_process_1.execSync.mockReturnValue(Buffer.from(JSON.stringify(mockLocalDependencies)));
|
|
76
|
+
const result = yield (0, repoDependencyFacts_1.getDependencyVersionFacts)();
|
|
77
|
+
expect(result).toEqual([]);
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
describe('findPropertiesInTree', () => {
|
|
81
|
+
it('should find properties in the tree correctly', () => {
|
|
82
|
+
const depGraph = {
|
|
83
|
+
commander: { version: '2.0.0' },
|
|
84
|
+
nodemon: { version: '3.9.0' }
|
|
85
|
+
};
|
|
86
|
+
const minVersions = { commander: '^2.0.0', nodemon: '^3.9.0' };
|
|
87
|
+
const result = (0, repoDependencyFacts_1.findPropertiesInTree)(depGraph, minVersions);
|
|
88
|
+
expect(result).toEqual([
|
|
89
|
+
{ dep: 'commander', ver: '2.0.0', min: '^2.0.0' },
|
|
90
|
+
{ dep: 'nodemon', ver: '3.9.0', min: '^3.9.0' }
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
it('should return an empty array if no properties match', () => {
|
|
94
|
+
const depGraph = {
|
|
95
|
+
someOtherDep: { version: '1.0.0' }
|
|
96
|
+
};
|
|
97
|
+
const minVersions = { commander: '^2.0.0', nodemon: '^3.9.0' };
|
|
98
|
+
const result = (0, repoDependencyFacts_1.findPropertiesInTree)(depGraph, minVersions);
|
|
99
|
+
expect(result).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
it('should handle nested dependencies correctly', () => {
|
|
102
|
+
const depGraph = {
|
|
103
|
+
commander: { version: '2.0.0',
|
|
104
|
+
dependencies: {
|
|
105
|
+
nodemon: { version: '3.9.0' }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const minVersions = { commander: '^2.0.0', nodemon: '^3.9.0' };
|
|
110
|
+
const result = (0, repoDependencyFacts_1.findPropertiesInTree)(depGraph, minVersions);
|
|
111
|
+
expect(result).toEqual([
|
|
112
|
+
{ dep: 'commander', ver: '2.0.0', min: '^2.0.0' },
|
|
113
|
+
{ dep: 'nodemon', ver: '3.9.0', min: '^3.9.0' }
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
it('should not add non-matching dependencies to results', () => {
|
|
117
|
+
const depGraph = {
|
|
118
|
+
someOtherDep: { version: '1.0.0' }
|
|
119
|
+
};
|
|
120
|
+
const minVersions = { commander: '^2.0.0', nodemon: '^3.9.0' };
|
|
121
|
+
const result = (0, repoDependencyFacts_1.findPropertiesInTree)(depGraph, minVersions);
|
|
122
|
+
expect(result).toEqual([]);
|
|
123
|
+
});
|
|
124
|
+
it('should handle complex mixed structures', () => {
|
|
125
|
+
const depGraph = {
|
|
126
|
+
commander: {
|
|
127
|
+
version: '2.0.0',
|
|
128
|
+
dependencies: {
|
|
129
|
+
nodemon: {
|
|
130
|
+
version: '3.9.0',
|
|
131
|
+
dependencies: {
|
|
132
|
+
lodash: {
|
|
133
|
+
version: '4.17.21'
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const minVersions = { commander: '^2.0.0', nodemon: '^3.9.0', lodash: '^4.17.20' };
|
|
141
|
+
const result = (0, repoDependencyFacts_1.findPropertiesInTree)(depGraph, minVersions);
|
|
142
|
+
//console.log(result);
|
|
143
|
+
expect(result).toEqual([
|
|
144
|
+
{ dep: 'commander', ver: '2.0.0', min: '^2.0.0' },
|
|
145
|
+
{ dep: 'nodemon', ver: '3.9.0', min: '^3.9.0' },
|
|
146
|
+
{ dep: 'lodash', ver: '4.17.21', min: '^4.17.20' }
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
});
|