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.
Files changed (53) hide show
  1. package/.czrc +3 -0
  2. package/.github/dependabot.yml +11 -0
  3. package/.github/workflows/ci.yml +28 -0
  4. package/.github/workflows/release.yml +34 -0
  5. package/.releaserc +11 -0
  6. package/CHANGELOG.md +20 -0
  7. package/LICENSE +21 -0
  8. package/README.md +81 -0
  9. package/commitlint.config.js +3 -0
  10. package/dist/core/cli.js +37 -0
  11. package/dist/core/engine.js +67 -0
  12. package/dist/core/engine.test.js +79 -0
  13. package/dist/facts/repoDependencyFacts.js +105 -0
  14. package/dist/facts/repoDependencyFacts.test.js +149 -0
  15. package/dist/facts/repoFilesystemFacts.js +91 -0
  16. package/dist/index.js +35 -0
  17. package/dist/operators/currentDependencies.js +58 -0
  18. package/dist/operators/currentDependencies.test.js +43 -0
  19. package/dist/operators/directoryStructureMatches.js +63 -0
  20. package/dist/operators/directoryStructureMatches.test.js +61 -0
  21. package/dist/operators/fileContains.js +17 -0
  22. package/dist/operators/fileContains.test.js +25 -0
  23. package/dist/operators/index.js +11 -0
  24. package/dist/rules/index.js +61 -0
  25. package/dist/rules/noDatabases-rule.json +21 -0
  26. package/dist/rules/standardDirectoryStructure-rule.json +25 -0
  27. package/dist/rules/supportedCoreDependencies-rule.json +29 -0
  28. package/dist/typeDefs.js +2 -0
  29. package/dist/utils/logger.js +14 -0
  30. package/dist/xfidelity +35 -0
  31. package/jest.config.js +6 -0
  32. package/package.json +69 -0
  33. package/src/core/cli.ts +42 -0
  34. package/src/core/engine.test.ts +82 -0
  35. package/src/core/engine.ts +68 -0
  36. package/src/facts/repoDependencyFacts.test.ts +166 -0
  37. package/src/facts/repoDependencyFacts.ts +88 -0
  38. package/src/facts/repoFilesystemFacts.ts +80 -0
  39. package/src/index.ts +25 -0
  40. package/src/operators/currentDependencies.test.ts +45 -0
  41. package/src/operators/currentDependencies.ts +40 -0
  42. package/src/operators/directoryStructureMatches.test.ts +47 -0
  43. package/src/operators/directoryStructureMatches.ts +43 -0
  44. package/src/operators/fileContains.test.ts +27 -0
  45. package/src/operators/fileContains.ts +19 -0
  46. package/src/operators/index.ts +12 -0
  47. package/src/rules/index.ts +29 -0
  48. package/src/rules/noDatabases-rule.json +21 -0
  49. package/src/rules/standardDirectoryStructure-rule.json +25 -0
  50. package/src/rules/supportedCoreDependencies-rule.json +29 -0
  51. package/src/typeDefs.ts +40 -0
  52. package/src/utils/logger.ts +20 -0
  53. package/tsconfig.json +9 -0
