@contentstack/cli-utilities 1.11.0 → 1.12.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.
@@ -49,13 +49,6 @@ class AuthenticationHandler {
49
49
  }
50
50
  async refreshAccessToken(error, maxRetryCount = 1) {
51
51
  if (error.response && error.response.status) {
52
- if (maxRetryCount >= 3) {
53
- index_1.cliux.print('Max retry count reached, please login to proceed', {
54
- color: 'yellow',
55
- });
56
- process.exit(1);
57
- }
58
- maxRetryCount++; // Increment for the next retry attempt
59
52
  switch (error.response.status) {
60
53
  case 401:
61
54
  // NOTE: Refresh the token if the type is OAuth.
@@ -68,11 +61,24 @@ class AuthenticationHandler {
68
61
  hostName = u.host;
69
62
  }
70
63
  hostName = hostName || region.cma;
71
- await this.refreshToken(hostName);
72
- return this.refreshAccessToken(error, maxRetryCount); // Retry after refreshing the token
64
+ const refreshed = await this.refreshToken(hostName);
65
+ if (refreshed) {
66
+ return this.refreshAccessToken(error, maxRetryCount); // Retry after refreshing the token
67
+ }
68
+ console.log('API(401) case error:-', error.response);
69
+ // For Basic Auth, exit immediately without retrying
70
+ return;
73
71
  }
72
+ break;
74
73
  case 429:
75
74
  case 408:
75
+ if (maxRetryCount >= 3) {
76
+ index_1.cliux.print('Max retry count reached, please login to proceed', {
77
+ color: 'yellow',
78
+ });
79
+ process.exit(1);
80
+ }
81
+ maxRetryCount++; // Increment for the next retry attempt
76
82
  // These cases require a wait, adding a delay before retrying
77
83
  await new Promise((resolve) => setTimeout(resolve, 1000)); // wait for 1 second
78
84
  return this.refreshAccessToken(error, maxRetryCount); // Retry
@@ -84,8 +90,6 @@ class AuthenticationHandler {
84
90
  refreshToken(hostName) {
85
91
  return new Promise((resolve) => {
86
92
  if (this.authType === 'BASIC') {
87
- // NOTE Handle basic auth 401 here
88
- resolve(false);
89
93
  index_1.cliux.print('Session timed out, please login to proceed', {
90
94
  color: 'yellow',
91
95
  });
@@ -106,7 +110,6 @@ class AuthenticationHandler {
106
110
  });
107
111
  }
108
112
  else {
109
- resolve(false);
110
113
  index_1.cliux.print('You do not have the permissions to perform this action, please login to proceed', {
111
114
  color: 'yellow',
112
115
  });
@@ -5,6 +5,8 @@ declare class Config {
5
5
  init(): Conf<Record<string, unknown>>;
6
6
  importOldConfig(): void;
7
7
  setOldConfigStoreData(data: any, _path?: string): void;
8
+ isConfigFileValid(configPath: string): boolean;
9
+ safeDeleteConfigIfInvalid(configFilePath: string): void;
8
10
  removeOldConfigStoreFile(): void;
9
11
  private getOldConfig;
10
12
  private fallbackInit;
@@ -56,6 +56,22 @@ class Config {
56
56
  }
57
57
  }
58
58
  }
59
+ isConfigFileValid(configPath) {
60
+ try {
61
+ const content = (0, fs_1.readFileSync)(configPath, 'utf8');
62
+ JSON.parse(content);
63
+ return true;
64
+ }
65
+ catch (e) {
66
+ return false;
67
+ }
68
+ }
69
+ safeDeleteConfigIfInvalid(configFilePath) {
70
+ if ((0, fs_1.existsSync)(configFilePath) && !this.isConfigFileValid(configFilePath)) {
71
+ console.warn(chalk_1.default.yellow(`Warning: Detected corrupted config at ${configFilePath}. Removing...`));
72
+ (0, fs_1.unlinkSync)(configFilePath);
73
+ }
74
+ }
59
75
  removeOldConfigStoreFile() {
60
76
  if ((0, fs_1.existsSync)(oldConfigPath)) {
61
77
  (0, fs_1.unlinkSync)(oldConfigPath); // NOTE remove old configstore file
@@ -98,6 +114,7 @@ class Config {
98
114
  try {
99
115
  // NOTE reading current code base encrypted file if exist
100
116
  const encryptionKey = this.getObfuscationKey();
117
+ this.safeDeleteConfigIfInvalid(oldConfigPath);
101
118
  this.config = new conf_1.default({ configName: CONFIG_NAME, encryptionKey, cwd });
102
119
  if ((_a = Object.keys(configData || {})) === null || _a === void 0 ? void 0 : _a.length) {
103
120
  this.config.set(configData); // NOTE set config data if passed any
@@ -119,6 +136,7 @@ class Config {
119
136
  };
120
137
  try {
121
138
  if (skip === false) {
139
+ this.safeDeleteConfigIfInvalid(oldConfigPath);
122
140
  const config = new conf_1.default({ configName: CONFIG_NAME });
123
141
  const oldConfigData = this.getConfigDataAndUnlinkConfigFile(config);
124
142
  this.getEncryptedConfig(oldConfigData, true);
@@ -137,6 +155,7 @@ class Config {
137
155
  getDecryptedConfig(configData) {
138
156
  var _a;
139
157
  try {
158
+ this.safeDeleteConfigIfInvalid(oldConfigPath);
140
159
  this.config = new conf_1.default({ configName: CONFIG_NAME, cwd });
141
160
  if ((_a = Object.keys(configData || {})) === null || _a === void 0 ? void 0 : _a.length) {
142
161
  this.config.set(configData); // NOTE set config data if passed any
@@ -146,6 +165,7 @@ class Config {
146
165
  // console.trace(error.message)
147
166
  try {
148
167
  const encryptionKey = this.getObfuscationKey();
168
+ this.safeDeleteConfigIfInvalid(oldConfigPath);
149
169
  let config = new conf_1.default({ configName: CONFIG_NAME, encryptionKey, cwd });
150
170
  const oldConfigData = this.getConfigDataAndUnlinkConfigFile(config);
151
171
  this.getDecryptedConfig(oldConfigData); // NOTE NOTE reinitialize the config with old data and new decrypted file
@@ -0,0 +1,10 @@
1
+ export declare const ERROR_TYPES: {
2
+ readonly NETWORK: "NETWORK_ERROR";
3
+ readonly DATABASE: "DATABASE_ERROR";
4
+ readonly APPLICATION: "APPLICATION_ERROR";
5
+ readonly UNKNOWN: "UNKNOWN_ERROR";
6
+ readonly NORMALIZATION: "NORMALIZATION_ERROR";
7
+ readonly API_RESPONSE: "API_RESPONSE_DATA";
8
+ readonly API_ERROR: "API_ERROR";
9
+ readonly SERVER_ERROR: "SERVER_ERROR";
10
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ERROR_TYPES = void 0;
4
+ exports.ERROR_TYPES = {
5
+ NETWORK: 'NETWORK_ERROR',
6
+ DATABASE: 'DATABASE_ERROR',
7
+ APPLICATION: 'APPLICATION_ERROR',
8
+ UNKNOWN: 'UNKNOWN_ERROR',
9
+ NORMALIZATION: 'NORMALIZATION_ERROR',
10
+ API_RESPONSE: 'API_RESPONSE_DATA',
11
+ API_ERROR: 'API_ERROR',
12
+ SERVER_ERROR: 'SERVER_ERROR',
13
+ };
@@ -0,0 +1,15 @@
1
+ export declare const logLevels: {
2
+ readonly error: 0;
3
+ readonly warn: 1;
4
+ readonly info: 2;
5
+ readonly success: 2;
6
+ readonly debug: 3;
7
+ readonly verbose: 4;
8
+ };
9
+ export declare const levelColors: {
10
+ error: string;
11
+ warn: string;
12
+ success: string;
13
+ info: string;
14
+ debug: string;
15
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.levelColors = exports.logLevels = void 0;
4
+ exports.logLevels = {
5
+ error: 0,
6
+ warn: 1,
7
+ info: 2,
8
+ success: 2,
9
+ debug: 3,
10
+ verbose: 4
11
+ };
12
+ // 2. Create color mappings (for console only)
13
+ exports.levelColors = {
14
+ error: 'red',
15
+ warn: 'yellow',
16
+ success: 'green',
17
+ info: 'blue',
18
+ debug: 'white'
19
+ };
@@ -1,3 +1,4 @@
1
+ import { logLevels } from "../constants/logging";
1
2
  export interface IPromptOptions {
2
3
  prompt?: string;
3
4
  type?: 'normal' | 'mask' | 'hide' | 'single';
@@ -62,3 +63,37 @@ export interface Locale {
62
63
  }
63
64
  export interface CliUXPromptOptions extends IPromptOptions {
64
65
  }
66
+ export interface LoggerConfig {
67
+ basePath: string;
68
+ processName?: string;
69
+ consoleLoggingEnabled?: boolean;
70
+ consoleLogLevel?: LogType;
71
+ logLevel?: LogType;
72
+ }
73
+ export interface PrintOptions {
74
+ bold?: boolean;
75
+ color?: string;
76
+ }
77
+ export type LogType = 'info' | 'warn' | 'error' | 'debug';
78
+ export type LogsType = LogType | PrintOptions | undefined;
79
+ export type MessageType = string | Error | Record<string, any> | Record<string, any>[];
80
+ export type LogLevel = keyof typeof logLevels;
81
+ export type ClassifiedError = {
82
+ type: string;
83
+ message: string;
84
+ error: Record<string, any>;
85
+ debug?: Record<string, any>;
86
+ meta?: Record<string, string | undefined>;
87
+ context?: string;
88
+ hidden?: boolean;
89
+ };
90
+ export type ErrorContext = {
91
+ operation?: string;
92
+ component?: string;
93
+ userId?: string;
94
+ requestId?: string;
95
+ email?: string;
96
+ sessionId?: string;
97
+ orgId?: string;
98
+ apiKey?: string;
99
+ };
@@ -0,0 +1,64 @@
1
+ import { ClassifiedError, ErrorContext } from '../interfaces';
2
+ /**
3
+ * Handles errors in a CLI application by classifying, normalizing, and extracting
4
+ * relevant information for debugging and logging purposes.
5
+ *
6
+ * This class provides methods to:
7
+ * - Normalize unknown error types into standard `Error` objects.
8
+ * - Classify errors into predefined categories such as API errors, network errors,
9
+ * server errors, and more.
10
+ * - Extract detailed error payloads for logging, including HTTP request and response
11
+ * details when applicable.
12
+ * - Identify sensitive information in error messages to prevent accidental exposure.
13
+ * - Generate debug payloads for enhanced troubleshooting when debugging is enabled.
14
+ *
15
+ * @remarks
16
+ * This class is designed to handle a wide range of error types, including generic
17
+ * JavaScript errors, API errors, and custom error objects. It also supports
18
+ * optional debugging and context metadata for enhanced error reporting.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const errorHandler = new CLIErrorHandler(true);
23
+ *
24
+ * try {
25
+ * // Some operation that may throw an error
26
+ * } catch (error) {
27
+ * const classifiedError = errorHandler.classifyError(error, {
28
+ * operation: 'fetchData',
29
+ * component: 'DataService',
30
+ * });
31
+ * console.error(classifiedError);
32
+ * }
33
+ * ```
34
+ *
35
+ * @public
36
+ */
37
+ export default class CLIErrorHandler {
38
+ private isDebug;
39
+ constructor(isDebug?: boolean);
40
+ /**
41
+ * Classifies an error into a structured format for better handling and debugging.
42
+ *
43
+ * @param error - The error object to classify. Can be of any type.
44
+ * @param context - Optional additional context about the error, typically used to provide
45
+ * more information about where or why the error occurred.
46
+ *
47
+ * @returns A `ClassifiedError` object containing details about the error, including its type,
48
+ * message, payload, context, metadata, and whether it contains sensitive information.
49
+ * If the error is an API error or debugging is enabled, additional debug information
50
+ * is included.
51
+ *
52
+ * @throws This method handles its own errors and will return a `ClassifiedError` with type
53
+ * `ERROR_TYPES.NORMALIZATION` if it fails to normalize or classify the input error.
54
+ */
55
+ classifyError(error: unknown, context?: ErrorContext): ClassifiedError;
56
+ private normalizeToError;
57
+ private isApiError;
58
+ private determineErrorType;
59
+ private extractErrorPayload;
60
+ private extractDebugPayload;
61
+ private extractMeta;
62
+ private containsSensitiveInfo;
63
+ }
64
+ export { CLIErrorHandler };
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CLIErrorHandler = void 0;
4
+ const helpers_1 = require("../helpers");
5
+ const errorTypes_1 = require("../constants/errorTypes");
6
+ /**
7
+ * Handles errors in a CLI application by classifying, normalizing, and extracting
8
+ * relevant information for debugging and logging purposes.
9
+ *
10
+ * This class provides methods to:
11
+ * - Normalize unknown error types into standard `Error` objects.
12
+ * - Classify errors into predefined categories such as API errors, network errors,
13
+ * server errors, and more.
14
+ * - Extract detailed error payloads for logging, including HTTP request and response
15
+ * details when applicable.
16
+ * - Identify sensitive information in error messages to prevent accidental exposure.
17
+ * - Generate debug payloads for enhanced troubleshooting when debugging is enabled.
18
+ *
19
+ * @remarks
20
+ * This class is designed to handle a wide range of error types, including generic
21
+ * JavaScript errors, API errors, and custom error objects. It also supports
22
+ * optional debugging and context metadata for enhanced error reporting.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const errorHandler = new CLIErrorHandler(true);
27
+ *
28
+ * try {
29
+ * // Some operation that may throw an error
30
+ * } catch (error) {
31
+ * const classifiedError = errorHandler.classifyError(error, {
32
+ * operation: 'fetchData',
33
+ * component: 'DataService',
34
+ * });
35
+ * console.error(classifiedError);
36
+ * }
37
+ * ```
38
+ *
39
+ * @public
40
+ */
41
+ class CLIErrorHandler {
42
+ constructor(isDebug = false) {
43
+ this.isDebug = isDebug;
44
+ }
45
+ /**
46
+ * Classifies an error into a structured format for better handling and debugging.
47
+ *
48
+ * @param error - The error object to classify. Can be of any type.
49
+ * @param context - Optional additional context about the error, typically used to provide
50
+ * more information about where or why the error occurred.
51
+ *
52
+ * @returns A `ClassifiedError` object containing details about the error, including its type,
53
+ * message, payload, context, metadata, and whether it contains sensitive information.
54
+ * If the error is an API error or debugging is enabled, additional debug information
55
+ * is included.
56
+ *
57
+ * @throws This method handles its own errors and will return a `ClassifiedError` with type
58
+ * `ERROR_TYPES.NORMALIZATION` if it fails to normalize or classify the input error.
59
+ */
60
+ classifyError(error, context) {
61
+ try {
62
+ const normalized = this.normalizeToError(error);
63
+ const isApi = this.isApiError(normalized);
64
+ const type = this.determineErrorType(normalized);
65
+ const hidden = this.containsSensitiveInfo(normalized);
66
+ const result = {
67
+ type,
68
+ message: normalized.message || 'Unhandled error',
69
+ error: this.extractErrorPayload(normalized),
70
+ context: context ? JSON.stringify(context) : undefined,
71
+ meta: this.extractMeta(context),
72
+ hidden,
73
+ };
74
+ if (isApi || this.isDebug) {
75
+ result.debug = this.extractDebugPayload(normalized, context);
76
+ }
77
+ return result;
78
+ }
79
+ catch (e) {
80
+ return {
81
+ type: errorTypes_1.ERROR_TYPES.NORMALIZATION,
82
+ message: 'Failed to normalize or classify error',
83
+ error: { message: String(e) },
84
+ context: context ? JSON.stringify(context) : undefined,
85
+ meta: this.extractMeta(context),
86
+ hidden: false,
87
+ };
88
+ }
89
+ }
90
+ normalizeToError(error) {
91
+ if (!error)
92
+ return new Error('Unknown error occurred');
93
+ if (error instanceof Error)
94
+ return error;
95
+ if (typeof error === 'string')
96
+ return new Error(error);
97
+ if (typeof error === 'object') {
98
+ try {
99
+ const msg = error.message;
100
+ const err = new Error(msg || 'Unknown error');
101
+ Object.assign(err, error);
102
+ return err;
103
+ }
104
+ catch (_a) {
105
+ return new Error(JSON.stringify(error));
106
+ }
107
+ }
108
+ return new Error(String(error));
109
+ }
110
+ isApiError(error) {
111
+ return (error.isAxiosError ||
112
+ typeof error.status === 'number' ||
113
+ typeof error.statusText === 'string' ||
114
+ error.request !== undefined);
115
+ }
116
+ determineErrorType(error) {
117
+ var _a;
118
+ const status = error.status || ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status);
119
+ //Ignore 4XX errors
120
+ if (status >= 400 && status < 500) {
121
+ return errorTypes_1.ERROR_TYPES.API_ERROR;
122
+ }
123
+ //Server-side HTTP errors
124
+ if (status >= 500) {
125
+ return errorTypes_1.ERROR_TYPES.SERVER_ERROR;
126
+ }
127
+ //Network-related error
128
+ if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND' || error.code === 'ETIMEDOUT') {
129
+ return errorTypes_1.ERROR_TYPES.NETWORK;
130
+ }
131
+ //Database error
132
+ if (error.name === 'DatabaseError') {
133
+ return errorTypes_1.ERROR_TYPES.DATABASE;
134
+ }
135
+ //Axios errors without 4XX
136
+ if (error.isAxiosError) {
137
+ return errorTypes_1.ERROR_TYPES.NETWORK;
138
+ }
139
+ //Default
140
+ return errorTypes_1.ERROR_TYPES.APPLICATION;
141
+ }
142
+ extractErrorPayload(error) {
143
+ var _a, _b, _c, _d, _e, _f;
144
+ const code = error.code || error.errorCode;
145
+ const status = error.status || ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status);
146
+ const statusText = error.statusText || ((_b = error.response) === null || _b === void 0 ? void 0 : _b.statusText);
147
+ const method = ((_c = error.request) === null || _c === void 0 ? void 0 : _c.method) || ((_d = error.config) === null || _d === void 0 ? void 0 : _d.method) || 'UNKNOWN';
148
+ const url = ((_e = error.request) === null || _e === void 0 ? void 0 : _e.url) || ((_f = error.config) === null || _f === void 0 ? void 0 : _f.url);
149
+ const endpoint = url ? new URL(url, 'http://dummy').pathname : 'UNKNOWN';
150
+ const payload = {
151
+ name: error.name,
152
+ message: (0, helpers_1.formatError)(error),
153
+ code,
154
+ status,
155
+ statusText,
156
+ method,
157
+ endpoint,
158
+ };
159
+ if (this.isDebug) {
160
+ payload.stack = error.stack;
161
+ }
162
+ return payload;
163
+ }
164
+ extractDebugPayload(error, context) {
165
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
166
+ const method = ((_a = error.request) === null || _a === void 0 ? void 0 : _a.method) || ((_b = error.config) === null || _b === void 0 ? void 0 : _b.method);
167
+ const url = ((_c = error.request) === null || _c === void 0 ? void 0 : _c.url) || ((_d = error.config) === null || _d === void 0 ? void 0 : _d.url);
168
+ const status = error.status || ((_e = error.response) === null || _e === void 0 ? void 0 : _e.status);
169
+ const statusText = error.statusText || ((_f = error.response) === null || _f === void 0 ? void 0 : _f.statusText);
170
+ const data = error.data || ((_g = error.response) === null || _g === void 0 ? void 0 : _g.data) || error.errors || error.error;
171
+ return {
172
+ command: context === null || context === void 0 ? void 0 : context.operation,
173
+ module: context === null || context === void 0 ? void 0 : context.component,
174
+ request: {
175
+ method,
176
+ url,
177
+ headers: (_h = error.request) === null || _h === void 0 ? void 0 : _h.headers,
178
+ data: (_j = error.request) === null || _j === void 0 ? void 0 : _j.data,
179
+ },
180
+ response: {
181
+ status,
182
+ statusText,
183
+ data,
184
+ },
185
+ };
186
+ }
187
+ extractMeta(context) {
188
+ return {
189
+ email: context === null || context === void 0 ? void 0 : context.email,
190
+ sessionId: context === null || context === void 0 ? void 0 : context.sessionId,
191
+ userId: context === null || context === void 0 ? void 0 : context.userId,
192
+ apiKey: context === null || context === void 0 ? void 0 : context.apiKey,
193
+ orgId: context === null || context === void 0 ? void 0 : context.orgId,
194
+ };
195
+ }
196
+ containsSensitiveInfo(error) {
197
+ try {
198
+ const content = `${error.message} ${error.stack || ''}`.toLowerCase();
199
+ return [
200
+ 'password',
201
+ 'token',
202
+ 'secret',
203
+ 'credentials',
204
+ 'api_key',
205
+ 'api-key',
206
+ 'authorization',
207
+ 'sessionid',
208
+ 'email',
209
+ ].some((term) => content.includes(term));
210
+ }
211
+ catch (_a) {
212
+ return false;
213
+ }
214
+ }
215
+ }
216
+ exports.default = CLIErrorHandler;
217
+ exports.CLIErrorHandler = CLIErrorHandler;
@@ -0,0 +1,23 @@
1
+ import { default as Logger } from './logger';
2
+ import { CLIErrorHandler } from './cliErrorHandler';
3
+ import { ErrorContext } from '../interfaces';
4
+ declare const v2Logger: Logger;
5
+ declare const cliErrorHandler: CLIErrorHandler;
6
+ /**
7
+ * Handles and logs an error by classifying it and logging the relevant details.
8
+ *
9
+ * This function uses the `cliErrorHandler` to classify the provided error and logs
10
+ * the error details using `v2Logger`. If debug information is available, it logs
11
+ * additional debug details, including a stack trace if not already present.
12
+ *
13
+ * @param error - The error to be handled and logged. Can be of any type.
14
+ * @param context - Optional context information to assist in error classification
15
+ * and logging.
16
+ *
17
+ * @remarks
18
+ * - The error is always logged with its type, message, and other metadata.
19
+ * - If debug information is available, it is logged separately with a more specific
20
+ * debug type and additional details.
21
+ */
22
+ declare function handleAndLogError(error: unknown, context?: ErrorContext): void;
23
+ export { v2Logger, cliErrorHandler, handleAndLogError };
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleAndLogError = exports.cliErrorHandler = exports.v2Logger = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path = tslib_1.__importStar(require("path"));
6
+ const logger_1 = tslib_1.__importDefault(require("./logger"));
7
+ const cliErrorHandler_1 = require("./cliErrorHandler");
8
+ const v2Logger = new logger_1.default({ basePath: process.env.CS_CLI_LOG_PATH || path.join(process.cwd(), 'logs') });
9
+ exports.v2Logger = v2Logger;
10
+ const cliErrorHandler = new cliErrorHandler_1.CLIErrorHandler(true); // Enable debug mode for error classification
11
+ exports.cliErrorHandler = cliErrorHandler;
12
+ /**
13
+ * Handles and logs an error by classifying it and logging the relevant details.
14
+ *
15
+ * This function uses the `cliErrorHandler` to classify the provided error and logs
16
+ * the error details using `v2Logger`. If debug information is available, it logs
17
+ * additional debug details, including a stack trace if not already present.
18
+ *
19
+ * @param error - The error to be handled and logged. Can be of any type.
20
+ * @param context - Optional context information to assist in error classification
21
+ * and logging.
22
+ *
23
+ * @remarks
24
+ * - The error is always logged with its type, message, and other metadata.
25
+ * - If debug information is available, it is logged separately with a more specific
26
+ * debug type and additional details.
27
+ */
28
+ function handleAndLogError(error, context) {
29
+ const classified = cliErrorHandler.classifyError(error, context);
30
+ // Always log the error
31
+ v2Logger.logError({
32
+ type: classified.type,
33
+ message: classified.message,
34
+ error: classified.error,
35
+ context: classified.context,
36
+ hidden: classified.hidden,
37
+ meta: classified.meta,
38
+ });
39
+ // Log debug information if available
40
+ if (classified.debug) {
41
+ v2Logger.logDebug({
42
+ type: `${classified.type}_DEBUG`,
43
+ message: `${classified.message} [DEBUG]`,
44
+ debug: Object.assign(Object.assign({}, classified.debug), {
45
+ // Ensure stack trace is included if not already there
46
+ stackTrace: classified.debug.stackTrace || classified.error.stack }),
47
+ context: classified.context,
48
+ meta: classified.meta,
49
+ });
50
+ }
51
+ }
52
+ exports.handleAndLogError = handleAndLogError;
@@ -0,0 +1,57 @@
1
+ import * as winston from 'winston';
2
+ import { LoggerConfig } from '../interfaces/index';
3
+ export default class Logger {
4
+ private loggers;
5
+ private config;
6
+ private sensitiveKeys;
7
+ constructor(config: LoggerConfig);
8
+ getLoggerInstance(level?: 'error' | 'info' | 'warn' | 'debug' | 'hidden'): winston.Logger;
9
+ private get loggerOptions();
10
+ private createLogger;
11
+ private isSensitiveKey;
12
+ private redactObject;
13
+ private redact;
14
+ private isLogEntry;
15
+ private shouldLog;
16
+ error(message: string, meta?: any): void;
17
+ warn(message: string, meta?: any): void;
18
+ info(message: string, meta?: any): void;
19
+ success(message: string, meta?: any): void;
20
+ debug(message: string, meta?: any): void;
21
+ logError(params: {
22
+ type: string;
23
+ message: string;
24
+ error: any;
25
+ context?: string;
26
+ hidden?: boolean;
27
+ meta?: Record<string, any>;
28
+ }): void;
29
+ logWarn(params: {
30
+ type: string;
31
+ message: string;
32
+ warn?: any;
33
+ context?: string;
34
+ meta?: Record<string, any>;
35
+ }): void;
36
+ logInfo(params: {
37
+ type: string;
38
+ message: string;
39
+ info?: any;
40
+ context?: string;
41
+ meta?: Record<string, any>;
42
+ }): void;
43
+ logSuccess(params: {
44
+ type: string;
45
+ message: string;
46
+ data?: any;
47
+ context?: string;
48
+ meta?: Record<string, any>;
49
+ }): void;
50
+ logDebug(params: {
51
+ type: string;
52
+ message: string;
53
+ debug?: any;
54
+ context?: string;
55
+ meta?: Record<string, any>;
56
+ }): void;
57
+ }
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const traverse_1 = tslib_1.__importDefault(require("traverse"));
5
+ const full_1 = require("klona/full");
6
+ const path_1 = require("path");
7
+ const winston = tslib_1.__importStar(require("winston"));
8
+ const logging_1 = require("../constants/logging");
9
+ class Logger {
10
+ constructor(config) {
11
+ this.sensitiveKeys = [
12
+ /authtoken/i,
13
+ /^email$/i,
14
+ /^password$/i,
15
+ /secret/i,
16
+ /token/i,
17
+ /api[-._]?key/i,
18
+ /management[-._]?token/i,
19
+ /sessionid/i,
20
+ /orgid/i,
21
+ ];
22
+ this.config = config;
23
+ this.loggers = {
24
+ error: this.getLoggerInstance('error'),
25
+ warn: this.getLoggerInstance('warn'),
26
+ info: this.getLoggerInstance('info'),
27
+ debug: this.getLoggerInstance('debug'),
28
+ success: this.getLoggerInstance('info'), // Map success to info
29
+ };
30
+ }
31
+ getLoggerInstance(level = 'info') {
32
+ const filePath = (0, path_1.normalize)(process.env.CS_CLI_LOG_PATH || this.config.basePath).replace(/^(\.\.(\/|\\|$))+/, '');
33
+ if (level === 'hidden') {
34
+ return this.createLogger('error', filePath);
35
+ }
36
+ return this.createLogger(level, filePath);
37
+ }
38
+ get loggerOptions() {
39
+ return {
40
+ filename: '',
41
+ maxFiles: 20,
42
+ tailable: true,
43
+ maxsize: 1000000,
44
+ };
45
+ }
46
+ createLogger(level, filePath) {
47
+ return winston.createLogger({
48
+ levels: logging_1.logLevels,
49
+ level: level,
50
+ transports: [
51
+ new winston.transports.File(Object.assign(Object.assign({}, this.loggerOptions), { filename: `${filePath}/${level}.log`, format: winston.format.combine(winston.format.timestamp(), winston.format.json()) })),
52
+ new winston.transports.Console({
53
+ format: winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf((info) => {
54
+ const colorizer = winston.format.colorize();
55
+ const levelText = info.level.toUpperCase();
56
+ const timestamp = info.timestamp;
57
+ const message = info.message;
58
+ const meta = info.meta;
59
+ let fullLine = `[${timestamp}] ${levelText}: ${message}`;
60
+ if (meta && (info.level !== 'info' && info.level !== 'success')) {
61
+ const redactedMeta = this.isLogEntry(meta) ? JSON.stringify(this.redact(meta)) : JSON.stringify(this.redact(meta));
62
+ fullLine += ` - ${redactedMeta}`;
63
+ }
64
+ return colorizer.colorize(info.level, fullLine);
65
+ })),
66
+ }),
67
+ ],
68
+ });
69
+ }
70
+ isSensitiveKey(keyStr) {
71
+ return keyStr && typeof keyStr === 'string'
72
+ ? this.sensitiveKeys.some((regex) => regex.test(keyStr))
73
+ : false;
74
+ }
75
+ redactObject(obj) {
76
+ const self = this;
77
+ (0, traverse_1.default)(obj).forEach(function redactor() {
78
+ if (this.key && self.isSensitiveKey(this.key)) {
79
+ this.update('[REDACTED]');
80
+ }
81
+ });
82
+ }
83
+ redact(info) {
84
+ try {
85
+ const copy = (0, full_1.klona)(info);
86
+ this.redactObject(copy);
87
+ const splat = copy[Symbol.for('splat')];
88
+ if (splat)
89
+ this.redactObject(splat);
90
+ return copy;
91
+ }
92
+ catch (error) {
93
+ return info;
94
+ }
95
+ }
96
+ isLogEntry(obj) {
97
+ return typeof obj === 'object' && 'level' in obj && 'message' in obj;
98
+ }
99
+ shouldLog(level, target) {
100
+ const configLevel = target === 'console' ? this.config.consoleLogLevel : this.config.logLevel;
101
+ const minLevel = configLevel ? logging_1.logLevels[configLevel] : 2; // default: info
102
+ const entryLevel = logging_1.logLevels[level];
103
+ return entryLevel <= minLevel;
104
+ }
105
+ /* === Public Log Methods === */
106
+ error(message, meta) {
107
+ if (this.shouldLog('error', 'console') || this.shouldLog('error', 'file')) {
108
+ this.loggers.error.error(message, meta);
109
+ }
110
+ }
111
+ warn(message, meta) {
112
+ if (this.shouldLog('warn', 'console') || this.shouldLog('warn', 'file')) {
113
+ this.loggers.warn.warn(message, meta);
114
+ }
115
+ }
116
+ info(message, meta) {
117
+ if (this.shouldLog('info', 'console') || this.shouldLog('info', 'file')) {
118
+ this.loggers.info.info(message, meta);
119
+ }
120
+ }
121
+ success(message, meta) {
122
+ if (this.shouldLog('info', 'console') || this.shouldLog('info', 'file')) {
123
+ this.loggers.success.info(message, Object.assign(Object.assign({}, meta), { type: 'success' }));
124
+ }
125
+ }
126
+ debug(message, meta) {
127
+ if (this.shouldLog('debug', 'console') || this.shouldLog('debug', 'file')) {
128
+ this.loggers.debug.debug(message, meta);
129
+ }
130
+ }
131
+ /* === Structured Logging === */
132
+ logError(params) {
133
+ const logPayload = {
134
+ level: logging_1.logLevels.error,
135
+ message: params.message,
136
+ timestamp: new Date(),
137
+ meta: Object.assign({ type: params.type, error: params.error, context: params.context }, params.meta),
138
+ };
139
+ const targetLevel = params.hidden ? 'debug' : 'error';
140
+ if (this.shouldLog(targetLevel, 'console') || this.shouldLog(targetLevel, 'file')) {
141
+ this.loggers[targetLevel].error(logPayload);
142
+ }
143
+ }
144
+ logWarn(params) {
145
+ const logPayload = {
146
+ level: logging_1.logLevels.warn,
147
+ message: params.message,
148
+ timestamp: new Date(),
149
+ meta: Object.assign({ type: params.type, context: params.context }, params.meta),
150
+ };
151
+ if (this.shouldLog('warn', 'console') || this.shouldLog('warn', 'file')) {
152
+ this.loggers.warn.warn(logPayload);
153
+ }
154
+ }
155
+ logInfo(params) {
156
+ const logPayload = {
157
+ level: logging_1.logLevels.info,
158
+ message: params.message,
159
+ timestamp: new Date(),
160
+ meta: Object.assign({ type: params.type, info: params.info, context: params.context }, params.meta),
161
+ };
162
+ if (this.shouldLog('info', 'console') || this.shouldLog('info', 'file')) {
163
+ this.loggers.info.info(logPayload);
164
+ }
165
+ }
166
+ logSuccess(params) {
167
+ const logPayload = {
168
+ level: logging_1.logLevels.success,
169
+ message: params.message,
170
+ timestamp: new Date(),
171
+ meta: Object.assign({ type: params.type, data: params.data, context: params.context }, params.meta),
172
+ };
173
+ if (this.shouldLog('info', 'console') || this.shouldLog('info', 'file')) {
174
+ this.loggers.success.info(logPayload);
175
+ }
176
+ }
177
+ logDebug(params) {
178
+ const logPayload = {
179
+ level: logging_1.logLevels.debug,
180
+ message: params.message,
181
+ timestamp: new Date(),
182
+ meta: Object.assign({ type: params.type, debug: params.debug, context: params.context }, params.meta),
183
+ };
184
+ if (this.shouldLog('debug', 'console') || this.shouldLog('debug', 'file')) {
185
+ this.loggers.debug.debug(logPayload);
186
+ }
187
+ }
188
+ }
189
+ exports.default = Logger;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentstack/cli-utilities",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Utilities for contentstack projects",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -32,16 +32,16 @@
32
32
  "author": "contentstack",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@contentstack/management": "~1.20.1",
36
- "@contentstack/marketplace-sdk": "^1.2.5",
37
- "@oclif/core": "^4.2.7",
38
- "axios": "^1.8.2",
35
+ "@contentstack/management": "~1.21.4",
36
+ "@contentstack/marketplace-sdk": "^1.2.8",
37
+ "@oclif/core": "^4.3.0",
38
+ "axios": "^1.9.0",
39
39
  "chalk": "^4.1.2",
40
40
  "cli-cursor": "^3.1.0",
41
41
  "cli-progress": "^3.12.0",
42
42
  "cli-table": "^0.3.11",
43
43
  "conf": "^10.2.0",
44
- "dotenv": "^16.4.7",
44
+ "dotenv": "^16.5.0",
45
45
  "figures": "^3.2.0",
46
46
  "inquirer": "8.2.6",
47
47
  "inquirer-search-checkbox": "^1.0.0",
@@ -52,7 +52,7 @@
52
52
  "mkdirp": "^1.0.4",
53
53
  "open": "^8.4.2",
54
54
  "ora": "^5.4.1",
55
- "papaparse": "^5.5.2",
55
+ "papaparse": "^5.5.3",
56
56
  "recheck": "~4.4.5",
57
57
  "rxjs": "^6.6.7",
58
58
  "traverse": "^0.6.11",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/chai": "^4.3.20",
67
- "@types/inquirer": "^9.0.7",
67
+ "@types/inquirer": "^9.0.8",
68
68
  "@types/mkdirp": "^1.0.2",
69
69
  "@types/mocha": "^10.0.10",
70
70
  "@types/node": "^14.18.63",
@@ -72,12 +72,12 @@
72
72
  "@types/traverse": "^0.6.37",
73
73
  "chai": "^4.5.0",
74
74
  "eslint": "^8.57.1",
75
- "eslint-config-oclif": "^6.0.15",
75
+ "eslint-config-oclif": "^6.0.62",
76
76
  "eslint-config-oclif-typescript": "^3.1.14",
77
77
  "fancy-test": "^2.0.42",
78
78
  "mocha": "10.8.2",
79
79
  "nyc": "^15.1.0",
80
- "sinon": "^19.0.2",
80
+ "sinon": "^19.0.5",
81
81
  "ts-node": "^10.9.2",
82
82
  "typescript": "^4.9.5"
83
83
  }