x-fidelity 3.0.2 → 3.0.4
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 +17 -0
- package/dist/core/cli.d.ts +2 -2
- package/dist/core/cli.js +92 -90
- package/dist/core/configManager.js +9 -4
- package/dist/index.js +15 -5
- package/dist/xfidelity +15 -5
- package/package.json +2 -2
- package/src/core/cli.ts +93 -90
- package/src/core/configManager.ts +9 -3
- package/src/index.ts +16 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## [3.0.4](https://github.com/zotoio/x-fidelity/compare/v3.0.3...v3.0.4) (2025-02-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **plugins:** ensure logger instance reused and fix entrypoint logic ([c21ad6e](https://github.com/zotoio/x-fidelity/commit/c21ad6e764e18f7910170ed970492208fe4197e8))
|
|
7
|
+
|
|
8
|
+
## [3.0.3](https://github.com/zotoio/x-fidelity/compare/v3.0.2...v3.0.3) (2025-02-18)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **error handling:** deal with unexpected errors globally ensuring async ops finish ([2292d4d](https://github.com/zotoio/x-fidelity/commit/2292d4d09ef75a419778d3df77381a1353217361))
|
|
14
|
+
* improve error handling and telemetry logging ([f7fa9ca](https://github.com/zotoio/x-fidelity/commit/f7fa9ca6b63bcf75ebab8dbc710eb78fc718cefe))
|
|
15
|
+
* normalize event type casing in telemetry test ([9b791f9](https://github.com/zotoio/x-fidelity/commit/9b791f969816f29efa6138b9f95235c6614a3ac7))
|
|
16
|
+
* standardize event type casing to lowercase in error handling ([0416cca](https://github.com/zotoio/x-fidelity/commit/0416ccae4f796ee1a3404f65545a00d67e26bbc7))
|
|
17
|
+
|
|
1
18
|
## [3.0.2](https://github.com/zotoio/x-fidelity/compare/v3.0.1...v3.0.2) (2025-02-15)
|
|
2
19
|
|
|
3
20
|
|
package/dist/core/cli.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const DEMO_CONFIG_PATH: string;
|
|
2
|
-
declare const options: import("commander").OptionValues;
|
|
3
|
-
export
|
|
2
|
+
export declare const options: import("commander").OptionValues;
|
|
3
|
+
export declare function initCLI(): void;
|
package/dist/core/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.options = exports.DEMO_CONFIG_PATH = void 0;
|
|
7
|
+
exports.initCLI = initCLI;
|
|
7
8
|
const logger_1 = require("../utils/logger");
|
|
8
9
|
const commander_1 = require("commander");
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
@@ -11,85 +12,85 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
11
12
|
const package_json_1 = require("../../package.json");
|
|
12
13
|
const inputValidation_1 = require("../utils/inputValidation");
|
|
13
14
|
const prettyjson_1 = __importDefault(require("prettyjson"));
|
|
14
|
-
// Ensure logger is initialized
|
|
15
|
-
if (!logger_1.logger || typeof logger_1.logger.info !== 'function') {
|
|
16
|
-
console.error({ msg: 'Logger is not properly initialized' });
|
|
17
|
-
// Instead of exiting, we'll create a fallback logger
|
|
18
|
-
const fallbackLogger = {
|
|
19
|
-
info: (obj) => console.log(JSON.stringify(obj)),
|
|
20
|
-
error: (obj) => console.error(JSON.stringify(obj)),
|
|
21
|
-
warn: (obj) => console.warn(JSON.stringify(obj)),
|
|
22
|
-
debug: (obj) => console.debug(JSON.stringify(obj))
|
|
23
|
-
};
|
|
24
|
-
global.logger = fallbackLogger;
|
|
25
|
-
}
|
|
26
|
-
logger_1.logger.info('CLI initialized');
|
|
27
15
|
exports.DEMO_CONFIG_PATH = path_1.default.resolve(__dirname, '../demoConfig');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
exports.options = commander_1.program.opts();
|
|
17
|
+
function initCLI() {
|
|
18
|
+
// Ensure logger is initialized
|
|
19
|
+
if (!logger_1.logger || typeof logger_1.logger.info !== 'function') {
|
|
20
|
+
console.error({ msg: 'Logger is not properly initialized' });
|
|
21
|
+
// Instead of exiting, we'll create a fallback logger
|
|
22
|
+
const fallbackLogger = {
|
|
23
|
+
info: (obj) => console.log(JSON.stringify(obj)),
|
|
24
|
+
error: (obj) => console.error(JSON.stringify(obj)),
|
|
25
|
+
warn: (obj) => console.warn(JSON.stringify(obj)),
|
|
26
|
+
debug: (obj) => console.debug(JSON.stringify(obj))
|
|
27
|
+
};
|
|
28
|
+
global.logger = fallbackLogger;
|
|
29
|
+
}
|
|
30
|
+
logger_1.logger.info('CLI initialized');
|
|
31
|
+
// we are overiding the default behavior of commander to exit
|
|
32
|
+
// the process due to https://github.com/pinojs/pino/issues/871
|
|
33
|
+
commander_1.program.exitOverride(() => {
|
|
34
|
+
try {
|
|
35
|
+
if (process.env.NODE_ENV !== 'test')
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
//swallow
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
commander_1.program
|
|
43
|
+
.option("-d, --dir <directory>", "code directory to analyze. equivalent of directory argument")
|
|
44
|
+
.option("-a, --archetype <archetype>", "The archetype to use for analysis", "node-fullstack")
|
|
45
|
+
.option("-c, --configServer <configServer>", "The config server URL for fetching remote archetype configurations and rules. This takes precedence over localConfigPath.")
|
|
46
|
+
.option("-o, --openaiEnabled <boolean>", "Enable OpenAI analysis", false)
|
|
47
|
+
.option("-t, --telemetryCollector <telemetryCollector>", "The URL telemetry data will be sent to for usage analysis")
|
|
48
|
+
.option("-m, --mode <mode>", "Run mode: 'client' or 'server'", "client")
|
|
49
|
+
.option("-p, --port <port>", "The port to run the server on", "8888")
|
|
50
|
+
.option("-l, --localConfigPath <path>", "Path to local archetype config and rules", exports.DEMO_CONFIG_PATH)
|
|
51
|
+
.option("-j, --jsonTTL <minutes>", "Set the server json cache TTL in minutes", "10")
|
|
52
|
+
.option("-e, --extensions <modules...>", "Space-separated list of npm module names to load as extensions")
|
|
53
|
+
.option("-x, --examine <archetype>", "Examine the archetype configuration and rules")
|
|
54
|
+
.version(package_json_1.version, "-v, --version", "Output the version number of xfidelity")
|
|
55
|
+
.helpOption("-h, --help", "Display help for command")
|
|
56
|
+
.argument('[directory]', 'code directory to analyze');
|
|
57
|
+
commander_1.program.parse(process.argv);
|
|
58
|
+
// Resolve paths
|
|
59
|
+
const resolvePath = (inputPath) => {
|
|
60
|
+
const resolvedPath = path_1.default === null || path_1.default === void 0 ? void 0 : path_1.default.resolve(process.cwd(), inputPath);
|
|
61
|
+
if (!fs_1.default.existsSync(resolvedPath)) {
|
|
62
|
+
throw new Error(`Path does not exist: ${resolvedPath}`);
|
|
63
|
+
}
|
|
64
|
+
if (!(0, inputValidation_1.validateInput)(resolvedPath)) {
|
|
65
|
+
throw new Error(`Potential malicious input detected: ${inputPath}`);
|
|
66
|
+
}
|
|
67
|
+
return resolvedPath;
|
|
68
|
+
};
|
|
69
|
+
if (commander_1.program.args.length === 0) {
|
|
70
|
+
if (process.env.NODE_ENV === 'test' || exports.options.mode === 'server')
|
|
71
|
+
exports.options.dir = '.';
|
|
72
|
+
if (!exports.options.dir && process.env.NODE_ENV !== 'test')
|
|
73
|
+
commander_1.program.help({ error: false });
|
|
74
|
+
}
|
|
75
|
+
if (process.env.NODE_ENV === 'test' || exports.options.mode === 'server')
|
|
76
|
+
exports.options.dir = '.';
|
|
31
77
|
try {
|
|
32
|
-
|
|
33
|
-
process.exit(0);
|
|
78
|
+
exports.options.dir = commander_1.program.args.length == 1 && commander_1.program.args[0] !== undefined ? resolvePath(commander_1.program.args[0]) : exports.options.dir && resolvePath(exports.options.dir);
|
|
34
79
|
}
|
|
35
80
|
catch (error) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
39
|
-
commander_1.program
|
|
40
|
-
.option("-d, --dir <directory>", "code directory to analyze. equivalent of directory argument")
|
|
41
|
-
.option("-a, --archetype <archetype>", "The archetype to use for analysis", "node-fullstack")
|
|
42
|
-
.option("-c, --configServer <configServer>", "The config server URL for fetching remote archetype configurations and rules. This takes precedence over localConfigPath.")
|
|
43
|
-
.option("-o, --openaiEnabled <boolean>", "Enable OpenAI analysis", false)
|
|
44
|
-
.option("-t, --telemetryCollector <telemetryCollector>", "The URL telemetry data will be sent to for usage analysis")
|
|
45
|
-
.option("-m, --mode <mode>", "Run mode: 'client' or 'server'", "client")
|
|
46
|
-
.option("-p, --port <port>", "The port to run the server on", "8888")
|
|
47
|
-
.option("-l, --localConfigPath <path>", "Path to local archetype config and rules", exports.DEMO_CONFIG_PATH)
|
|
48
|
-
.option("-j, --jsonTTL <minutes>", "Set the server json cache TTL in minutes", "10")
|
|
49
|
-
.option("-e, --extensions <modules...>", "Space-separated list of npm module names to load as extensions")
|
|
50
|
-
.option("-x, --examine <archetype>", "Examine the archetype configuration and rules")
|
|
51
|
-
.version(package_json_1.version, "-v, --version", "Output the version number of xfidelity")
|
|
52
|
-
.helpOption("-h, --help", "Display help for command")
|
|
53
|
-
.argument('[directory]', 'code directory to analyze');
|
|
54
|
-
const options = commander_1.program.opts();
|
|
55
|
-
exports.options = options;
|
|
56
|
-
commander_1.program.parse(process.argv);
|
|
57
|
-
// Resolve paths
|
|
58
|
-
const resolvePath = (inputPath) => {
|
|
59
|
-
const resolvedPath = path_1.default === null || path_1.default === void 0 ? void 0 : path_1.default.resolve(process.cwd(), inputPath);
|
|
60
|
-
if (!fs_1.default.existsSync(resolvedPath)) {
|
|
61
|
-
throw new Error(`Path does not exist: ${resolvedPath}`);
|
|
81
|
+
if (process.env.NODE_ENV !== 'test')
|
|
82
|
+
commander_1.program.error(`Error resolving repo path to analyse: ${error}`);
|
|
62
83
|
}
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
try {
|
|
85
|
+
if (exports.options.localConfigPath) {
|
|
86
|
+
exports.options.localConfigPath = resolvePath(exports.options.localConfigPath);
|
|
87
|
+
}
|
|
65
88
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (process.env.NODE_ENV === 'test' || options.mode === 'server')
|
|
70
|
-
options.dir = '.';
|
|
71
|
-
if (!options.dir && process.env.NODE_ENV !== 'test')
|
|
72
|
-
commander_1.program.help({ error: false });
|
|
73
|
-
}
|
|
74
|
-
if (process.env.NODE_ENV === 'test' || options.mode === 'server')
|
|
75
|
-
options.dir = '.';
|
|
76
|
-
try {
|
|
77
|
-
options.dir = commander_1.program.args.length == 1 && commander_1.program.args[0] !== undefined ? resolvePath(commander_1.program.args[0]) : options.dir && resolvePath(options.dir);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
if (process.env.NODE_ENV !== 'test')
|
|
81
|
-
commander_1.program.error(`Error resolving repo path to analyse: ${error}`);
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
if (options.localConfigPath) {
|
|
85
|
-
options.localConfigPath = resolvePath(options.localConfigPath);
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (process.env.NODE_ENV !== 'test')
|
|
91
|
+
commander_1.program.error(`LocalConfigPath does not exist or is invalid: ${error}`);
|
|
86
92
|
}
|
|
87
|
-
|
|
88
|
-
catch (error) {
|
|
89
|
-
if (process.env.NODE_ENV !== 'test')
|
|
90
|
-
commander_1.program.error(`LocalConfigPath does not exist or is invalid: ${error}`);
|
|
91
|
-
}
|
|
92
|
-
const bannerArt = `\n
|
|
93
|
+
const bannerArt = `\n
|
|
93
94
|
=====================================
|
|
94
95
|
__ __ ________ ______
|
|
95
96
|
| ## | ## | ######## \\######
|
|
@@ -101,22 +102,23 @@ const bannerArt = `\n
|
|
|
101
102
|
\\## \\## \\## \\######
|
|
102
103
|
-------------------------------------
|
|
103
104
|
`;
|
|
104
|
-
logger_1.logger.info(bannerArt);
|
|
105
|
-
logger_1.logger.info(`\n${prettyjson_1.default.render({
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
})}
|
|
105
|
+
logger_1.logger.info(bannerArt);
|
|
106
|
+
logger_1.logger.info(`\n${prettyjson_1.default.render({
|
|
107
|
+
version: package_json_1.version,
|
|
108
|
+
startTime: new Date().toString().slice(0, 24),
|
|
109
|
+
archetype: exports.options.archetype,
|
|
110
|
+
directory: exports.options.dir,
|
|
111
|
+
configServer: exports.options.configServer ? exports.options.configServer : 'none',
|
|
112
|
+
mode: exports.options.mode,
|
|
113
|
+
port: exports.options.mode === 'server' ? exports.options.port : 'n/a',
|
|
114
|
+
localConfigPath: exports.options.localConfigPath ? exports.options.localConfigPath : 'none',
|
|
115
|
+
jsonTTL: `${exports.options.jsonTTL} minutes`,
|
|
116
|
+
openaiEnabled: exports.options.openaiEnabled,
|
|
117
|
+
extensions: exports.options.extensions ? exports.options.extensions : 'none'
|
|
118
|
+
})}
|
|
118
119
|
-------------------------------------
|
|
119
|
-
`);
|
|
120
|
-
// print help if no arguments are passed
|
|
121
|
-
if (commander_1.program.options.length === 0)
|
|
122
|
-
|
|
120
|
+
`);
|
|
121
|
+
// print help if no arguments are passed
|
|
122
|
+
if (commander_1.program.options.length === 0)
|
|
123
|
+
commander_1.program.help();
|
|
124
|
+
}
|
|
@@ -70,7 +70,10 @@ class ConfigManager {
|
|
|
70
70
|
return __awaiter(this, void 0, void 0, function* () {
|
|
71
71
|
const { archetype = cli_1.options.archetype, logPrefix } = params;
|
|
72
72
|
if (!ConfigManager.configs[archetype]) {
|
|
73
|
-
ConfigManager.configs[archetype] = yield ConfigManager.initialize({ archetype, logPrefix })
|
|
73
|
+
ConfigManager.configs[archetype] = yield ConfigManager.initialize({ archetype, logPrefix }).catch(error => {
|
|
74
|
+
logger_1.logger.error(error, `Error initializing config for archetype: ${archetype}`);
|
|
75
|
+
throw error;
|
|
76
|
+
});
|
|
74
77
|
}
|
|
75
78
|
return ConfigManager.configs[archetype];
|
|
76
79
|
});
|
|
@@ -118,8 +121,7 @@ class ConfigManager {
|
|
|
118
121
|
}
|
|
119
122
|
catch (error) {
|
|
120
123
|
logger_1.logger.error(`Failed to load extension ${moduleName} from all locations: ${error}`);
|
|
121
|
-
|
|
122
|
-
process.exit(1);
|
|
124
|
+
throw new Error(`Failed to load extension ${moduleName} from all locations: ${error}`);
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
}
|
|
@@ -134,7 +136,10 @@ class ConfigManager {
|
|
|
134
136
|
if (logPrefix)
|
|
135
137
|
(0, logger_1.setLogPrefix)(logPrefix);
|
|
136
138
|
// Load plugins during initialization
|
|
137
|
-
yield this.loadPlugins(cli_1.options.extensions)
|
|
139
|
+
yield this.loadPlugins(cli_1.options.extensions).catch(error => {
|
|
140
|
+
logger_1.logger.error(error, `Error loading plugins`);
|
|
141
|
+
throw error;
|
|
142
|
+
});
|
|
138
143
|
logger_1.logger.info(`Initializing config manager for archetype: ${archetype}`);
|
|
139
144
|
logger_1.logger.debug(`Initialize params: ${JSON.stringify(params)}`);
|
|
140
145
|
const config = {
|
package/dist/index.js
CHANGED
|
@@ -51,11 +51,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
52
|
exports.main = main;
|
|
53
53
|
const logger_1 = require("./utils/logger");
|
|
54
|
-
|
|
54
|
+
const cli_1 = require("./core/cli");
|
|
55
|
+
if (require.main === module) {
|
|
56
|
+
(0, logger_1.initializeLogger)();
|
|
57
|
+
(0, logger_1.setLogLevel)(process.env.XFI_LOG_LEVEL || 'info');
|
|
58
|
+
(0, cli_1.initCLI)();
|
|
59
|
+
}
|
|
55
60
|
const executionLogPrefix = (0, logger_1.getLogPrefix)();
|
|
56
|
-
(0, logger_1.setLogLevel)(process.env.XFI_LOG_LEVEL || 'info');
|
|
57
61
|
const prettyjson_1 = __importDefault(require("prettyjson"));
|
|
58
|
-
const cli_1 = require("./core/cli");
|
|
59
62
|
const analyzer_1 = require("./core/engine/analyzer");
|
|
60
63
|
const configServer_1 = require("./server/configServer");
|
|
61
64
|
const telemetry_1 = require("./utils/telemetry");
|
|
@@ -71,7 +74,7 @@ const handleError = (error) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
71
74
|
},
|
|
72
75
|
timestamp: new Date().toISOString()
|
|
73
76
|
}, executionLogPrefix);
|
|
74
|
-
logger_1.logger.error(
|
|
77
|
+
logger_1.logger.error(error, 'Execution failure');
|
|
75
78
|
});
|
|
76
79
|
const outcomeMessage = (message) => `\n
|
|
77
80
|
==========================================================================
|
|
@@ -129,7 +132,14 @@ function main() {
|
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
catch (e) {
|
|
132
|
-
yield handleError(e)
|
|
135
|
+
yield handleError(e).then(() => {
|
|
136
|
+
// give some time async ops to finish if not handled directly
|
|
137
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}, 3000);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
133
143
|
}
|
|
134
144
|
});
|
|
135
145
|
}
|
package/dist/xfidelity
CHANGED
|
@@ -51,11 +51,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
52
|
exports.main = main;
|
|
53
53
|
const logger_1 = require("./utils/logger");
|
|
54
|
-
|
|
54
|
+
const cli_1 = require("./core/cli");
|
|
55
|
+
if (require.main === module) {
|
|
56
|
+
(0, logger_1.initializeLogger)();
|
|
57
|
+
(0, logger_1.setLogLevel)(process.env.XFI_LOG_LEVEL || 'info');
|
|
58
|
+
(0, cli_1.initCLI)();
|
|
59
|
+
}
|
|
55
60
|
const executionLogPrefix = (0, logger_1.getLogPrefix)();
|
|
56
|
-
(0, logger_1.setLogLevel)(process.env.XFI_LOG_LEVEL || 'info');
|
|
57
61
|
const prettyjson_1 = __importDefault(require("prettyjson"));
|
|
58
|
-
const cli_1 = require("./core/cli");
|
|
59
62
|
const analyzer_1 = require("./core/engine/analyzer");
|
|
60
63
|
const configServer_1 = require("./server/configServer");
|
|
61
64
|
const telemetry_1 = require("./utils/telemetry");
|
|
@@ -71,7 +74,7 @@ const handleError = (error) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
71
74
|
},
|
|
72
75
|
timestamp: new Date().toISOString()
|
|
73
76
|
}, executionLogPrefix);
|
|
74
|
-
logger_1.logger.error(
|
|
77
|
+
logger_1.logger.error(error, 'Execution failure');
|
|
75
78
|
});
|
|
76
79
|
const outcomeMessage = (message) => `\n
|
|
77
80
|
==========================================================================
|
|
@@ -129,7 +132,14 @@ function main() {
|
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
catch (e) {
|
|
132
|
-
yield handleError(e)
|
|
135
|
+
yield handleError(e).then(() => {
|
|
136
|
+
// give some time async ops to finish if not handled directly
|
|
137
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}, 3000);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
133
143
|
}
|
|
134
144
|
});
|
|
135
145
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x-fidelity",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "cli for opinionated framework adherence checks",
|
|
5
5
|
"main": "dist/index",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "rimraf dist/ && tsc && yarn copy-files",
|
|
20
|
-
"copy-files": "cp -rf src/demoConfig dist/ && cp dist/index.js dist/xfidelity",
|
|
20
|
+
"copy-files": "cp -rf src/demoConfig dist/ && cp dist/index.js dist/xfidelity && chmod 755 dist/xfidelity",
|
|
21
21
|
"start": "NODE_ENV=test OPENAI_API_KEY= jest --watch",
|
|
22
22
|
"test": "NODE_ENV=test yarn lint && NODE_ENV=test OPENAI_API_KEY= jest",
|
|
23
23
|
"test:coverage": "NODE_ENV=test OPENAI_API_KEY= jest --coverage",
|
package/src/core/cli.ts
CHANGED
|
@@ -6,84 +6,87 @@ import { version } from "../../package.json";
|
|
|
6
6
|
import { validateInput } from '../utils/inputValidation';
|
|
7
7
|
import json from 'prettyjson';
|
|
8
8
|
|
|
9
|
-
// Ensure logger is initialized
|
|
10
|
-
if (!logger || typeof logger.info !== 'function') {
|
|
11
|
-
console.error({ msg: 'Logger is not properly initialized' });
|
|
12
|
-
// Instead of exiting, we'll create a fallback logger
|
|
13
|
-
const fallbackLogger = {
|
|
14
|
-
info: (obj: unknown) => console.log(JSON.stringify(obj)),
|
|
15
|
-
error: (obj: unknown) => console.error(JSON.stringify(obj)),
|
|
16
|
-
warn: (obj: unknown) => console.warn(JSON.stringify(obj)),
|
|
17
|
-
debug: (obj: unknown) => console.debug(JSON.stringify(obj))
|
|
18
|
-
};
|
|
19
|
-
(global as any).logger = fallbackLogger;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
logger.info('CLI initialized');
|
|
23
|
-
|
|
24
9
|
export const DEMO_CONFIG_PATH = path.resolve(__dirname, '../demoConfig');
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.option("-o, --openaiEnabled <boolean>", "Enable OpenAI analysis", false)
|
|
41
|
-
.option("-t, --telemetryCollector <telemetryCollector>", "The URL telemetry data will be sent to for usage analysis")
|
|
42
|
-
.option("-m, --mode <mode>", "Run mode: 'client' or 'server'", "client")
|
|
43
|
-
.option("-p, --port <port>", "The port to run the server on", "8888")
|
|
44
|
-
.option("-l, --localConfigPath <path>", "Path to local archetype config and rules", DEMO_CONFIG_PATH)
|
|
45
|
-
.option("-j, --jsonTTL <minutes>", "Set the server json cache TTL in minutes", "10")
|
|
46
|
-
.option("-e, --extensions <modules...>", "Space-separated list of npm module names to load as extensions")
|
|
47
|
-
.option("-x, --examine <archetype>", "Examine the archetype configuration and rules")
|
|
48
|
-
.version(version, "-v, --version", "Output the version number of xfidelity")
|
|
49
|
-
.helpOption("-h, --help", "Display help for command")
|
|
50
|
-
.argument('[directory]', 'code directory to analyze');
|
|
51
|
-
|
|
52
|
-
const options = program.opts();
|
|
53
|
-
|
|
54
|
-
program.parse(process.argv);
|
|
55
|
-
|
|
56
|
-
// Resolve paths
|
|
57
|
-
const resolvePath = (inputPath: string) => {
|
|
58
|
-
const resolvedPath = path?.resolve(process.cwd(), inputPath);
|
|
59
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
60
|
-
throw new Error(`Path does not exist: ${resolvedPath}`);
|
|
10
|
+
export const options = program.opts();
|
|
11
|
+
|
|
12
|
+
export function initCLI() {
|
|
13
|
+
|
|
14
|
+
// Ensure logger is initialized
|
|
15
|
+
if (!logger || typeof logger.info !== 'function') {
|
|
16
|
+
console.error({ msg: 'Logger is not properly initialized' });
|
|
17
|
+
// Instead of exiting, we'll create a fallback logger
|
|
18
|
+
const fallbackLogger = {
|
|
19
|
+
info: (obj: unknown) => console.log(JSON.stringify(obj)),
|
|
20
|
+
error: (obj: unknown) => console.error(JSON.stringify(obj)),
|
|
21
|
+
warn: (obj: unknown) => console.warn(JSON.stringify(obj)),
|
|
22
|
+
debug: (obj: unknown) => console.debug(JSON.stringify(obj))
|
|
23
|
+
};
|
|
24
|
+
(global as any).logger = fallbackLogger;
|
|
61
25
|
}
|
|
62
|
-
|
|
63
|
-
|
|
26
|
+
|
|
27
|
+
logger.info('CLI initialized');
|
|
28
|
+
|
|
29
|
+
// we are overiding the default behavior of commander to exit
|
|
30
|
+
// the process due to https://github.com/pinojs/pino/issues/871
|
|
31
|
+
program.exitOverride(() => {
|
|
32
|
+
try {
|
|
33
|
+
if (process.env.NODE_ENV !== 'test') process.exit(0);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
//swallow
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.option("-d, --dir <directory>", "code directory to analyze. equivalent of directory argument")
|
|
41
|
+
.option("-a, --archetype <archetype>", "The archetype to use for analysis", "node-fullstack")
|
|
42
|
+
.option("-c, --configServer <configServer>", "The config server URL for fetching remote archetype configurations and rules. This takes precedence over localConfigPath.")
|
|
43
|
+
.option("-o, --openaiEnabled <boolean>", "Enable OpenAI analysis", false)
|
|
44
|
+
.option("-t, --telemetryCollector <telemetryCollector>", "The URL telemetry data will be sent to for usage analysis")
|
|
45
|
+
.option("-m, --mode <mode>", "Run mode: 'client' or 'server'", "client")
|
|
46
|
+
.option("-p, --port <port>", "The port to run the server on", "8888")
|
|
47
|
+
.option("-l, --localConfigPath <path>", "Path to local archetype config and rules", DEMO_CONFIG_PATH)
|
|
48
|
+
.option("-j, --jsonTTL <minutes>", "Set the server json cache TTL in minutes", "10")
|
|
49
|
+
.option("-e, --extensions <modules...>", "Space-separated list of npm module names to load as extensions")
|
|
50
|
+
.option("-x, --examine <archetype>", "Examine the archetype configuration and rules")
|
|
51
|
+
.version(version, "-v, --version", "Output the version number of xfidelity")
|
|
52
|
+
.helpOption("-h, --help", "Display help for command")
|
|
53
|
+
.argument('[directory]', 'code directory to analyze');
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
program.parse(process.argv);
|
|
58
|
+
|
|
59
|
+
// Resolve paths
|
|
60
|
+
const resolvePath = (inputPath: string) => {
|
|
61
|
+
const resolvedPath = path?.resolve(process.cwd(), inputPath);
|
|
62
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
63
|
+
throw new Error(`Path does not exist: ${resolvedPath}`);
|
|
64
|
+
}
|
|
65
|
+
if (!validateInput(resolvedPath)) {
|
|
66
|
+
throw new Error(`Potential malicious input detected: ${inputPath}`);
|
|
67
|
+
}
|
|
68
|
+
return resolvedPath;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (program.args.length === 0) {
|
|
72
|
+
if (process.env.NODE_ENV === 'test' || options.mode === 'server') options.dir = '.';
|
|
73
|
+
if (!options.dir && process.env.NODE_ENV !== 'test') program.help({ error: false });
|
|
64
74
|
}
|
|
65
|
-
return resolvedPath;
|
|
66
|
-
};
|
|
67
75
|
|
|
68
|
-
if (program.args.length === 0) {
|
|
69
76
|
if (process.env.NODE_ENV === 'test' || options.mode === 'server') options.dir = '.';
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (process.env.NODE_ENV
|
|
74
|
-
|
|
75
|
-
options.dir = program.args.length == 1 && program.args[0] !== undefined ? resolvePath(program.args[0]) : options.dir && resolvePath(options.dir);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
if (process.env.NODE_ENV !== 'test') program.error(`Error resolving repo path to analyse: ${error}`)
|
|
78
|
-
}
|
|
77
|
+
try {
|
|
78
|
+
options.dir = program.args.length == 1 && program.args[0] !== undefined ? resolvePath(program.args[0]) : options.dir && resolvePath(options.dir);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (process.env.NODE_ENV !== 'test') program.error(`Error resolving repo path to analyse: ${error}`)
|
|
81
|
+
}
|
|
79
82
|
|
|
80
|
-
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
try {
|
|
84
|
+
if (options.localConfigPath) {
|
|
85
|
+
options.localConfigPath = resolvePath(options.localConfigPath);
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (process.env.NODE_ENV !== 'test') program.error(`LocalConfigPath does not exist or is invalid: ${error}`);
|
|
83
89
|
}
|
|
84
|
-
} catch (error) {
|
|
85
|
-
if (process.env.NODE_ENV !== 'test') program.error(`LocalConfigPath does not exist or is invalid: ${error}`);
|
|
86
|
-
}
|
|
87
90
|
|
|
88
91
|
const bannerArt = `\n
|
|
89
92
|
=====================================
|
|
@@ -98,25 +101,25 @@ const bannerArt = `\n
|
|
|
98
101
|
-------------------------------------
|
|
99
102
|
`;
|
|
100
103
|
|
|
101
|
-
logger.info(bannerArt);
|
|
102
|
-
|
|
103
|
-
logger.info(`\n${json.render({
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
})}
|
|
104
|
+
logger.info(bannerArt);
|
|
105
|
+
|
|
106
|
+
logger.info(`\n${json.render({
|
|
107
|
+
version,
|
|
108
|
+
startTime: new Date().toString().slice(0, 24),
|
|
109
|
+
archetype: options.archetype,
|
|
110
|
+
directory: options.dir,
|
|
111
|
+
configServer: options.configServer ? options.configServer : 'none',
|
|
112
|
+
mode: options.mode,
|
|
113
|
+
port: options.mode === 'server' ? options.port : 'n/a',
|
|
114
|
+
localConfigPath: options.localConfigPath ? options.localConfigPath : 'none',
|
|
115
|
+
jsonTTL: `${options.jsonTTL} minutes`,
|
|
116
|
+
openaiEnabled: options.openaiEnabled,
|
|
117
|
+
extensions: options.extensions ? options.extensions : 'none'
|
|
118
|
+
})}
|
|
116
119
|
-------------------------------------
|
|
117
|
-
`);
|
|
120
|
+
`);
|
|
118
121
|
|
|
119
|
-
// print help if no arguments are passed
|
|
120
|
-
if (program.options.length === 0) program.help();
|
|
122
|
+
// print help if no arguments are passed
|
|
123
|
+
if (program.options.length === 0) program.help();
|
|
124
|
+
}
|
|
121
125
|
|
|
122
|
-
export { options };
|
|
@@ -30,7 +30,10 @@ export class ConfigManager {
|
|
|
30
30
|
public static async getConfig(params: GetConfigParams): Promise<ExecutionConfig> {
|
|
31
31
|
const { archetype = options.archetype, logPrefix } = params;
|
|
32
32
|
if (!ConfigManager.configs[archetype]) {
|
|
33
|
-
ConfigManager.configs[archetype] = await ConfigManager.initialize({ archetype, logPrefix })
|
|
33
|
+
ConfigManager.configs[archetype] = await ConfigManager.initialize({ archetype, logPrefix }).catch(error => {
|
|
34
|
+
logger.error(error, `Error initializing config for archetype: ${archetype}`);
|
|
35
|
+
throw error;
|
|
36
|
+
});
|
|
34
37
|
}
|
|
35
38
|
return ConfigManager.configs[archetype];
|
|
36
39
|
}
|
|
@@ -76,7 +79,7 @@ export class ConfigManager {
|
|
|
76
79
|
logger.info(`Successfully loaded extension: ${moduleName}`);
|
|
77
80
|
} catch (error) {
|
|
78
81
|
logger.error(`Failed to load extension ${moduleName} from all locations: ${error}`);
|
|
79
|
-
|
|
82
|
+
throw new Error(`Failed to load extension ${moduleName} from all locations: ${error}`);
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
}
|
|
@@ -90,7 +93,10 @@ export class ConfigManager {
|
|
|
90
93
|
if (logPrefix) setLogPrefix(logPrefix);
|
|
91
94
|
|
|
92
95
|
// Load plugins during initialization
|
|
93
|
-
await this.loadPlugins(options.extensions)
|
|
96
|
+
await this.loadPlugins(options.extensions).catch(error => {
|
|
97
|
+
logger.error(error, `Error loading plugins`);
|
|
98
|
+
throw error;
|
|
99
|
+
});
|
|
94
100
|
logger.info(`Initializing config manager for archetype: ${archetype}`);
|
|
95
101
|
logger.debug(`Initialize params: ${JSON.stringify(params)}`);
|
|
96
102
|
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { logger, initializeLogger, getLogPrefix, setLogLevel } from './utils/logger';
|
|
3
|
-
|
|
3
|
+
import { options, initCLI } from "./core/cli";
|
|
4
|
+
if (require.main === module) {
|
|
5
|
+
initializeLogger()
|
|
6
|
+
setLogLevel(process.env.XFI_LOG_LEVEL || 'info');
|
|
7
|
+
initCLI();
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
const executionLogPrefix = getLogPrefix();
|
|
5
|
-
setLogLevel(process.env.XFI_LOG_LEVEL || 'info');
|
|
6
11
|
|
|
7
12
|
import json from 'prettyjson';
|
|
8
|
-
import { options } from "./core/cli";
|
|
9
13
|
import { analyzeCodebase } from "./core/engine/analyzer";
|
|
10
14
|
import { startServer } from './server/configServer';
|
|
11
15
|
import { sendTelemetry } from './utils/telemetry';
|
|
@@ -23,7 +27,7 @@ const handleError = async (error: Error) => {
|
|
|
23
27
|
},
|
|
24
28
|
timestamp: new Date().toISOString()
|
|
25
29
|
}, executionLogPrefix);
|
|
26
|
-
logger.error(
|
|
30
|
+
logger.error(error, 'Execution failure');
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
const outcomeMessage = (message: string) => `\n
|
|
@@ -81,8 +85,14 @@ export async function main() {
|
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
} catch (e: any) {
|
|
84
|
-
await handleError(e)
|
|
85
|
-
|
|
88
|
+
await handleError(e).then(() => {
|
|
89
|
+
// give some time async ops to finish if not handled directly
|
|
90
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}, 3000);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
86
96
|
}
|
|
87
97
|
}
|
|
88
98
|
|