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 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
 
@@ -1,3 +1,3 @@
1
1
  export declare const DEMO_CONFIG_PATH: string;
2
- declare const options: import("commander").OptionValues;
3
- export { options };
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
- // we are overiding the default behavior of commander to exit
29
- // the process due to https://github.com/pinojs/pino/issues/871
30
- commander_1.program.exitOverride(() => {
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
- if (process.env.NODE_ENV !== 'test')
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
- //swallow
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
- if (!(0, inputValidation_1.validateInput)(resolvedPath)) {
64
- throw new Error(`Potential malicious input detected: ${inputPath}`);
84
+ try {
85
+ if (exports.options.localConfigPath) {
86
+ exports.options.localConfigPath = resolvePath(exports.options.localConfigPath);
87
+ }
65
88
  }
66
- return resolvedPath;
67
- };
68
- if (commander_1.program.args.length === 0) {
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
- version: package_json_1.version,
107
- startTime: new Date().toString().slice(0, 24),
108
- archetype: options.archetype,
109
- directory: options.dir,
110
- configServer: options.configServer ? options.configServer : 'none',
111
- mode: options.mode,
112
- port: options.mode === 'server' ? options.port : 'n/a',
113
- localConfigPath: options.localConfigPath ? options.localConfigPath : 'none',
114
- jsonTTL: `${options.jsonTTL} minutes`,
115
- openaiEnabled: options.openaiEnabled,
116
- extensions: options.extensions ? options.extensions : 'none'
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
- commander_1.program.help();
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
- if (process.env.NODE_ENV !== 'test')
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
- (0, logger_1.initializeLogger)();
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(JSON.stringify(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
- (0, logger_1.initializeLogger)();
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(JSON.stringify(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.2",
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
- // we are overiding the default behavior of commander to exit
27
- // the process due to https://github.com/pinojs/pino/issues/871
28
- program.exitOverride(() => {
29
- try {
30
- if (process.env.NODE_ENV !== 'test') process.exit(0);
31
- } catch (error) {
32
- //swallow
33
- }
34
- });
35
-
36
- program
37
- .option("-d, --dir <directory>", "code directory to analyze. equivalent of directory argument")
38
- .option("-a, --archetype <archetype>", "The archetype to use for analysis", "node-fullstack")
39
- .option("-c, --configServer <configServer>", "The config server URL for fetching remote archetype configurations and rules. This takes precedence over localConfigPath.")
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
- if (!validateInput(resolvedPath)) {
63
- throw new Error(`Potential malicious input detected: ${inputPath}`);
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
- if (!options.dir && process.env.NODE_ENV !== 'test') program.help({ error: false });
71
- }
72
-
73
- if (process.env.NODE_ENV === 'test' || options.mode === 'server') options.dir = '.';
74
- try {
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
- if (options.localConfigPath) {
82
- options.localConfigPath = resolvePath(options.localConfigPath);
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
- version,
105
- startTime: new Date().toString().slice(0, 24),
106
- archetype: options.archetype,
107
- directory: options.dir,
108
- configServer: options.configServer ? options.configServer : 'none',
109
- mode: options.mode,
110
- port: options.mode === 'server' ? options.port : 'n/a',
111
- localConfigPath: options.localConfigPath ? options.localConfigPath : 'none',
112
- jsonTTL: `${options.jsonTTL} minutes`,
113
- openaiEnabled: options.openaiEnabled,
114
- extensions: options.extensions ? options.extensions : 'none'
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
- if (process.env.NODE_ENV !== 'test') process.exit(1);
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
- initializeLogger()
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(JSON.stringify(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