From 80cb4ffffc44f80b1fea132711a5add41984620e Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 28 Oct 2025 13:30:08 +0530 Subject: [PATCH 1/2] updated error messages --- src/api.ts | 17 ++-- src/core/index.ts | 57 +++++------ src/core/inet.ts | 17 ++-- src/core/plugins.ts | 13 +-- src/core/process.ts | 5 +- src/core/q.ts | 23 ++--- src/core/token-management.ts | 11 ++- src/index.ts | 11 ++- src/util/fs.ts | 13 +-- src/util/index.ts | 26 +++-- src/util/messages.ts | 181 +++++++++++++++++++++++++++++++++++ src/util/unprocessible.ts | 7 +- 12 files changed, 289 insertions(+), 92 deletions(-) create mode 100644 src/util/messages.ts diff --git a/src/api.ts b/src/api.ts index a089112..b707241 100644 --- a/src/api.ts +++ b/src/api.ts @@ -10,6 +10,7 @@ import { join } from 'path' import { stringify } from 'querystring' import { sanitizeUrl } from '@braintree/sanitize-url'; import { readFileSync } from './util/fs' +import { MESSAGES } from './util/messages' const debug = Debug('api') let MAX_RETRY_LIMIT @@ -74,7 +75,7 @@ export const get = (req, RETRY = 1) => { } try { - debug(`${options.method.toUpperCase()}: ${options.path}`) + debug(MESSAGES.API.REQUEST(options.method, options.path)) let timeDelay let body = '' const httpRequest = request(options, (response) => { @@ -83,12 +84,12 @@ export const get = (req, RETRY = 1) => { .setEncoding('utf-8') .on('data', (chunk) => body += chunk) .on('end', () => { - debug(`status: ${response.statusCode}.`) + debug(MESSAGES.API.STATUS(response.statusCode)) if (response.statusCode >= 200 && response.statusCode <= 399) { return resolve(JSON.parse(body)) } else if (response.statusCode === 429) { timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE - debug(`API rate limit exceeded. Retrying ${options.path} with ${timeDelay} ms delay`) + debug(MESSAGES.API.RATE_LIMIT(options.path, timeDelay)) return setTimeout(() => { return get(req, RETRY) @@ -98,7 +99,7 @@ export const get = (req, RETRY = 1) => { } else if (response.statusCode >= 500) { // retry, with delay timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE - debug(`Retrying ${options.path} with ${timeDelay} ms delay`) + debug(MESSAGES.API.RETRY(options.path, timeDelay)) RETRY++ return setTimeout(() => { @@ -107,7 +108,7 @@ export const get = (req, RETRY = 1) => { .catch(reject) }, timeDelay) } else { - debug(`Request failed\n${JSON.stringify(options)}`) + debug(MESSAGES.API.REQUEST_FAILED(options)) return reject(body) } @@ -116,19 +117,19 @@ export const get = (req, RETRY = 1) => { // Set socket timeout to handle socket hang ups httpRequest.setTimeout(options.timeout, () => { - debug(`Request timeout for ${options.path || 'unknown'}`) + debug(MESSAGES.API.REQUEST_TIMEOUT(options.path)) httpRequest.destroy() reject(new Error('Request timeout')) }) // Enhanced error handling for socket hang ups and connection resets httpRequest.on('error', (error: any) => { - debug(`Request error for ${options.path || 'unknown'}: ${error?.message || 'Unknown error'} (${error?.code || 'NO_CODE'})`) + debug(MESSAGES.API.REQUEST_ERROR(options.path, error?.message, error?.code)) // Handle socket hang up and connection reset errors with retry if ((error?.code === 'ECONNRESET' || error?.message?.includes('socket hang up')) && RETRY <= MAX_RETRY_LIMIT) { timeDelay = Math.pow(Math.SQRT2, RETRY) * RETRY_DELAY_BASE - debug(`Socket hang up detected. Retrying ${options.path || 'unknown'} with ${timeDelay} ms delay (attempt ${RETRY}/${MAX_RETRY_LIMIT})`) + debug(MESSAGES.API.SOCKET_HANGUP_RETRY(options.path, timeDelay, RETRY, MAX_RETRY_LIMIT)) RETRY++ return setTimeout(() => { diff --git a/src/core/index.ts b/src/core/index.ts index 71d0c89..c59f33c 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -13,6 +13,7 @@ import { get, init as initAPI } from '../api' import { existsSync, readFileSync } from '../util/fs' import { filterItems, formatItems, groupItems, markCheckpoint } from '../util/index' import { logger } from '../util/logger' +import { MESSAGES } from '../util/messages' import { map } from '../util/promise.map' import { netConnectivityIssues } from './inet' import { Q as Queue } from './q' @@ -76,7 +77,7 @@ export const init = (contentStore, assetStore) => { config = getConfig() Q = new Queue(contentStore, assetStore, config) initAPI(config.contentstack) - debug('Sync core:start invoked') + debug(MESSAGES.SYNC_CORE.START) return new Promise((resolve, reject) => { try { @@ -84,7 +85,7 @@ export const init = (contentStore, assetStore) => { const checkPointConfig: ICheckpoint = config.checkpoint const paths = config.paths const environment = Contentstack.environment || process.env.NODE_ENV || 'development' - debug(`Environment: ${environment}`) + debug(MESSAGES.SYNC_CORE.ENVIRONMENT(environment)) const request: any = { qs: { environment, @@ -136,9 +137,9 @@ const loadCheckpoint = (checkPointConfig: ICheckpoint, paths: any): void => { // Set sync token if checkpoint is found if (checkpoint) { - debug("Found sync token in checkpoint file:", checkpoint); + debug(MESSAGES.SYNC_CORE.TOKEN_FOUND, checkpoint); Contentstack.sync_token = checkpoint.token; - debug("Using sync token:", Contentstack.sync_token); + debug(MESSAGES.SYNC_CORE.TOKEN_USING, Contentstack.sync_token); } }; @@ -146,13 +147,13 @@ const loadCheckpoint = (checkPointConfig: ICheckpoint, paths: any): void => { function readHiddenFile(filePath: string) { try { if (!fs.existsSync(filePath)) { - logger.error("File does not exist:", filePath); + logger.error(MESSAGES.SYNC_CORE.FILE_NOT_FOUND(filePath)); return; } const data = fs.readFileSync(filePath, "utf8"); return JSON.parse(data); } catch (err) { - logger.error("Error reading file:", err); + logger.error(MESSAGES.SYNC_CORE.FILE_READ_ERROR(err)); return undefined; } } @@ -174,15 +175,15 @@ export const pop = () => { */ export const poke = async () => { try { - debug('Invoked poke'); - logger.info('Received \'contentstack sync\' notification') + debug(MESSAGES.SYNC_CORE.POKE_INVOKED); + logger.info(MESSAGES.SYNC_CORE.POKE_NOTIFICATION) if (!flag.lockdown) { flag.WQ = true return await check() } return null; } catch (error) { - debug('Error [poke]', error); + debug(MESSAGES.SYNC_CORE.POKE_ERROR, error); throw error; } } @@ -193,12 +194,12 @@ export const poke = async () => { */ const check = async () => { try { - debug(`Check called. SQ status is ${flag.SQ} and WQ status is ${flag.WQ}`) + debug(MESSAGES.SYNC_CORE.CHECK_CALLED(flag.SQ, flag.WQ)) if (!flag.SQ && flag.WQ) { flag.WQ = false flag.SQ = true await sync(); - debug(`Sync completed and SQ flag updated. Cooloff duration is ${config.syncManager.cooloff}`) + debug(MESSAGES.SYNC_CORE.CHECK_COMPLETE(config.syncManager.cooloff)) setTimeout(() => { flag.SQ = false emitter.emit('check') @@ -206,11 +207,11 @@ const check = async () => { } } catch (error) { logger.error(error) - debug('Error [check]', error); + debug(MESSAGES.SYNC_CORE.CHECK_ERROR, error); check().then(() => { - debug('passed [check] error'); + debug(MESSAGES.SYNC_CORE.CHECK_RECOVERED); }).catch((error) => { - debug('failed [check] error', error); + debug(MESSAGES.SYNC_CORE.CHECK_FAILED, error); }); throw error; } @@ -221,9 +222,9 @@ const check = async () => { */ const sync = async () => { try { - debug('started [sync]'); + debug(MESSAGES.SYNC_CORE.SYNC_STARTED); const tokenObject = await getToken(); - debug('tokenObject [sync]', tokenObject); + debug(MESSAGES.SYNC_CORE.SYNC_TOKEN_OBJECT, tokenObject); const token: IToken = (tokenObject as IToken) const request: any = { qs: { @@ -234,7 +235,7 @@ const sync = async () => { } return await fire(request) } catch (error) { - debug('Error [sync]', error); + debug(MESSAGES.SYNC_CORE.SYNC_ERROR, error); throw error } } @@ -243,7 +244,7 @@ const sync = async () => { * @description Used to lockdown the 'sync' process in case of exceptions */ export const lock = () => { - debug('Contentstack sync locked..') + debug(MESSAGES.SYNC_CORE.SYNC_LOCKED) flag.lockdown = true } @@ -251,7 +252,7 @@ export const lock = () => { * @description Used to unlock the 'sync' process in case of errors/exceptions */ export const unlock = (refire?: boolean) => { - debug('Contentstack sync unlocked..', refire) + debug(MESSAGES.SYNC_CORE.SYNC_UNLOCKED, refire) flag.lockdown = false if (typeof refire === 'boolean' && refire) { flag.WQ = true @@ -269,7 +270,7 @@ export const unlock = (refire?: boolean) => { * @param {Object} req - Contentstack sync API request object */ const fire = (req: IApiRequest) => { - debug(`Fire called with: ${JSON.stringify(req)}`) + debug(MESSAGES.SYNC_CORE.FIRE_CALLED(req)) flag.SQ = true return new Promise((resolve, reject) => { @@ -279,7 +280,7 @@ const fire = (req: IApiRequest) => { delete req.qs.sync_token delete req.path const syncResponse: ISyncResponse = response - debug('Response [fire]', syncResponse.items.length); + debug(MESSAGES.SYNC_CORE.FIRE_COMPLETE(syncResponse.items.length)); if (syncResponse.items.length) { return filterItems(syncResponse, config).then(() => { if (syncResponse.items.length === 0) { @@ -319,7 +320,7 @@ const fire = (req: IApiRequest) => { return map(contentTypeUids, (uid) => { return new Promise((mapResolve, mapReject) => { - debug(`API called with for content type: ${uid}`) + debug(MESSAGES.SYNC_CORE.API_CALL_CT(uid)) return get({ path: `${Contentstack.apis.content_types}${uid}`, qs: { @@ -344,7 +345,7 @@ const fire = (req: IApiRequest) => { return mapReject(err) }).catch((error) => { - debug('Error [map] fetching content type schema:', error) + debug(MESSAGES.SYNC_CORE.ERROR_MAP, error) if (netConnectivityIssues(error)) { flag.SQ = false } @@ -361,11 +362,11 @@ const fire = (req: IApiRequest) => { flag.SQ = false } // Errorred while fetching content type schema - debug('Error [mapResolve]:', error) + debug(MESSAGES.SYNC_CORE.ERROR_MAP_RESOLVE, error) return reject(error) }) }).catch((processError) => { - debug('Error [filterItems]:', processError) + debug(MESSAGES.SYNC_CORE.ERROR_FILTER_ITEMS, processError) return reject(processError) }) } @@ -374,7 +375,7 @@ const fire = (req: IApiRequest) => { .then(resolve) .catch(reject) }).catch((error) => { - debug('Error [fire]', error); + debug(MESSAGES.SYNC_CORE.ERROR_FIRE, error); if (netConnectivityIssues(error)) { flag.SQ = false } @@ -406,7 +407,7 @@ const postProcess = (req, resp) => { req.qs[name] = resp[name] if (flag.lockdown) { - logger.log('Checkpoint: lockdown has been invoked') + logger.log(MESSAGES.SYNC_CORE.CHECKPOINT_LOCKDOWN) flag.requestCache = { params: req, reject, @@ -419,7 +420,7 @@ const postProcess = (req, resp) => { return resolve('') } - debug(`Re-Fire called with: ${JSON.stringify(req)}`) + debug(MESSAGES.SYNC_CORE.REFIRE_CALLED(req)) return fire(req) .then(resolve) .catch(reject); diff --git a/src/core/inet.ts b/src/core/inet.ts index fdae781..424e228 100644 --- a/src/core/inet.ts +++ b/src/core/inet.ts @@ -9,6 +9,7 @@ import dnsSocket from 'dns-socket' import { EventEmitter } from 'events' import { getConfig } from '../index' import { logger } from '../util/logger' +import { MESSAGES } from '../util/messages' import { poke } from './index' interface ISyncManager { @@ -47,7 +48,7 @@ export const init = () => { port = sm.inet.port dns = sm.inet.dns currentTimeout = sm.inet.retryTimeout - debug(`inet initiated - waiting ${currentTimeout} before checking connectivity.`) + debug(MESSAGES.INET.INITIATED(currentTimeout)) // start checking for net connectivity, 30 seconds after the app has started setTimeout(checkNetConnectivity, currentTimeout) } @@ -57,14 +58,14 @@ export const checkNetConnectivity = () => { retries: sm.inet.retries, timeout: sm.inet.timeout, }) - debug('checking network connectivity') + debug(MESSAGES.INET.CHECKING) socket.query(query, port, dns, (err) => { if (err) { - debug(`errorred.. ${err}`) + debug(MESSAGES.INET.CHECK_FAILED(err)) disconnected = true return socket.destroy(() => { - debug('socket destroyed') + debug(MESSAGES.INET.CLEANUP_ERROR) emitter.emit('disconnected', currentTimeout += sm.inet.retryIncrement) }) } else if (disconnected) { @@ -73,7 +74,7 @@ export const checkNetConnectivity = () => { disconnected = false return socket.destroy(() => { - debug('socket destroyed') + debug(MESSAGES.INET.CLEANUP_SUCCESS) emitter.emit('ok') }) }) @@ -92,12 +93,12 @@ export const netConnectivityIssues = (error) => { emitter.on('ok', () => { currentTimeout = sm.inet.retryTimeout - debug(`pinging ${sm.inet.host} in ${sm.inet.timeout} ms`) + debug(MESSAGES.INET.PINGING(sm.inet.host, sm.inet.timeout)) setTimeout(checkNetConnectivity, sm.inet.timeout) }) emitter.on('disconnected', (timeout) => { - logger.warn('Network disconnected') - debug(`pinging ${sm.inet.host} in ${timeout} ms`) + logger.warn(MESSAGES.INET.DISCONNECTED) + debug(MESSAGES.INET.PINGING(sm.inet.host, timeout)) setTimeout(checkNetConnectivity, timeout) }) diff --git a/src/core/plugins.ts b/src/core/plugins.ts index d330fb9..bc2cfc5 100644 --- a/src/core/plugins.ts +++ b/src/core/plugins.ts @@ -7,6 +7,7 @@ import Debug from 'debug' import { hasIn } from 'lodash' import { normalizePluginPath } from '../util/index' +import { MESSAGES } from '../util/messages' import { validatePlugin } from '../util/validations' const debug = Debug('plugins') @@ -18,7 +19,7 @@ const pluginMethods = ['beforeSync', 'afterSync'] * @returns {Object} pluginInstance - An instance of plugins, with valid registered methods */ export const load = (config) => { - debug('Plugins load called') + debug(MESSAGES.PLUGINS.LOAD_CALLED) try { const pluginInstances = { external: {}, @@ -54,17 +55,17 @@ export const load = (config) => { } else { pluginInstances.external[pluginMethod].push(Plugin[pluginMethod]) } - debug(`${pluginMethod} loaded from ${pluginName} successfully!`) + debug(MESSAGES.PLUGINS.METHOD_LOADED(pluginMethod, pluginName)) } else { - debug(`${pluginMethod} not found in ${pluginName}`) + debug(MESSAGES.PLUGINS.METHOD_NOT_FOUND(pluginMethod, pluginName)) } }) }) - debug('Plugins loaded successfully!') + debug(MESSAGES.PLUGINS.LOAD_SUCCESS) return pluginInstances } catch (error) { - debug('Error while loading plugins:', error) - throw new Error(`Failed to load plugins: ${error?.message}`) + debug(MESSAGES.PLUGINS.LOAD_ERROR, error) + throw new Error(MESSAGES.PLUGINS.LOAD_ERROR_DETAIL(error?.message)) } } diff --git a/src/core/process.ts b/src/core/process.ts index 5a527a9..fe17813 100644 --- a/src/core/process.ts +++ b/src/core/process.ts @@ -11,6 +11,7 @@ import { getConfig } from '../index' import { logger } from '../util/logger' +import { MESSAGES } from '../util/messages' import { lock, unlock } from './index' /** @@ -21,7 +22,7 @@ const handleExit = (signal) => { lock() const { syncManager } = getConfig() const killDuration = (process.env.KILLDURATION) ? calculateKillDuration() : syncManager.processTimeout - logger.info(`Received ${signal}. This will shut down the process in ${killDuration}ms..`) + logger.info(MESSAGES.PROCESS.SHUTDOWN(signal, killDuration)) setTimeout(abort, killDuration) } @@ -33,7 +34,7 @@ const handleExit = (signal) => { * @param {Object} error - Unhandled error object */ const unhandledErrors = (error) => { - logger.error('Unhandled exception caught. Locking down process for 10s to recover..') + logger.error(MESSAGES.PROCESS.UNHANDLED_ERROR) logger.error(error) lock() setTimeout(() => { diff --git a/src/core/q.ts b/src/core/q.ts index e076728..90deb4d 100644 --- a/src/core/q.ts +++ b/src/core/q.ts @@ -10,6 +10,7 @@ import { cloneDeep } from 'lodash' import { lock, unlock } from '.' import { filterUnwantedKeys, getSchema } from '../util/index' import { logger } from '../util/logger' +import { MESSAGES } from '../util/messages' import { series } from '../util/series' import { saveFailedItems } from '../util/unprocessible' import { load } from './plugins' @@ -54,7 +55,7 @@ export class Q extends EventEmitter { this.on('push', this.push) this.on('unshift', this.unshift) instance = this - debug('Core \'Q\' constructor initiated') + debug(MESSAGES.QUEUE.CONSTRUCTOR) } return instance @@ -66,7 +67,7 @@ export class Q extends EventEmitter { this.iLock = true lock() } - debug(`Content type '${data._content_type_uid}' received for '${data._type}'`) + debug(MESSAGES.QUEUE.CONTENT_TYPE_RECEIVED(data._content_type_uid, data._type)) this.emit('next') } @@ -80,7 +81,7 @@ export class Q extends EventEmitter { this.iLock = true lock() } - debug(`Content type '${data._content_type_uid}' received for '${data._type}'`) + debug(MESSAGES.QUEUE.CONTENT_TYPE_RECEIVED(data._content_type_uid, data._type)) this.emit('next') } @@ -93,7 +94,7 @@ export class Q extends EventEmitter { try { notify('error', obj) logger.error(obj) - debug(`Error handler called with ${JSON.stringify(obj)}`) + debug(MESSAGES.QUEUE.ERROR_HANDLER_CALLED(obj)) if (typeof obj.checkpoint !== 'undefined') { await saveToken(obj.checkpoint.name, obj.checkpoint.token) } @@ -102,7 +103,7 @@ export class Q extends EventEmitter { this.emit('next') } catch (error) { // probably, the context could change - logger.error('Something went wrong in errorHandler!') + logger.error(MESSAGES.QUEUE.ERROR_IN_HANDLER) logger.error(error) that.inProgress = false that.emit('next') @@ -123,7 +124,7 @@ export class Q extends EventEmitter { unlock(true) this.iLock = false } - debug(`Calling 'next'. In progress status is ${this.inProgress}, and Q length is ${this.q.length}`) + debug(MESSAGES.QUEUE.NEXT_CALLED(this.inProgress, this.q.length)) if (!this.inProgress && this.q.length) { this.inProgress = true const item = this.q.shift() @@ -167,7 +168,7 @@ export class Q extends EventEmitter { checkpoint = data._checkpoint delete data._checkpoint } - debug(`Executing: ${JSON.stringify(data)}`) + debug(MESSAGES.QUEUE.EXECUTING(data)) const beforeSyncInternalPlugins = [] // re-initializing everytime with const.. avoids memory leaks const beforeSyncPlugins = [] @@ -214,16 +215,16 @@ export class Q extends EventEmitter { await Promise.all(beforeSyncPlugins) } - debug('Before action plugins executed successfully!') + debug(MESSAGES.QUEUE.BEFORE_PLUGINS) await this.contentStore[action](data) - debug(`Completed '${action}' on connector successfully!`) + debug(MESSAGES.QUEUE.ACTION_COMPLETE(action)) if (typeof schema !== 'undefined') { if (branch) schema.branch = branch await this.contentStore.updateContentType(schema) } - debug('Connector instance called successfully!') + debug(MESSAGES.QUEUE.CONNECTOR_CALLED) if (this.syncManager.serializePlugins) { this.pluginInstances.external.afterSync.forEach((method) => { afterSyncPlugins.push(() => method(action, transformedData, transformedSchema)) @@ -242,7 +243,7 @@ export class Q extends EventEmitter { await saveToken(checkpoint.name, checkpoint.token) } - debug('After action plugins executed successfully!') + debug(MESSAGES.QUEUE.AFTER_PLUGINS) logger.log( `${type}: { content_type: '${contentType}', ${ (locale) ? `locale: '${locale}',` : '' diff --git a/src/core/token-management.ts b/src/core/token-management.ts index f4c99f6..a454db2 100644 --- a/src/core/token-management.ts +++ b/src/core/token-management.ts @@ -8,6 +8,7 @@ import Debug from 'debug' import { getConfig } from '../index' import { existsSync, readFile, writeFile } from '../util/fs' import { getFile } from '../util/index' +import { MESSAGES } from '../util/messages' const debug = Debug('token-management') let counter = 0 @@ -43,12 +44,12 @@ export const getToken = () => { const token = config.paths.token let data: any = {} if (existsSync(checkpoint)) { - debug(`Checkpoint read: ${checkpoint}`) + debug(MESSAGES.TOKEN.CHECKPOINT_READ(checkpoint)) const contents: any = await readFile(checkpoint) data = JSON.parse(contents) } else if (existsSync(token)) { - debug(`Token read: ${token}`) + debug(MESSAGES.TOKEN.TOKEN_READ(token)) const contents: any = await readFile(token) data = JSON.parse(contents) @@ -68,7 +69,7 @@ export const getToken = () => { * @param {String} type - Token type */ export const saveToken = (name, token) => { - debug(`Save token invoked with name: ${name}, token: ${token}`) + debug(MESSAGES.TOKEN.SAVE_TOKEN(name)) return new Promise(async (resolve, reject) => { try { @@ -99,7 +100,7 @@ export const saveToken = (name, token) => { return `${config.paths.ledger}-${counter}` }) as any) - debug(`ledger file: ${file} exists?(${existsSync(file)})`) + debug(MESSAGES.TOKEN.LEDGER_CHECK(file, existsSync(file))) if (!existsSync(file)) { await writeFile(file, JSON.stringify([obj])) @@ -124,7 +125,7 @@ export const saveToken = (name, token) => { * @param {String} type - Token type */ export const saveCheckpoint = async (name, token) => { - debug(`Save token invoked with name: ${name}, token: ${token}`) + debug(MESSAGES.TOKEN.SAVE_CHECKPOINT(name)) const config = getConfig() const path = config.paths.checkpoint const data: IToken = { diff --git a/src/index.ts b/src/index.ts index 8eeb7b7..1c09a26 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import { notifications } from './core/q' import { buildConfigPaths } from './util/build-paths' import { formatSyncFilters } from './util/index' import { logger, setLogger } from './util/logger' +import { MESSAGES } from './util/messages' import { validateAssetStore, @@ -208,26 +209,26 @@ export const start = (config: IConfig = {}): Promise<{}> => { configure() return assetStore.start(appConfig).then((assetInstance: IAssetStore) => { - debug('Asset store instance has returned successfully!') + debug(MESSAGES.INDEX.ASSET_STORE_INIT) validateAssetStoreInstance(assetInstance) assetStoreInstance = assetInstance return contentStore.start(assetInstance, appConfig) }).then((contentStoreInstance) => { - debug('Content store instance has returned successfully!') + debug(MESSAGES.INDEX.CONTENT_STORE_INIT) validateContentStoreInstance(contentStoreInstance) appConfig = formatSyncFilters(appConfig) return init(contentStoreInstance, assetStoreInstance) }).then(() => { - debug('Sync Manager initiated successfully!') + debug(MESSAGES.INDEX.SYNC_MANAGER_INIT) listener.register(poke) // start checking for inet 10 secs after the app has started pinger() return listener.start(appConfig) }).then(() => { - logger.info('Contentstack sync utility started successfully!') + logger.info(MESSAGES.INDEX.SYNC_UTILITY_STARTED) return resolve('') }).catch(reject) @@ -245,7 +246,7 @@ export const start = (config: IConfig = {}): Promise<{}> => { */ export const debugNotifications = (action) => { return (item) => { - debug(`Notifications: ${action} - ${JSON.stringify(item)}`) + debug(MESSAGES.INDEX.NOTIFICATION(action, item)) } } diff --git a/src/util/fs.ts b/src/util/fs.ts index c2dbfc3..5c2e2f2 100644 --- a/src/util/fs.ts +++ b/src/util/fs.ts @@ -10,6 +10,7 @@ import { existsSync, readFile as rf, readFileSync as rFS, stat } from 'fs' import { mkdirp, mkdirpSync } from 'mkdirp' import { dirname } from 'path' import writeFileAtomic from 'write-file-atomic' +import { MESSAGES } from './messages' export { existsSync } const debug = Debug('sm:util-fs') @@ -21,7 +22,7 @@ const debug = Debug('sm:util-fs') * @returns {Promise} Returns a promise */ export const writeFile = (filePath, data) => { - debug(`Write file called on ${filePath}`) + debug(MESSAGES.FS.WRITE_FILE(filePath)) return new Promise((resolve, reject) => { try { @@ -51,7 +52,7 @@ export const writeFile = (filePath, data) => { * @returns {Promise} Returns a promise */ export const readFile = (filePath) => { - debug(`Read file called on ${filePath}`) + debug(MESSAGES.FS.READ_FILE(filePath)) return new Promise((resolve, reject) => { try { @@ -67,7 +68,7 @@ export const readFile = (filePath) => { return resolve(data) }) } - const err: any = new Error(`Invalid 'read' operation on file. Expected ${filePath} to be of type 'file'!`) + const err: any = new Error(MESSAGES.FS.INVALID_READ(filePath)) err.code = 'IOORF' return reject(err) @@ -84,11 +85,11 @@ export const readFile = (filePath) => { * @returns {String} Returns the data that's been read */ export const readFileSync = (filePath) => { - debug(`Read file sync called on ${filePath}`) + debug(MESSAGES.FS.READ_FILE_SYNC(filePath)) if (existsSync(filePath)) { return rFS(filePath, {encoding: 'utf-8'}) } - const err: any = new Error(`Invalid 'read' operation on file. Expected ${filePath} to be of type 'file'!`) + const err: any = new Error(MESSAGES.FS.INVALID_READ(filePath)) err.code = 'IOORFS' throw err } @@ -99,7 +100,7 @@ export const readFileSync = (filePath) => { * @returns {String} Returns a promise */ export const mkdir = (path) => { - debug(`mkdir called on ${path}`) + debug(MESSAGES.FS.MKDIR(path)) return mkdirp(path) .then(() => '') .catch(error => Promise.reject(error)) diff --git a/src/util/index.ts b/src/util/index.ts index f004d51..3246e00 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -30,6 +30,9 @@ import { import { logger, } from './logger' +import { + MESSAGES, +} from './messages' import { saveFilteredItems, } from './unprocessible' @@ -40,6 +43,9 @@ import { import { sanitizePath } from './../plugins/helper'; const debug = Debug('util:index') + +// Re-export messages for convenience +export { MESSAGES } from './messages' const formattedAssetType = '_assets' const formattedContentType = '_content_types' const assetType = 'sys_assets' @@ -194,8 +200,8 @@ export const formatItems = (items, config) => { items[i]._content_type_uid = formattedContentType break default: - logger.error('Item\'s type did not match any expected case!!') - logger.error(JSON.stringify(items[i])) + logger.error(MESSAGES.UTIL.ITEM_TYPE_MISMATCH) + logger.error(MESSAGES.UTIL.ITEM_SERIALIZED(items[i])) // remove the element from items[i]s items[i].splice(i, 1) i-- @@ -216,7 +222,7 @@ export const markCheckpoint = (groupedItems, syncResponse) => { const tokenValue = syncResponse[tokenName] const contentTypeUids = Object.keys(groupedItems) if (contentTypeUids.length === 1 && contentTypeUids[0] === '_assets') { - debug(`Only assets found in SYNC API response. Last content type is ${contentTypeUids[0]}`) + debug(MESSAGES.UTIL.ONLY_ASSETS(contentTypeUids[0])) const items = groupedItems[contentTypeUids[0]] // find the last item, add checkpoint to it items[items.length - 1]._checkpoint = { @@ -224,7 +230,7 @@ export const markCheckpoint = (groupedItems, syncResponse) => { token: tokenValue, } } else if (contentTypeUids.length === 1 && contentTypeUids[0] === '_content_types') { - debug(`Only content type events found in SYNC API response. Last content type is ${contentTypeUids[0]}`) + debug(MESSAGES.UTIL.ONLY_CT_EVENTS(contentTypeUids[0])) const items = groupedItems[contentTypeUids[0]] // find the last item, add checkpoint to it items[items.length - 1]._checkpoint = { @@ -233,7 +239,7 @@ export const markCheckpoint = (groupedItems, syncResponse) => { } } else if (contentTypeUids.length === 2 && (contentTypeUids.indexOf('_assets') !== -1 && contentTypeUids.indexOf( '_content_types'))) { - debug(`Assets & content types found found in SYNC API response. Last content type is ${contentTypeUids[1]}`) + debug(MESSAGES.UTIL.ASSETS_AND_CT(contentTypeUids[1])) const items = groupedItems[contentTypeUids[1]] // find the last item, add checkpoint to it items[items.length - 1]._checkpoint = { @@ -242,7 +248,7 @@ export const markCheckpoint = (groupedItems, syncResponse) => { } } else { const lastContentTypeUid = contentTypeUids[contentTypeUids.length - 1] - debug(`Mixed content types found in SYNC API response. Last content type is ${lastContentTypeUid}`) + debug(MESSAGES.UTIL.MIXED_CT(lastContentTypeUid)) const entries = groupedItems[lastContentTypeUid] entries[entries.length - 1]._checkpoint = { name: tokenName, @@ -274,7 +280,7 @@ export const getFile = (file, rotate) => { return resolve(file) } else { - return reject(new Error(`${file} is not of type file`)) + return reject(new Error(MESSAGES.UTIL.FILE_NOT_TYPE(file))) } }) } else { @@ -416,7 +422,7 @@ export const normalizePluginPath = (config, plugin, isInternal) => { if (plugin.path && typeof plugin.path === 'string' && plugin.path.length > 0) { if (isAbsolute(plugin.path)) { if (!existsSync(plugin.path)) { - throw new Error(`${plugin.path} does not exist!`) + throw new Error(MESSAGES.UTIL.PLUGIN_PATH_NOT_EXIST(plugin.path)) } return plugin.path @@ -425,7 +431,7 @@ export const normalizePluginPath = (config, plugin, isInternal) => { pluginPath = resolve(join(sanitizePath(config.paths.baseDir), sanitizePath(plugin.name), 'index.js')) if (!existsSync(pluginPath)) { - throw new Error(`${pluginPath} does not exist!`) + throw new Error(MESSAGES.UTIL.PLUGIN_PATH_NOT_EXIST(pluginPath)) } return pluginPath @@ -441,7 +447,7 @@ export const normalizePluginPath = (config, plugin, isInternal) => { pluginPath = resolve(join(sanitizePath(config.paths.plugin), sanitizePath(plugin.name), 'index.js')) if (!existsSync(pluginPath)) { - throw new Error(`Unable to find plugin: ${JSON.stringify(plugin)}`) + throw new Error(MESSAGES.UTIL.UNABLE_TO_FIND_PLUGIN(plugin)) } return pluginPath diff --git a/src/util/messages.ts b/src/util/messages.ts new file mode 100644 index 0000000..8b41966 --- /dev/null +++ b/src/util/messages.ts @@ -0,0 +1,181 @@ +/*! +* Contentstack DataSync Manager +* Centralized Messages and Logging Constants +* Copyright (c) 2025 Contentstack LLC +* MIT Licensed +*/ + +/** + * @description Centralized messages for logging and error handling + * This file contains all user-facing messages, debug logs, and error messages + * for consistency and easier maintenance. + */ + +export const MESSAGES = { + // Network connectivity messages (inet.ts) + INET: { + INITIATED: (timeout: number) => `inet initiated. Waiting ${timeout} ms before checking connectivity.`, + CHECKING: 'Checking network connectivity...', + CHECK_FAILED: (err: any) => `Network connectivity check failed: ${err}`, + CLEANUP_SUCCESS: 'Network check successful. Cleaning up connection.', + CLEANUP_ERROR: 'Network check failed. Cleaning up connection.', + PINGING: (host: string, timeout: number) => `Pinging ${host} in ${timeout} ms`, + DISCONNECTED: 'The network connection was lost.', + }, + + // Process management messages (process.ts) + PROCESS: { + SHUTDOWN: (signal: string, duration: number) => `Received ${signal}. Shutting down the process in ${duration} ms.`, + UNHANDLED_ERROR: 'An unexpected error occurred. Locking the process for 10 seconds to recover.', + }, + + // API messages (api.ts) + API: { + REQUEST: (method: string, path: string) => `${method.toUpperCase()}: ${path}`, + STATUS: (code: number) => `Status: ${code}.`, + RATE_LIMIT: (path: string, delay: number) => `API rate limit exceeded. Retrying ${path} after a ${delay} ms delay.`, + RETRY: (path: string, delay: number) => `Retrying ${path} after a ${delay} ms delay.`, + REQUEST_FAILED: (options: any) => `Request failed.\n${JSON.stringify(options)}`, + REQUEST_TIMEOUT: (path: string) => `Request timeout for ${path || 'unknown'}`, + REQUEST_ERROR: (path: string, message: string, code: string) => + `Request error for ${path || 'unknown'}: ${message || 'Unknown error'} (${code || 'NO_CODE'})`, + SOCKET_HANGUP_RETRY: (path: string, delay: number, attempt: number, max: number) => + `Socket hang up detected. Retrying ${path || 'unknown'} with ${delay} ms delay (attempt ${attempt}/${max})`, + }, + + // Plugin messages (plugins.ts) + PLUGINS: { + LOAD_CALLED: 'Initializing plugin load...', + METHOD_LOADED: (method: string, name: string) => `${method} loaded from ${name} successfully!`, + METHOD_NOT_FOUND: (method: string, name: string) => `${method} not found in ${name}.`, + LOAD_SUCCESS: 'All plugins loaded successfully.', + LOAD_ERROR: 'An error occurred while loading plugins.', + LOAD_ERROR_DETAIL: (message: string) => `Failed to load plugins: ${message}`, + }, + + // Sync core messages (core/index.ts) + SYNC_CORE: { + START: 'Sync core: Start invoked.', + ENVIRONMENT: (env: string) => `Current environment: ${env}`, + TOKEN_FOUND: 'Sync token found in the checkpoint file:', + TOKEN_USING: 'Using sync token:', + FILE_NOT_FOUND: (path: string) => `File not found: ${path}`, + FILE_READ_ERROR: (err: any) => `An error occurred while reading the file: ${err}`, + POKE_INVOKED: 'Poke command invoked.', + POKE_NOTIFICATION: 'Received "contentstack sync" notification.', + POKE_ERROR: 'Error during poke operation.', + CHECK_CALLED: (sqStatus: boolean, wqStatus: boolean) => + `Check initiated. SQ status: ${sqStatus}, WQ status: ${wqStatus}.`, + CHECK_COMPLETE: (cooloff: number) => + `Sync completed. SQ flag updated. Cooloff duration: ${cooloff}.`, + CHECK_ERROR: 'Error during check operation.', + CHECK_RECOVERED: 'Check recovered from errors.', + CHECK_FAILED: 'Check failed due to an error.', + SYNC_STARTED: 'Sync process started.', + SYNC_TOKEN_OBJECT: 'Token object received for sync.', + SYNC_ERROR: 'Error during sync operation.', + SYNC_LOCKED: 'Contentstack sync is locked.', + SYNC_UNLOCKED: 'Contentstack sync is unlocked.', + FIRE_CALLED: (req: any) => `Fire operation triggered with: ${JSON.stringify(req)}`, + FIRE_COMPLETE: (itemCount: number) => `Fire operation completed. Items received: ${itemCount}.`, + API_CALL_CT: (uid: string) => `API call initiated for content type: ${uid}.`, + ERROR_MAP: 'Error [map]: Failed to fetch content type schema.', + ERROR_MAP_RESOLVE: 'Error [mapResolve]: Unable to resolve mapping.', + ERROR_FILTER_ITEMS: 'Error [filterItems]: Unable to filter items.', + ERROR_FIRE: 'Error during fire operation.', + REFIRE_CALLED: (req: any) => `Re-fire operation triggered with: ${JSON.stringify(req)}`, + CHECKPOINT_LOCKDOWN: 'Checkpoint: lockdown has been invoked', + }, + + // Main index messages (index.ts) + INDEX: { + ASSET_STORE_INIT: 'Asset store instance initialized successfully.', + CONTENT_STORE_INIT: 'Content store instance initialized successfully.', + SYNC_MANAGER_INIT: 'Sync manager initialized successfully.', + SYNC_UTILITY_STARTED: 'Contentstack sync utility started successfully.', + NOTIFICATION: (action: string, item: any) => `Notification received: ${action} – ${JSON.stringify(item)}`, + }, + + // Token management messages (token-management.ts) + TOKEN: { + CHECKPOINT_READ: (path: string) => `Checkpoint read from file: ${path}`, + TOKEN_READ: (path: string) => `Token retrieved: ${path}`, + SAVE_TOKEN: (name: string) => `Saving token with name: ${name}`, + SAVE_CHECKPOINT: (name: string) => `Saving checkpoint token with name: ${name}`, + LEDGER_CHECK: (file: string, exists: boolean) => `Ledger file check: ${file} exists? → ${exists}`, + }, + + // Queue messages (q.ts) + QUEUE: { + CONSTRUCTOR: 'Core \'Q\' constructor initiated.', + CONTENT_TYPE_RECEIVED: (ctUid: string, type: string) => + `Received content type '${ctUid}' for '${type}'`, + ERROR_HANDLER_CALLED: (obj: any) => `Error handler invoked with: ${JSON.stringify(obj)}`, + ERROR_IN_HANDLER: 'An error occurred in the error handler.', + NEXT_CALLED: (inProgress: boolean, qLength: number) => + `Calling 'next'. Current progress status: ${inProgress}, Queue length: ${qLength}.`, + EXECUTING: (data: any) => `Executing queue item: ${JSON.stringify(data)}`, + BEFORE_PLUGINS: 'Before-action plugins executed successfully.', + ACTION_COMPLETE: (action: string) => `Completed '${action}' on connector successfully.`, + CONNECTOR_CALLED: 'Connector instance invoked successfully.', + AFTER_PLUGINS: 'After-action plugins executed successfully.', + }, + + // File system messages (fs.ts) + FS: { + WRITE_FILE: (path: string) => `Writing file to: ${path}`, + READ_FILE: (path: string) => `Reading file from: ${path}`, + READ_FILE_SYNC: (path: string) => `Reading file synchronously from: ${path}`, + MKDIR: (path: string) => `Creating directory: ${path}`, + INVALID_READ: (path: string) => `Invalid 'read' operation on file. Expected ${path} to be of type 'file'!`, + }, + + // Utility messages (util/index.ts) + UTIL: { + ITEM_TYPE_MISMATCH: 'The item\'s type did not match any expected case.', + ITEM_SERIALIZED: (item: any) => `Serialized item: ${JSON.stringify(item)}`, + ONLY_ASSETS: (lastCt: string) => + `Only assets were found in the SYNC API response. Last content type: ${lastCt}.`, + ONLY_CT_EVENTS: (lastCt: string) => + `Only content type events were found in the SYNC API response. Last content type: ${lastCt}.`, + ASSETS_AND_CT: (lastCt: string) => + `Assets and content types were found in the SYNC API response. Last content type: ${lastCt}.`, + MIXED_CT: (lastCt: string) => + `Mixed content types were found in the SYNC API response. Last content type: ${lastCt}.`, + FILE_NOT_TYPE: (file: string) => `${file} is not of type file`, + UNABLE_TO_FIND_PLUGIN: (plugin: any) => `Unable to find plugin: ${JSON.stringify(plugin)}`, + PLUGIN_PATH_NOT_EXIST: (path: string) => `${path} does not exist!`, + }, + + // Unprocessible items messages (unprocessible.ts) + UNPROCESSIBLE: { + WRITE_FAILED: (data: any, file: string, error: any) => + `Failed to write data to ${file}: ${JSON.stringify(data)}. Error: ${error}`, + READ_FAILED: (path: string) => `Failed to read file from: ${path}`, + WRITE_OBJECT_FAILED: (file: string, data: any, error: any) => + `Failed to write object to ${file}: ${JSON.stringify(data)}. Error: ${error}`, + }, +} + +/** + * @description Logger level constants + */ +export const LOG_LEVELS = { + DEBUG: 'debug', + INFO: 'info', + WARN: 'warn', + ERROR: 'error', + LOG: 'log', +} + +/** + * @description Error codes + */ +export const ERROR_CODES = { + ILLEGAL_CONTENT_TYPE_CALL: 'ICTC', + INVALID_OPERATION_ON_READ_FILE: 'IOORF', + INVALID_OPERATION_ON_READ_FILE_SYNC: 'IOORFS', + MAX_RETRY_LIMIT_EXCEEDED: 'Max retry limit exceeded!', + REQUEST_TIMEOUT: 'Request timeout', +} + diff --git a/src/util/unprocessible.ts b/src/util/unprocessible.ts index 2e5d7a9..7b52fc6 100644 --- a/src/util/unprocessible.ts +++ b/src/util/unprocessible.ts @@ -9,6 +9,7 @@ import { getConfig } from '../index' import { existsSync, readFile, writeFile } from './fs' import { getFile } from './index' import { logger } from './logger' +import { MESSAGES } from './messages' const counter = { failed: 0, @@ -77,13 +78,13 @@ export const saveFilteredItems = (items, name, token) => { return writeFile(file, JSON.stringify(loggedItems)).then(resolve).catch((error) => { // failed to log failed items - logger.error(`Failed to write ${JSON.stringify(loggedItems)} at ${error}`) + logger.error(MESSAGES.UNPROCESSIBLE.WRITE_FAILED(loggedItems, file, error)) logger.error(error) return resolve('') }) }).catch((error) => { - logger.error(`Failed to read file from path ${fail}`) + logger.error(MESSAGES.UNPROCESSIBLE.READ_FAILED(file)) logger.error(error) return resolve('') @@ -91,7 +92,7 @@ export const saveFilteredItems = (items, name, token) => { } return writeFile(file, JSON.stringify([objDetails])).then(resolve).catch((error) => { - logger.error(`Failed while writing ${JSON.stringify(objDetails)} at ${file}`) + logger.error(MESSAGES.UNPROCESSIBLE.WRITE_OBJECT_FAILED(file, objDetails, error)) logger.error(error) return resolve('') From acdeceac7d6013bf59b5d08a3207d80ac8f1e11c Mon Sep 17 00:00:00 2001 From: "harshitha.d" Date: Fri, 7 Nov 2025 17:26:56 +0530 Subject: [PATCH 2/2] bump version to 2.1.3 in package.json and package-lock.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3944f58..afe65bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/datasync-manager", - "version": "2.1.2", + "version": "2.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/datasync-manager", - "version": "2.1.2", + "version": "2.1.3", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.1", diff --git a/package.json b/package.json index dc7e4a9..b3eb346 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/datasync-manager", "author": "Contentstack LLC ", - "version": "2.1.2", + "version": "2.1.3", "description": "The primary module of Contentstack DataSync. Syncs Contentstack data with your server using Contentstack Sync API", "main": "dist/index.js", "dependencies": {