@@ -0,0 +1,45 @@
1
+ import { logger } from '../utils/logger';
2
+ import { currentDependencies } from './currentDependencies';
3
+
4
+ describe('currentDependencies', () => {
5
+ it('returns true when filePath is not package.json', () => {
6
+ const filePath = 'notPackage.json';
7
+ const dependencyData = {};
8
+ expect(currentDependencies.fn(filePath, JSON.stringify(dependencyData))).toBe(true);
9
+ });
10
+
11
+ it('returns true when all dependencies are up-to-date', () => {
12
+ const filePath = 'package.json';
13
+ const dependencyData = {
14
+ installedDependencyVersions: [
15
+ { dep: 'dep1', ver: '2.0.0', min: '1.0.0' },
16
+ { dep: 'dep2', ver: '3.0.0', min: '2.0.0' }
17
+ ]
18
+ };
19
+ expect(currentDependencies.fn(filePath, dependencyData)).toBe(true);
20
+ });
21
+
22
+ it('returns false when at least one dependency is outdated', () => {
23
+ const filePath = 'package.json';
24
+ const dependencyData = {
25
+ installedDependencyVersions: [
26
+ { dep: 'dep1', ver: '.0.0', min: '1.0.0' },
27
+ { dep: 'dep2', ver: '1.0.0', min: '2.0.0' }
28
+ ]
29
+ };
30
+ expect(currentDependencies.fn(filePath, dependencyData)).toBe(false);
31
+ });
32
+
33
+ it('logs an error when at least one dependency is outdated', () => {
34
+ const filePath = 'package.json';
35
+ const dependencyData = {
36
+ installedDependencyVersions: [
37
+ { dep: 'dep1', ver: '1.0.0', min: '1.0.0' },
38
+ { dep: 'dep2', ver: '1.0.0', min: '2.0.0' }
39
+ ]
40
+ };
41
+ const errorSpy = jest.spyOn(logger, 'error');
42
+ currentDependencies.fn(filePath, dependencyData);
43
+ expect(errorSpy).toHaveBeenCalled();
44
+ });
45
+ });
@@ -0,0 +1,40 @@
1
+ import { logger } from '../utils/logger';
2
+ import { RuleDefn, VersionData } from '../typeDefs';
3
+ import * as semver from 'semver';
4
+
5
+ const currentDependencies: RuleDefn = {
6
+ 'name': 'currentDependencies',
7
+ 'fn': (filePath: any, dependencyData: any) => {
8
+ let result = false;
9
+
10
+ // this is a special rule we only run on the root package.json, however it checks the entire dependency tree.
11
+ if (filePath !== 'package.json') {
12
+ return true;
13
+ }
14
+ logger.debug(`currentDependencies: working..`);
15
+
16
+ try {
17
+ logger.debug(`currentDependencies: processing ${dependencyData.installedDependencyVersions}`);
18
+
19
+ dependencyData.installedDependencyVersions.map((versionData: VersionData) => {
20
+ logger.debug(`currentDependencies: checking ${versionData.dep}`);
21
+
22
+ const requiredRange = new semver.Range(versionData.min);
23
+ if (!semver.gtr(versionData.ver, requiredRange)) {
24
+ let msg = `dependency ${versionData.dep} is outdated. Current version: ${versionData.ver}, required: ${versionData.min}`;
25
+ logger.error(`currentDependencies: ${msg}`);
26
+ throw new Error(msg);
27
+ }
28
+ });
29
+ result = true;
30
+ } catch (e) {
31
+
32
+ result = false;
33
+ }
34
+ logger.debug(`currentDependencies: ${result}`);
35
+ return result;
36
+
37
+ }
38
+ }
39
+
40
+ export { currentDependencies };
@@ -0,0 +1,47 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import { directoryStructureMatches } from './directoryStructureMatches';
4
+ import { Stats } from 'fs';
5
+
6
+ jest.mock('fs');
7
+ jest.mock('path');
8
+
9
+ describe('directoryStructureMatches', () => {
10
+ const mockedFs = fs as jest.Mocked<typeof fs>;
11
+ const mockedPath = path as jest.Mocked<typeof path>;
12
+
13
+ beforeEach(() => {
14
+ mockedFs.existsSync.mockReset();
15
+ mockedFs.lstatSync.mockReset();
16
+ mockedPath.dirname.mockReset();
17
+ mockedPath.join.mockReset();
18
+ });
19
+
20
+ it('should return true if filePath is not "yarn.lock" or hasChecked is true', () => {
21
+ const result = directoryStructureMatches.fn('notYarn.lock', {});
22
+ expect(result).toBe(true);
23
+ });
24
+ // ...
25
+
26
+ it('should return true if directory structure matches the standard structure', () => {
27
+ mockedPath.dirname.mockReturnValue('/repo');
28
+ mockedPath.join.mockImplementation((...args) => args.join('/'));
29
+ mockedFs.existsSync.mockReturnValue(true);
30
+ mockedFs.lstatSync.mockReturnValue({ isDirectory: () => true } as Stats);
31
+
32
+ const result = directoryStructureMatches.fn('yarn.lock', { dir1: null, dir2: { subdir1: null } });
33
+
34
+ expect(result).toBe(true);
35
+ });
36
+
37
+ // it('should return false if directory structure does not match the standard structure', () => {
38
+ // mockedPath.dirname.mockReturnValue('/repo');
39
+ // mockedPath.join.mockImplementation((...args) => args.join('/'));
40
+ // mockedFs.existsSync.mockReturnValue(false);
41
+ // mockedFs.lstatSync.mockReturnValue({ isDirectory: () => false } as Stats);
42
+
43
+ // const result = directoryStructureMatches.fn('yarn.lock', { dir1: null, dir2: { subdir1: null } });
44
+
45
+ // expect(result).toBe(false);
46
+ // });
47
+ });
@@ -0,0 +1,43 @@
1
+ import { RuleDefn } from '../typeDefs';
2
+ import { logger } from '../utils/logger';
3
+ import * as path from 'path';
4
+ import * as fs from 'fs';
5
+
6
+ let hasChecked = false;
7
+
8
+ const directoryStructureMatches: RuleDefn = {
9
+ 'name': 'directoryStructureMatches',
10
+ 'fn': (filePath: any, standardStructure: any) => {
11
+ if (filePath !== 'yarn.lock' || hasChecked) {
12
+ return true;
13
+ }
14
+
15
+ const repoPath = path.dirname(filePath);
16
+ let result = true;
17
+
18
+ // check the directory structure of the repo against the standard structure
19
+ function checkStructure(currentPath: string, structure: any): boolean {
20
+ for (const key in structure) {
21
+ const newPath = path.join(currentPath, key);
22
+ logger.debug(`checking ${newPath}`);
23
+ if (!fs.existsSync(newPath) || !fs.lstatSync(newPath).isDirectory()) {
24
+ logger.debug(`Missing or invalid directory: ${newPath}`);
25
+ return false;
26
+ } else {
27
+ result = checkStructure(newPath, structure[key]);
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+
33
+ const checkResult = checkStructure(repoPath, standardStructure);
34
+ //console.log(checkResult)
35
+ if (!checkResult) {
36
+ result = false;
37
+ }
38
+ hasChecked = true;
39
+ return result;
40
+ }
41
+ };
42
+
43
+ export { directoryStructureMatches };
@@ -0,0 +1,27 @@
1
+ import { fileContains } from './fileContains';
2
+
3
+ describe('fileContains', () => {
4
+ it('returns true when the checkString is found in the fileContent', () => {
5
+ const fileContent = 'Hello, world!';
6
+ const checkString = 'world';
7
+ expect(fileContains.fn(fileContent, checkString)).toBe(true);
8
+ });
9
+
10
+ it('returns false when the checkString is not found in the fileContent', () => {
11
+ const fileContent = 'Hello, world!';
12
+ const checkString = 'universe';
13
+ expect(fileContains.fn(fileContent, checkString)).toBe(false);
14
+ });
15
+
16
+ it('returns true when the checkString is a regular expression that matches part of the fileContent', () => {
17
+ const fileContent = 'Hello, world!';
18
+ const checkString = '\\bworld\\b';
19
+ expect(fileContains.fn(fileContent, checkString)).toBe(true);
20
+ });
21
+
22
+ it('returns false when the checkString is a regular expression that does not match any part of the fileContent', () => {
23
+ const fileContent = 'Hello, world!';
24
+ const checkString = '\\buniverse\\b';
25
+ expect(fileContains.fn(fileContent, checkString)).toBe(false);
26
+ });
27
+ });
@@ -0,0 +1,19 @@
1
+ import { logger } from '../utils/logger';
2
+ import { RuleDefn } from '../typeDefs';
3
+
4
+ const fileContains: RuleDefn = {
5
+ 'name': 'fileContains',
6
+ 'fn': (fileContent: any, checkString: any) => {
7
+ let result = false;
8
+
9
+ const regex = new RegExp(checkString, 'g');
10
+ if (regex.test(fileContent)) {
11
+ result = true;
12
+ }
13
+ logger.debug(`fileContains '${checkString}': ${result}`);
14
+ return result;
15
+
16
+ }
17
+ }
18
+
19
+ export { fileContains };
@@ -0,0 +1,12 @@
1
+ import { RuleDefn } from '../typeDefs';
2
+ import { currentDependencies } from './currentDependencies';
3
+ import { fileContains } from './fileContains';
4
+ import { directoryStructureMatches } from './directoryStructureMatches';
5
+
6
+ let operators: RuleDefn[] = [];
7
+ operators.push(currentDependencies);
8
+ operators.push(fileContains);
9
+ operators.push(directoryStructureMatches);
10
+ //oracle
11
+
12
+ export { operators };
@@ -0,0 +1,29 @@
1
+ import { logger } from '../utils/logger';
2
+ import { RuleProperties } from 'json-rules-engine';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+
6
+ let rules: RuleProperties[] = [];
7
+
8
+ // import each json file in local directory and add to rules array as a RuleProperties object
9
+ async function loadRules() {
10
+ logger.debug(`loading json rules..`);
11
+ const ruleFiles = (await fs.promises.readdir(__dirname)).filter(file => file.endsWith('.json'));
12
+
13
+ logger.debug(`found ${ruleFiles.length} rule files to load.`);
14
+ const rules = await Promise.all(ruleFiles.map(async file => {
15
+ try {
16
+ logger.debug(`loading ${file}...`);
17
+ const rule = await import(path.join(__dirname, file));
18
+ return rule;
19
+ } catch (e) {
20
+ logger.error(`FATAL: Error loading rule file: ${file}`);
21
+ logger.error(e);
22
+ console.error(e);
23
+ }
24
+ }));
25
+ return rules;
26
+ }
27
+
28
+
29
+ export { loadRules };
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "noDatabases",
3
+ "conditions": {
4
+ "not": {
5
+ "any": [
6
+ {
7
+ "fact": "fileData",
8
+ "path": "$.fileContent",
9
+ "operator": "fileContains",
10
+ "value": "oracle"
11
+ }
12
+ ]
13
+ }
14
+ },
15
+ "event": {
16
+ "type": "violation",
17
+ "params": {
18
+ "message": "noDatabases: code must not directly call databases"
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "standardDirectoryStructure",
3
+ "conditions": {
4
+ "all": [
5
+ {
6
+ "fact": "fileData",
7
+ "path": "$.filePath",
8
+ "operator": "directoryStructureMatches",
9
+ "value": {
10
+ "fact": "standardStructure"
11
+ }
12
+ }
13
+ ]
14
+ },
15
+ "event": {
16
+ "type": "violation",
17
+ "params": {
18
+ "message": "The directory structure does not match the standard.",
19
+ "details": {
20
+ "fact": "fileData",
21
+ "path": "$.filePath"
22
+ }
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "supportedCoreDependencies",
3
+ "conditions": {
4
+ "all": [
5
+ {
6
+ "fact": "fileData",
7
+ "path": "$.filePath",
8
+ "operator": "currentDependencies",
9
+ "value": {
10
+ "fact": "dependencyData"
11
+ }
12
+ }
13
+ ]
14
+ },
15
+ "event": {
16
+ "type": "violation",
17
+ "params": {
18
+ "message": "some important dependencies have expired!",
19
+ "filePath": {
20
+ "fact": "fileData",
21
+ "path": "$.filePath"
22
+ },
23
+ "minimumDependencyVersions": {
24
+ "fact": "dependencyData",
25
+ "path": "$.minimumDependencyVersions"
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,40 @@
1
+ import { OperatorEvaluator } from 'json-rules-engine';
2
+
3
+ export type RuleDefn = {
4
+ name: string,
5
+ fn: OperatorEvaluator<string, any>
6
+ }
7
+ export interface ScanResult {
8
+ filePath: string;
9
+ errors: RuleFailure[];
10
+ }
11
+ /**
12
+ * Represents a rule failure.
13
+ */
14
+ export interface RuleFailure {
15
+ ruleFailure: string;
16
+ details: Record<string, any> | undefined;
17
+ }/**
18
+ * Represents the version data of a dependency.
19
+ */
20
+
21
+ export interface VersionData {
22
+ dep: string;
23
+ ver: string;
24
+ min: string;
25
+ }
26
+ /**
27
+ * Represents the minimum dependency versions.
28
+ */
29
+
30
+ export interface MinimumDepVersions {
31
+ [propertyName: string]: string;
32
+ }
33
+ /**
34
+ * Represents the local dependencies.
35
+ */
36
+
37
+ export interface LocalDependencies {
38
+ [propertyName: string]: { version: string; };
39
+ }
40
+
@@ -0,0 +1,20 @@
1
+ import { createLogger, format, transports } from 'winston';
2
+
3
+ const logger = createLogger({
4
+ level: 'debug',
5
+ format: format.combine(
6
+ format.timestamp({
7
+ format: 'YYYY-MM-DD HH:mm:ss.SSS ZZ'
8
+ }),
9
+ format.errors({ stack: true }),
10
+ format.splat(),
11
+ format.json(),
12
+ format.prettyPrint()
13
+ ),
14
+ transports: [
15
+ new transports.File({ filename: 'x-fidelity.log'})
16
+ ]
17
+ });
18
+
19
+
20
+ export { logger };
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "outDir": "dist",
6
+ "strict": true,
7
+ "esModuleInterop": true
8
+ }
9
+ }