diff --git a/.talismanrc b/.talismanrc index 0fd8d87921..ed306764ca 100644 --- a/.talismanrc +++ b/.talismanrc @@ -44,7 +44,17 @@ fileignoreconfig: - filename: packages/contentstack-variants/src/import/audiences.ts checksum: f24697ef86e928bb4d16f93c021b647639cc344a7f02463d79d69f9434ebed56 - filename: packages/contentstack-variants/src/import/events.ts - checksum: 6cb014b5518ffe204a9f894ad801c05e2ef91a1692049168f74dd12a224363c4 + checksum: 88256a99c8ff8d6904df2e3767b39f4761d35ce680b3cabd712c33889bd02fca - filename: packages/contentstack-import/src/import/modules/personalize.ts checksum: 1311a613177160637e21b3983b281b384c2cb15837d001a398b67afef30a393a +- filename: packages/contentstack-export/src/export/modules/environments.ts + checksum: fd33318628321583dbeedd70ba7ba97f1e167d364dd26847771d745db295b16f +- filename: packages/contentstack-import/src/import/modules/environments.ts + checksum: 25ec3da4b218c5bbabcfa1af59f26d62e99110bf361a77aab30bfa3ab402da05 +- filename: packages/contentstack-variants/src/utils/constants.ts + checksum: 0ceef8ec8489a05d8ecf07cfa7e92575b0da7d5a6c0ed65b64f46d23aab7074d +- filename: packages/contentstack-export/src/utils/marketplace-app-helper.ts + checksum: fcd17c120a0359baeb61b7bd0f8d1ace2662f7f7293d355867f578312fe3a1a0 +- filename: packages/contentstack-variants/src/import/variant-entries.ts + checksum: 6e645a3d95903058f32306d306912353272e86e60571919a34125a9cd7b69a59 version: "1.0" diff --git a/packages/contentstack-export/src/export/modules/assets.ts b/packages/contentstack-export/src/export/modules/assets.ts index 33e7753839..f774f2c6eb 100644 --- a/packages/contentstack-export/src/export/modules/assets.ts +++ b/packages/contentstack-export/src/export/modules/assets.ts @@ -24,6 +24,7 @@ import { import config from '../../config'; import { ModuleClassParams } from '../../types'; import BaseClass, { CustomPromiseHandler, CustomPromiseHandlerInput } from './base-class'; +import { PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; export default class ExportAssets extends BaseClass { private assetsRootPath: string; @@ -33,8 +34,8 @@ export default class ExportAssets extends BaseClass { constructor({ exportConfig, stackAPIClient }: ModuleClassParams) { super({ exportConfig, stackAPIClient }); - this.exportConfig.context.module = 'assets'; - this.currentModuleName = 'Assets'; + this.exportConfig.context.module = MODULE_CONTEXTS.ASSETS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.ASSETS]; } get commonQueryParam(): Record { @@ -64,41 +65,59 @@ export default class ExportAssets extends BaseClass { // Add sub-processes if (typeof assetsFolderCount === 'number' && assetsFolderCount > 0) { - progress.addProcess('Folders', assetsFolderCount); + progress.addProcess(PROCESS_NAMES.ASSET_FOLDERS, assetsFolderCount); } if (typeof assetsCount === 'number' && assetsCount > 0) { - progress.addProcess('Metadata', assetsCount); - progress.addProcess('Downloads', assetsCount); + progress.addProcess(PROCESS_NAMES.ASSET_METADATA, assetsCount); + progress.addProcess(PROCESS_NAMES.ASSET_DOWNLOADS, assetsCount); } try { // Process asset folders if (typeof assetsFolderCount === 'number' && assetsFolderCount > 0) { - progress.startProcess('Folders').updateStatus('Fetching folder structure...', 'Folders'); + progress + .startProcess(PROCESS_NAMES.ASSET_FOLDERS) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.ASSET_FOLDERS].FETCHING, + PROCESS_NAMES.ASSET_FOLDERS, + ); await this.getAssetsFolders(assetsFolderCount); - progress.completeProcess('Folders', true); + progress.completeProcess(PROCESS_NAMES.ASSET_FOLDERS, true); } // Process asset metadata if (typeof assetsCount === 'number' && assetsCount > 0) { - progress.startProcess('Metadata').updateStatus('Fetching asset information...', 'Metadata'); + progress + .startProcess(PROCESS_NAMES.ASSET_METADATA) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.ASSET_METADATA].FETCHING, + PROCESS_NAMES.ASSET_METADATA, + ); await this.getAssets(assetsCount); - progress.completeProcess('Metadata', true); + progress.completeProcess(PROCESS_NAMES.ASSET_METADATA, true); } // Get versioned assets if (!isEmpty(this.versionedAssets) && this.assetConfig.includeVersionedAssets) { log.debug('Fetching versioned assets metadata...', this.exportConfig.context); - progress.updateStatus('Processing versioned assets...', 'Metadata'); + progress.updateStatus( + PROCESS_STATUS[PROCESS_NAMES.ASSET_METADATA].FETCHING_VERSION, + PROCESS_NAMES.ASSET_METADATA, + ); await this.getVersionedAssets(); } // Download all assets if (typeof assetsCount === 'number' && assetsCount > 0) { - progress.startProcess('Downloads').updateStatus('Downloading asset files...', 'Downloads'); + progress + .startProcess(PROCESS_NAMES.ASSET_DOWNLOADS) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.ASSET_DOWNLOADS].DOWNLOADING, + PROCESS_NAMES.ASSET_DOWNLOADS, + ); log.debug('Starting download of all assets...', this.exportConfig.context); await this.downloadAssets(); - progress.completeProcess('Downloads', true); + progress.completeProcess(PROCESS_NAMES.ASSET_DOWNLOADS, true); } this.completeProgress(true); @@ -128,13 +147,23 @@ export default class ExportAssets extends BaseClass { if (!isEmpty(items)) { this.assetsFolder.push(...items); items.forEach((folder: any) => { - this.progressManager?.tick(true, `folder: ${folder.name || folder.uid}`, null, 'Folders'); + this.progressManager?.tick( + true, + `folder: ${folder.name || folder.uid}`, + null, + PROCESS_NAMES.ASSET_FOLDERS, + ); }); } }; const onReject = ({ error }: any) => { - this.progressManager?.tick(false, 'asset folder', error?.message || 'Failed to fetch folder', 'Folders'); + this.progressManager?.tick( + false, + 'asset folder', + error?.message || PROCESS_STATUS[PROCESS_NAMES.ASSET_FOLDERS].FAILED, + PROCESS_NAMES.ASSET_FOLDERS, + ); handleAndLogError(error, { ...this.exportConfig.context }); }; @@ -197,7 +226,12 @@ export default class ExportAssets extends BaseClass { } const onReject = ({ error }: any) => { - this.progressManager?.tick(false, 'asset', error?.message || 'Failed to fetch asset', 'Metadata'); + this.progressManager?.tick( + false, + 'asset', + error?.message || PROCESS_STATUS[PROCESS_NAMES.ASSET_METADATA].FAILED, + PROCESS_NAMES.ASSET_METADATA, + ); handleAndLogError(error, { ...this.exportConfig.context }, messageHandler.parse('ASSET_QUERY_FAILED')); }; @@ -219,7 +253,12 @@ export default class ExportAssets extends BaseClass { fs?.writeIntoFile(items, { mapKeyVal: true }); // Track progress for each asset with process name items.forEach((asset: any) => { - this.progressManager?.tick(true, `asset: ${asset.filename || asset.uid}`, null, 'Metadata'); + this.progressManager?.tick( + true, + `asset: ${asset.filename || asset.uid}`, + null, + PROCESS_NAMES.ASSET_METADATA, + ); }); } }; @@ -418,7 +457,12 @@ export default class ExportAssets extends BaseClass { } else { data.pipe(assetWriterStream); } - this.progressManager?.tick(true, `Downloaded asset: ${asset.filename || asset.uid}`, null, 'Downloads'); + this.progressManager?.tick( + true, + `Downloaded asset: ${asset.filename || asset.uid}`, + null, + PROCESS_NAMES.ASSET_DOWNLOADS, + ); log.success(messageHandler.parse('ASSET_DOWNLOAD_SUCCESS', asset.filename, asset.uid), this.exportConfig.context); }; @@ -428,7 +472,7 @@ export default class ExportAssets extends BaseClass { false, `Failed to download asset: ${asset.filename || asset.uid}`, null, - 'Downloads', + PROCESS_NAMES.ASSET_DOWNLOADS, ); handleAndLogError( error, diff --git a/packages/contentstack-export/src/export/modules/content-types.ts b/packages/contentstack-export/src/export/modules/content-types.ts index b5127a09f1..28c6f1cbdd 100644 --- a/packages/contentstack-export/src/export/modules/content-types.ts +++ b/packages/contentstack-export/src/export/modules/content-types.ts @@ -2,8 +2,8 @@ import * as path from 'path'; import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePath } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil, executeTask } from '../../utils'; import { ExportConfig, ModuleClassParams } from '../../types'; +import { fsUtil, executeTask, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ContentTypesExport extends BaseClass { private stackAPIClient: ReturnType; @@ -52,8 +52,8 @@ export default class ContentTypesExport extends BaseClass { sanitizePath(this.contentTypesConfig.dirName), ); this.contentTypes = []; - this.exportConfig.context.module = 'content-types'; - this.currentModuleName = 'Content Types'; + this.exportConfig.context.module = MODULE_CONTEXTS.CONTENT_TYPES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.CONTENT_TYPES]; } async start() { diff --git a/packages/contentstack-export/src/export/modules/custom-roles.ts b/packages/contentstack-export/src/export/modules/custom-roles.ts index 6b5b489fa0..19ec0417a0 100644 --- a/packages/contentstack-export/src/export/modules/custom-roles.ts +++ b/packages/contentstack-export/src/export/modules/custom-roles.ts @@ -6,7 +6,13 @@ import { resolve as pResolve } from 'node:path'; import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil } from '../../utils'; +import { + fsUtil, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, +} from '../../utils'; import { CustomRoleConfig, ModuleClassParams } from '../../types'; export default class ExportCustomRoles extends BaseClass { @@ -25,8 +31,8 @@ export default class ExportCustomRoles extends BaseClass { this.existingRoles = { Admin: 1, Developer: 1, 'Content Manager': 1 }; this.localesMap = {}; this.sourceLocalesMap = {}; - this.exportConfig.context.module = 'custom-roles'; - this.currentModuleName = 'Custom Roles'; + this.exportConfig.context.module = MODULE_CONTEXTS.CUSTOM_ROLES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.CUSTOM_ROLES]; } async start(): Promise { @@ -66,21 +72,36 @@ export default class ExportCustomRoles extends BaseClass { // Create nested progress manager const progress = this.createNestedProgress(this.currentModuleName) - .addProcess('Fetch Roles', totalRoles) - .addProcess('Fetch Locales', totalLocales) - .addProcess('Process Mappings', 1); - - progress.startProcess('Fetch Roles').updateStatus('Fetching custom roles...', 'Fetch Roles'); + .addProcess(PROCESS_NAMES.FETCH_ROLES, totalRoles) + .addProcess(PROCESS_NAMES.FETCH_LOCALES, totalLocales) + .addProcess(PROCESS_NAMES.PROCESS_MAPPINGS, 1); + + progress + .startProcess(PROCESS_NAMES.FETCH_ROLES) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.FETCH_ROLES].FETCHING, + PROCESS_NAMES.FETCH_ROLES, + ); await this.getCustomRoles(); - progress.completeProcess('Fetch Roles', true); + progress.completeProcess(PROCESS_NAMES.FETCH_ROLES, true); - progress.startProcess('Fetch Locales').updateStatus('Fetching locales...', 'Fetch Locales'); + progress + .startProcess(PROCESS_NAMES.FETCH_LOCALES) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.FETCH_LOCALES].FETCHING, + PROCESS_NAMES.FETCH_LOCALES, + ); await this.getLocales(); - progress.completeProcess('Fetch Locales', true); + progress.completeProcess(PROCESS_NAMES.FETCH_LOCALES, true); - progress.startProcess('Process Mappings').updateStatus('Processing role-locale mappings...', 'Process Mappings'); + progress + .startProcess(PROCESS_NAMES.PROCESS_MAPPINGS) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.PROCESS_MAPPINGS].PROCESSING, + PROCESS_NAMES.PROCESS_MAPPINGS, + ); await this.getCustomRolesLocales(); - progress.completeProcess('Process Mappings', true); + progress.completeProcess(PROCESS_NAMES.PROCESS_MAPPINGS, true); log.debug( `Custom roles export completed. Total custom roles: ${Object.keys(this.customRoles).length}`, @@ -124,7 +145,7 @@ export default class ExportCustomRoles extends BaseClass { log.info(messageHandler.parse('ROLES_EXPORTING_ROLE', role.name), this.exportConfig.context); this.customRoles[role.uid] = role; - this.progressManager?.tick(true, `role: ${role.name}`, null, 'Fetch Roles'); + this.progressManager?.tick(true, `role: ${role.name}`, null, PROCESS_NAMES.FETCH_ROLES); }); const customRolesFilePath = pResolve(this.rolesFolderPath, this.customRolesConfig.fileName); @@ -153,7 +174,7 @@ export default class ExportCustomRoles extends BaseClass { this.sourceLocalesMap[locale.uid] = locale; // Track progress for each locale - this.progressManager?.tick(true, `locale: ${locale.name}`, null, 'Fetch Locales'); + this.progressManager?.tick(true, `locale: ${locale.name}`, null, PROCESS_NAMES.FETCH_LOCALES); } log.debug(`Mapped ${Object.keys(this.sourceLocalesMap).length} locales`, this.exportConfig.context); @@ -198,6 +219,6 @@ export default class ExportCustomRoles extends BaseClass { } // Track progress for mapping completion - this.progressManager?.tick(true, 'role-locale mappings', null, 'Process Mappings'); + this.progressManager?.tick(true, 'role-locale mappings', null, PROCESS_NAMES.PROCESS_MAPPINGS); } } diff --git a/packages/contentstack-export/src/export/modules/entries.ts b/packages/contentstack-export/src/export/modules/entries.ts index 51554499b7..b8e9974dc6 100644 --- a/packages/contentstack-export/src/export/modules/entries.ts +++ b/packages/contentstack-export/src/export/modules/entries.ts @@ -3,7 +3,13 @@ import { ContentstackClient, FsUtility, handleAndLogError, messageHandler, log } import { Export, ExportProjects } from '@contentstack/cli-variants'; import { sanitizePath } from '@contentstack/cli-utilities'; -import { fsUtil } from '../../utils'; +import { + fsUtil, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, +} from '../../utils'; import BaseClass, { ApiOptions } from './base-class'; import { ExportConfig, ModuleClassParams } from '../../types'; @@ -52,21 +58,20 @@ export default class EntriesExport extends BaseClass { 'schema.json', ); this.projectInstance = new ExportProjects(this.exportConfig); - this.exportConfig.context.module = 'entries'; - this.currentModuleName = 'Entries'; + this.exportConfig.context.module = MODULE_CONTEXTS.ENTRIES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.ENTRIES]; } async start() { try { log.debug('Starting entries export process...', this.exportConfig.context); - + // Initial analysis with loading spinner - const [locales, contentTypes, entryRequestOptions, totalEntriesCount, variantInfo] = await this.withLoadingSpinner( - 'ENTRIES: Analyzing content structure and entries...', - async () => { + const [locales, contentTypes, entryRequestOptions, totalEntriesCount, variantInfo] = + await this.withLoadingSpinner('ENTRIES: Analyzing content structure and entries...', async () => { const locales = fsUtil.readFile(this.localesFilePath) as Array>; const contentTypes = fsUtil.readFile(this.schemaFilePath) as Array>; - + if (!Array.isArray(locales) || locales?.length === 0) { log.debug(`No locales found in ${this.localesFilePath}`, this.exportConfig.context); } else { @@ -77,7 +82,10 @@ export default class EntriesExport extends BaseClass { log.info(messageHandler.parse('CONTENT_TYPE_NO_TYPES'), this.exportConfig.context); return [locales, contentTypes, [], 0, null]; } - log.debug(`Loaded ${contentTypes?.length} content types from ${this.schemaFilePath}`, this.exportConfig.context); + log.debug( + `Loaded ${contentTypes?.length} content types from ${this.schemaFilePath}`, + this.exportConfig.context, + ); // Create entry request objects const entryRequestOptions = this.createRequestObjects(locales, contentTypes); @@ -87,11 +95,10 @@ export default class EntriesExport extends BaseClass { ); // Get total entries count for better progress tracking - const totalEntriesCount = await this.getTotalEntriesCount(entryRequestOptions); + const totalEntriesCount = await this.getTotalEntriesCount(entryRequestOptions); const variantInfo = await this.setupVariantExport(); return [locales, contentTypes, entryRequestOptions, totalEntriesCount, variantInfo]; - } - ); + }); if (contentTypes?.length === 0) { return; @@ -100,22 +107,24 @@ export default class EntriesExport extends BaseClass { // Create nested progress manager const progress = this.createNestedProgress(this.currentModuleName); - // Add sub-processes + // Add sub-processes if (totalEntriesCount > 0) { - progress.addProcess('Entries', totalEntriesCount); - + progress.addProcess(PROCESS_NAMES.ENTRIES, totalEntriesCount); + if (this.entriesConfig.exportVersions) { - progress.addProcess('Entry Versions', totalEntriesCount); + progress.addProcess(PROCESS_NAMES.ENTRY_VERSIONS, totalEntriesCount); } - + if (this.exportVariantEntry) { - progress.addProcess('Variant Entries', 0); + progress.addProcess(PROCESS_NAMES.VARIANT_ENTRIES, 0); } } // Process entry collections if (totalEntriesCount > 0) { - progress.startProcess('Entries').updateStatus('Processing entry collections...', 'Entries'); + progress + .startProcess(PROCESS_NAMES.ENTRIES) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.ENTRIES].PROCESSING, PROCESS_NAMES.ENTRIES); for (let entryRequestOption of entryRequestOptions) { try { @@ -127,33 +136,36 @@ export default class EntriesExport extends BaseClass { this.entriesFileHelper?.completeFile(true); log.success( - messageHandler.parse('ENTRIES_EXPORT_COMPLETE', entryRequestOption.contentType, entryRequestOption.locale), + messageHandler.parse( + 'ENTRIES_EXPORT_COMPLETE', + entryRequestOption.contentType, + entryRequestOption.locale, + ), this.exportConfig.context, ); } catch (error) { this.progressManager?.tick( false, `${entryRequestOption.contentType}:${entryRequestOption.locale}`, - error?.message || 'Failed to export entries', - 'Entries', + error?.message || PROCESS_STATUS[PROCESS_NAMES.ENTRIES].FAILED, + PROCESS_NAMES.ENTRIES, ); throw error; } } - progress.completeProcess('Entries', true); + progress.completeProcess(PROCESS_NAMES.ENTRIES, true); if (this.entriesConfig.exportVersions) { - progress.completeProcess('Entry Versions', true); + progress.completeProcess(PROCESS_NAMES.ENTRY_VERSIONS, true); } - + if (this.exportVariantEntry) { - progress.completeProcess('Variant Entries', true); + progress.completeProcess(PROCESS_NAMES.VARIANT_ENTRIES, true); } } this.completeProgress(true); log.success(messageHandler.parse('ENTRIES_EXPORT_SUCCESS'), this.exportConfig.context); - } catch (error) { handleAndLogError(error, { ...this.exportConfig.context }); this.completeProgress(false, error?.message || 'Entries export failed'); @@ -162,9 +174,9 @@ export default class EntriesExport extends BaseClass { async getTotalEntriesCount(entryRequestOptions: Array>): Promise { log.debug('Calculating total entries count for progress tracking...', this.exportConfig.context); - + let totalCount = 0; - + try { for (const option of entryRequestOptions) { const countQuery = { @@ -173,34 +185,27 @@ export default class EntriesExport extends BaseClass { include_count: true, query: { locale: option.locale }, }; - + this.applyQueryFilters(countQuery, 'entries'); - + try { - const response = await this.stackAPIClient - .contentType(option.contentType) - .entry() - .query(countQuery) - .find(); - + const response = await this.stackAPIClient.contentType(option.contentType).entry().query(countQuery).find(); + const count = response.count || 0; totalCount += count; log.debug( `Content type ${option.contentType} (${option.locale}): ${count} entries`, - this.exportConfig.context + this.exportConfig.context, ); } catch (error) { - log.debug( - `Failed to get count for ${option.contentType}:${option.locale}`, - this.exportConfig.context - ); + log.debug(`Failed to get count for ${option.contentType}:${option.locale}`, this.exportConfig.context); } } } catch (error) { log.debug('Error calculating total entries count, using collection count as fallback', this.exportConfig.context); return entryRequestOptions.length; } - + log.debug(`Total entries count: ${totalCount}`, this.exportConfig.context); return totalCount; } @@ -209,9 +214,9 @@ export default class EntriesExport extends BaseClass { if (!this.exportConfig.personalizationEnabled) { return null; } - + log.debug('Personalization is enabled, checking for variant entries...', this.exportConfig.context); - + try { const project = await this.projectInstance.projects({ connectedStackApiKey: this.exportConfig.apiKey }); @@ -219,7 +224,7 @@ export default class EntriesExport extends BaseClass { const project_id = project[0].uid; this.exportVariantEntry = true; log.debug(`Found project with ID: ${project_id}, enabling variant entry export`, this.exportConfig.context); - + this.variantEntries = new Export.VariantEntries(Object.assign(this.exportConfig, { project_id })); return { project_id }; } @@ -227,7 +232,7 @@ export default class EntriesExport extends BaseClass { log.debug('Failed to setup variant export', this.exportConfig.context); handleAndLogError(error, { ...this.exportConfig.context }); } - + return null; } @@ -329,7 +334,7 @@ export default class EntriesExport extends BaseClass { // Track progress for individual entries entriesSearchResponse.items.forEach((entry: any) => { - this.progressManager?.tick(true, `entry: ${entry.uid}`, null, 'Entries'); + this.progressManager?.tick(true, `entry: ${entry.uid}`, null, PROCESS_NAMES.ENTRIES); }); if (this.entriesConfig.exportVersions) { @@ -357,14 +362,17 @@ export default class EntriesExport extends BaseClass { if (this.variantEntries && typeof this.variantEntries.setParentProgressManager === 'function') { this.variantEntries.setParentProgressManager(this.progressManager); } - + await this.variantEntries.exportVariantEntry({ locale: options.locale, contentTypeUid: options.contentType, entries: entriesSearchResponse.items, }); - - log.debug(`Successfully exported variant entries for ${entriesSearchResponse.items.length} entries`, this.exportConfig.context); + + log.debug( + `Successfully exported variant entries for ${entriesSearchResponse.items.length} entries`, + this.exportConfig.context, + ); } catch (error) { log.debug('Failed to export variant entries', this.exportConfig.context); } @@ -399,7 +407,7 @@ export default class EntriesExport extends BaseClass { log.debug(`Writing versioned entry to: ${versionFilePath}`, this.exportConfig.context); fsUtil.writeFile(versionFilePath, response); // Track version progress if the process exists - this.progressManager?.tick(true, `version: ${entry.uid}`, null, 'Entry Versions'); + this.progressManager?.tick(true, `version: ${entry.uid}`, null, PROCESS_NAMES.ENTRY_VERSIONS); log.success( messageHandler.parse('ENTRIES_VERSIONED_EXPORT_SUCCESS', options.contentType, entry.uid, options.locale), this.exportConfig.context, @@ -411,8 +419,8 @@ export default class EntriesExport extends BaseClass { this.progressManager?.tick( false, `version: ${uid}`, - error?.message || 'Failed to fetch versions', - 'Entry Versions', + error?.message || PROCESS_STATUS[PROCESS_NAMES.ENTRY_VERSIONS].FAILED, + PROCESS_NAMES.ENTRY_VERSIONS, ); handleAndLogError( error, diff --git a/packages/contentstack-export/src/export/modules/environments.ts b/packages/contentstack-export/src/export/modules/environments.ts index f5a37d8941..ee46f46e73 100644 --- a/packages/contentstack-export/src/export/modules/environments.ts +++ b/packages/contentstack-export/src/export/modules/environments.ts @@ -4,8 +4,8 @@ import isEmpty from 'lodash/isEmpty'; import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil } from '../../utils'; import { EnvironmentConfig, ModuleClassParams } from '../../types'; +import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportEnvironments extends BaseClass { private environments: Record; @@ -21,8 +21,8 @@ export default class ExportEnvironments extends BaseClass { this.environments = {}; this.environmentConfig = exportConfig.modules.environments; this.qs = { include_count: true }; - this.exportConfig.context.module = 'environments'; - this.currentModuleName = 'Environments'; + this.exportConfig.context.module = MODULE_CONTEXTS.ENVIRONMENTS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.ENVIRONMENTS]; } async start(): Promise { diff --git a/packages/contentstack-export/src/export/modules/extensions.ts b/packages/contentstack-export/src/export/modules/extensions.ts index 73fbe26a03..00b52d0f6e 100644 --- a/packages/contentstack-export/src/export/modules/extensions.ts +++ b/packages/contentstack-export/src/export/modules/extensions.ts @@ -4,8 +4,8 @@ import { resolve as pResolve } from 'node:path'; import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil } from '../../utils'; import { ExtensionsConfig, ModuleClassParams } from '../../types'; +import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportExtensions extends BaseClass { private extensionsFolderPath: string; @@ -22,8 +22,8 @@ export default class ExportExtensions extends BaseClass { this.extensionConfig = exportConfig.modules.extensions; this.qs = { include_count: true }; this.applyQueryFilters(this.qs, 'extensions'); - this.exportConfig.context.module = 'extensions'; - this.currentModuleName = 'Extensions'; + this.exportConfig.context.module = MODULE_CONTEXTS.EXTENSIONS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.EXTENSIONS]; } async start(): Promise { diff --git a/packages/contentstack-export/src/export/modules/global-fields.ts b/packages/contentstack-export/src/export/modules/global-fields.ts index 31cec06a03..0d74f8da3b 100644 --- a/packages/contentstack-export/src/export/modules/global-fields.ts +++ b/packages/contentstack-export/src/export/modules/global-fields.ts @@ -1,15 +1,9 @@ import * as path from 'path'; -import { - ContentstackClient, - handleAndLogError, - messageHandler, - log, - sanitizePath, -} from '@contentstack/cli-utilities'; - -import { fsUtil } from '../../utils'; -import { ExportConfig, ModuleClassParams } from '../../types'; +import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePath } from '@contentstack/cli-utilities'; + import BaseClass from './base-class'; +import { ExportConfig, ModuleClassParams } from '../../types'; +import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class GlobalFieldsExport extends BaseClass { private stackAPIClient: ReturnType; @@ -50,8 +44,8 @@ export default class GlobalFieldsExport extends BaseClass { ); this.globalFields = []; this.applyQueryFilters(this.qs, 'global-fields'); - this.exportConfig.context.module = 'global-fields'; - this.currentModuleName = 'Global Fields'; + this.exportConfig.context.module = MODULE_CONTEXTS.GLOBAL_FIELDS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.GLOBAL_FIELDS]; } async start() { @@ -59,17 +53,14 @@ export default class GlobalFieldsExport extends BaseClass { log.debug('Starting global fields export process...', this.exportConfig.context); // Get global fields count and setup with loading spinner - const [totalCount] = await this.withLoadingSpinner( - 'GLOBAL-FIELDS: Analyzing global fields...', - async () => { - await fsUtil.makeDirectory(this.globalFieldsDirPath); - const countResponse = await this.stackAPIClient - .globalField() - .query({ ...this.qs, include_count: true, limit: 1 }) - .find(); - return [countResponse.count || 0]; - }, - ); + const [totalCount] = await this.withLoadingSpinner('GLOBAL-FIELDS: Analyzing global fields...', async () => { + await fsUtil.makeDirectory(this.globalFieldsDirPath); + const countResponse = await this.stackAPIClient + .globalField() + .query({ ...this.qs, include_count: true, limit: 1 }) + .find(); + return [countResponse.count || 0]; + }); if (totalCount === 0) { log.info(messageHandler.parse('GLOBAL_FIELDS_NOT_FOUND'), this.exportConfig.context); diff --git a/packages/contentstack-export/src/export/modules/labels.ts b/packages/contentstack-export/src/export/modules/labels.ts index bd01e17870..50a3aa7b56 100644 --- a/packages/contentstack-export/src/export/modules/labels.ts +++ b/packages/contentstack-export/src/export/modules/labels.ts @@ -4,8 +4,8 @@ import { resolve as pResolve } from 'node:path'; import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil } from '../../utils'; import { LabelConfig, ModuleClassParams } from '../../types'; +import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportLabels extends BaseClass { private labels: Record>; @@ -21,8 +21,8 @@ export default class ExportLabels extends BaseClass { this.labels = {}; this.labelConfig = exportConfig.modules.labels; this.qs = { include_count: true }; - this.exportConfig.context.module = 'labels'; - this.currentModuleName = 'Labels'; + this.exportConfig.context.module = MODULE_CONTEXTS.LABELS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.LABELS]; } async start(): Promise { diff --git a/packages/contentstack-export/src/export/modules/locales.ts b/packages/contentstack-export/src/export/modules/locales.ts index aefa941d0d..ad528194f1 100644 --- a/packages/contentstack-export/src/export/modules/locales.ts +++ b/packages/contentstack-export/src/export/modules/locales.ts @@ -1,9 +1,9 @@ import * as path from 'path'; import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePath } from '@contentstack/cli-utilities'; -import { fsUtil } from '../../utils'; import BaseClass from './base-class'; import { ExportConfig, ModuleClassParams } from '../../types'; +import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class LocaleExport extends BaseClass { private stackAPIClient: ReturnType; @@ -48,8 +48,8 @@ export default class LocaleExport extends BaseClass { ); this.locales = {}; this.masterLocale = {}; - this.exportConfig.context.module = 'locales'; - this.currentModuleName = 'Locales'; + this.exportConfig.context.module = MODULE_CONTEXTS.LOCALES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.LOCALES]; } async start() { diff --git a/packages/contentstack-export/src/export/modules/marketplace-apps.ts b/packages/contentstack-export/src/export/modules/marketplace-apps.ts index 78701e3c90..d303b9a981 100644 --- a/packages/contentstack-export/src/export/modules/marketplace-apps.ts +++ b/packages/contentstack-export/src/export/modules/marketplace-apps.ts @@ -17,9 +17,19 @@ import { handleAndLogError, } from '@contentstack/cli-utilities'; -import { fsUtil, getOrgUid, createNodeCryptoInstance, getDeveloperHubUrl } from '../../utils'; -import { ModuleClassParams, MarketplaceAppsConfig, ExportConfig, Installation, Manifest } from '../../types'; import BaseClass from './base-class'; +import { + fsUtil, + getOrgUid, + createNodeCryptoInstance, + getDeveloperHubUrl, + MODULE_CONTEXTS, + MODULE_NAMES, + PROCESS_NAMES, + PROCESS_STATUS, + askEncryptionKey, +} from '../../utils'; +import { ModuleClassParams, MarketplaceAppsConfig, ExportConfig, Installation, Manifest } from '../../types'; export default class ExportMarketplaceApps extends BaseClass { protected marketplaceAppConfig: MarketplaceAppsConfig; @@ -36,14 +46,14 @@ export default class ExportMarketplaceApps extends BaseClass { super({ exportConfig, stackAPIClient }); this.exportConfig = exportConfig; this.marketplaceAppConfig = exportConfig.modules.marketplace_apps; - this.exportConfig.context.module = 'marketplace-apps'; - this.currentModuleName = 'Marketplace Apps'; + this.exportConfig.context.module = MODULE_CONTEXTS.MARKETPLACE_APPS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.MARKETPLACE_APPS]; } async start(): Promise { try { log.debug('Starting marketplace apps export process...', this.exportConfig.context); - + if (!isAuthenticated()) { cliux.print( 'WARNING!!! To export Marketplace apps, you must be logged in. Please check csdx auth:login --help to log in', @@ -53,42 +63,55 @@ export default class ExportMarketplaceApps extends BaseClass { } // Initial setup and analysis with loading spinner - const [appsCount] = await this.withLoadingSpinner( - 'MARKETPLACE-APPS: Analyzing marketplace apps...', - async () => { - await this.setupPaths(); - const appsCount = await this.getAppsCount(); - return [appsCount]; - } - ); + const [appsCount] = await this.withLoadingSpinner('MARKETPLACE-APPS: Analyzing marketplace apps...', async () => { + await this.setupPaths(); + const appsCount = await this.getAppsCount(); + return [appsCount]; + }); if (appsCount === 0) { log.info(messageHandler.parse('MARKETPLACE_APPS_NOT_FOUND'), this.exportConfig.context); return; } + // Handle encryption key prompt BEFORE starting progress + if (!this.exportConfig.forceStopMarketplaceAppsPrompt) { + log.debug('Validating security configuration before progress start', this.exportConfig.context); + cliux.print('\n'); + await askEncryptionKey(this.exportConfig); + this.nodeCrypto = await createNodeCryptoInstance(this.exportConfig); + + cliux.print('\n'); + } + // Create nested progress manager const progress = this.createNestedProgress(this.currentModuleName); // Add processes based on what we found - progress.addProcess('Fetch', appsCount); - progress.addProcess('Fetch config & manifest', appsCount); // Manifests and configurations + progress.addProcess(PROCESS_NAMES.FETCH_APPS, appsCount); + progress.addProcess(PROCESS_NAMES.FETCH_CONFIG_MANIFEST, appsCount); // Manifests and configurations // Fetch stack specific apps - progress.startProcess('Fetch').updateStatus('Fetching marketplace apps...', 'Fetch'); + progress + .startProcess(PROCESS_NAMES.FETCH_APPS) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.FETCH_APPS].FETCHING, PROCESS_NAMES.FETCH_APPS); await this.exportApps(); - progress.completeProcess('Fetch', true); + progress.completeProcess(PROCESS_NAMES.FETCH_APPS, true); // Process apps (manifests and configurations) if (this.installedApps.length > 0) { - progress.startProcess('Fetch config & manifest').updateStatus('Processing app manifests and configurations...', 'Fetch config & manifest'); + progress + .startProcess(PROCESS_NAMES.FETCH_CONFIG_MANIFEST) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.FETCH_CONFIG_MANIFEST].PROCESSING, + PROCESS_NAMES.FETCH_CONFIG_MANIFEST, + ); await this.getAppManifestAndAppConfig(); - progress.completeProcess('Fetch config & manifest', true); + progress.completeProcess(PROCESS_NAMES.FETCH_CONFIG_MANIFEST, true); } this.completeProgress(true); log.success('Marketplace apps export completed successfully', this.exportConfig.context); - } catch (error) { log.debug('Error occurred during marketplace apps export', this.exportConfig.context); handleAndLogError(error, { ...this.exportConfig.context }); @@ -103,13 +126,13 @@ export default class ExportMarketplaceApps extends BaseClass { this.marketplaceAppConfig.dirName, ); log.debug(`Marketplace apps folder path: ${this.marketplaceAppPath}`, this.exportConfig.context); - + await fsUtil.makeDirectory(this.marketplaceAppPath); log.debug('Created marketplace apps directory', this.exportConfig.context); - + this.developerHubBaseUrl = this.exportConfig.developerHubBaseUrl || (await getDeveloperHubUrl(this.exportConfig)); log.debug(`Developer hub base URL: ${this.developerHubBaseUrl}`, this.exportConfig.context); - + this.exportConfig.org_uid = await getOrgUid(this.exportConfig); this.query = { target_uids: this.exportConfig.source_stack }; log.debug(`Organization UID: ${this.exportConfig.org_uid}`, this.exportConfig.context); @@ -122,7 +145,7 @@ export default class ExportMarketplaceApps extends BaseClass { async getAppsCount(): Promise { log.debug('Fetching marketplace apps count...', this.exportConfig.context); - + try { const externalQuery = this.exportConfig.query?.modules['marketplace-apps']; if (externalQuery) { @@ -154,7 +177,7 @@ export default class ExportMarketplaceApps extends BaseClass { */ async exportApps(): Promise { log.debug('Starting apps export process...', this.exportConfig.context); - + await this.getStackSpecificApps(); log.debug(`Retrieved ${this.installedApps.length} stack-specific apps`, this.exportConfig.context); @@ -170,7 +193,7 @@ export default class ExportMarketplaceApps extends BaseClass { } return app; }); - + log.debug(`Processed ${this.installedApps.length} total marketplace apps`, this.exportConfig.context); } @@ -183,7 +206,7 @@ export default class ExportMarketplaceApps extends BaseClass { log.info(messageHandler.parse('MARKETPLACE_APPS_NOT_FOUND'), this.exportConfig.context); } else { log.debug(`Processing ${this.installedApps.length} installed apps`, this.exportConfig.context); - + for (const [index, app] of entries(this.installedApps)) { if (app.manifest.visibility === 'private') { log.debug(`Processing private app manifest: ${app.manifest.name}`, this.exportConfig.context); @@ -194,9 +217,14 @@ export default class ExportMarketplaceApps extends BaseClass { for (const [index, app] of entries(this.installedApps)) { log.debug(`Processing app configurations: ${app.manifest?.name || app.uid}`, this.exportConfig.context); await this.getAppConfigurations(+index, app); - + // Track progress for each app processed - this.progressManager?.tick(true, `app: ${app.manifest?.name || app.uid}`, null, 'Fetch config & manifest'); + this.progressManager?.tick( + true, + `app: ${app.manifest?.name || app.uid}`, + null, + PROCESS_NAMES.FETCH_CONFIG_MANIFEST, + ); } const marketplaceAppsFilePath = pResolve(this.marketplaceAppPath, this.marketplaceAppConfig.fileName); @@ -220,14 +248,20 @@ export default class ExportMarketplaceApps extends BaseClass { * app's manifest. */ async getPrivateAppsManifest(index: number, appInstallation: Installation) { - log.debug(`Fetching private app manifest for: ${appInstallation.manifest.name} (${appInstallation.manifest.uid})`, this.exportConfig.context); - + log.debug( + `Fetching private app manifest for: ${appInstallation.manifest.name} (${appInstallation.manifest.uid})`, + this.exportConfig.context, + ); + const manifest = await this.appSdk .marketplace(this.exportConfig.org_uid) .app(appInstallation.manifest.uid) .fetch({ include_oauth: true }) .catch((error) => { - log.debug(`Failed to fetch private app manifest for: ${appInstallation.manifest.name}`, this.exportConfig.context); + log.debug( + `Failed to fetch private app manifest for: ${appInstallation.manifest.name}`, + this.exportConfig.context, + ); handleAndLogError( error, { @@ -238,7 +272,10 @@ export default class ExportMarketplaceApps extends BaseClass { }); if (manifest) { - log.debug(`Successfully fetched private app manifest for: ${appInstallation.manifest.name}`, this.exportConfig.context); + log.debug( + `Successfully fetched private app manifest for: ${appInstallation.manifest.name}`, + this.exportConfig.context, + ); this.installedApps[index].manifest = manifest as unknown as Manifest; } } @@ -269,10 +306,14 @@ export default class ExportMarketplaceApps extends BaseClass { if (has(data, 'server_configuration') || has(data, 'configuration')) { log.debug(`Found configuration data for app: ${app}`, this.exportConfig.context); - + if (!this.nodeCrypto && (has(data, 'server_configuration') || has(data, 'configuration'))) { - log.debug(`Initializing NodeCrypto for app: ${app}`, this.exportConfig.context); this.nodeCrypto = await createNodeCryptoInstance(this.exportConfig); + + this.progressManager?.updateStatus( + PROCESS_STATUS[PROCESS_NAMES.FETCH_CONFIG_MANIFEST].PROCESSING, + PROCESS_NAMES.FETCH_CONFIG_MANIFEST, + ); } if (!isEmpty(data?.configuration)) { @@ -318,7 +359,7 @@ export default class ExportMarketplaceApps extends BaseClass { * the API. In this code, it is initially set to 0, indicating that no items should be skipped in */ async getStackSpecificApps(skip = 0) { - log.debug(`Fetching stack-specific apps with skip: ${skip}`, this.exportConfig.context); + log.debug(`Fetching stack-specific apps with skip: ${skip}`, this.exportConfig.context); const collection = await this.appSdk .marketplace(this.exportConfig.org_uid) .installation() @@ -333,7 +374,7 @@ export default class ExportMarketplaceApps extends BaseClass { if (collection) { const { items: apps, count } = collection; log.debug(`Fetched ${apps?.length || 0} apps out of total ${count}`, this.exportConfig.context); - + // NOTE Remove all the chain functions const installation = map(apps, (app) => omitBy(app, (val, _key) => { @@ -341,14 +382,14 @@ export default class ExportMarketplaceApps extends BaseClass { return false; }), ) as unknown as Installation[]; - + log.debug(`Processed ${installation.length} app installations`, this.exportConfig.context); - + // Track progress for each app fetched installation.forEach((app) => { - this.progressManager?.tick(true, `app: ${app.manifest?.name || app.uid}`, null, 'Fetch'); + this.progressManager?.tick(true, `app: ${app.manifest?.name || app.uid}`, null, PROCESS_NAMES.FETCH_APPS); }); - + this.installedApps = this.installedApps.concat(installation); if (count - (skip + 50) > 0) { diff --git a/packages/contentstack-export/src/export/modules/personalize.ts b/packages/contentstack-export/src/export/modules/personalize.ts index c6117bf033..933c08ee00 100644 --- a/packages/contentstack-export/src/export/modules/personalize.ts +++ b/packages/contentstack-export/src/export/modules/personalize.ts @@ -8,8 +8,9 @@ import { } from '@contentstack/cli-variants'; import { handleAndLogError, messageHandler, log, CLIProgressManager } from '@contentstack/cli-utilities'; -import { ModuleClassParams, ExportConfig } from '../../types'; import BaseClass from './base-class'; +import { ModuleClassParams, ExportConfig } from '../../types'; +import { MODULE_CONTEXTS, MODULE_NAMES, PROCESS_NAMES, PROCESS_STATUS } from '../../utils'; export default class ExportPersonalize extends BaseClass { public exportConfig: ExportConfig; @@ -23,18 +24,18 @@ export default class ExportPersonalize extends BaseClass { }; private readonly moduleDisplayMapper = { - events: 'Events', - attributes: 'Attributes', - audiences: 'Audiences', - experiences: 'Experiences', - }; + events: PROCESS_NAMES.PERSONALIZE_EVENTS, + attributes: PROCESS_NAMES.PERSONALIZE_ATTRIBUTES, + audiences: PROCESS_NAMES.PERSONALIZE_AUDIENCES, + experiences: PROCESS_NAMES.PERSONALIZE_EXPERIENCES, + } as const; constructor({ exportConfig, stackAPIClient }: ModuleClassParams) { super({ exportConfig, stackAPIClient }); this.exportConfig = exportConfig; this.personalizeConfig = exportConfig.modules.personalize; - this.exportConfig.context.module = 'personalize'; - this.currentModuleName = 'Personalize'; + this.exportConfig.context.module = MODULE_CONTEXTS.PERSONALIZE; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.PERSONALIZE]; } async start(): Promise { @@ -123,8 +124,11 @@ export default class ExportPersonalize extends BaseClass { } private addProjectProcess(progress: CLIProgressManager) { - progress.addProcess('Projects', 1); - log.debug('Added Projects process to personalize progress', this.exportConfig.context); + progress.addProcess(PROCESS_NAMES.PERSONALIZE_PROJECTS, 1); + log.debug( + `Added ${PROCESS_NAMES.PERSONALIZE_PROJECTS} process to personalize progress`, + this.exportConfig.context, + ); } private addModuleProcesses(progress: CLIProgressManager, moduleCount: number) { @@ -147,14 +151,19 @@ export default class ExportPersonalize extends BaseClass { } private async exportProjects(progress: CLIProgressManager) { - progress.startProcess('Projects').updateStatus('Exporting personalization projects...', 'Projects'); + progress + .startProcess(PROCESS_NAMES.PERSONALIZE_PROJECTS) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.PERSONALIZE_PROJECTS].EXPORTING, + PROCESS_NAMES.PERSONALIZE_PROJECTS, + ); log.debug('Starting projects export for personalization...', this.exportConfig.context); const projectsExporter = new ExportProjects(this.exportConfig); projectsExporter.setParentProgressManager(progress); await projectsExporter.start(); - progress.completeProcess('Projects', true); + progress.completeProcess(PROCESS_NAMES.PERSONALIZE_PROJECTS, true); } private async exportModules(progress: CLIProgressManager) { @@ -177,7 +186,12 @@ export default class ExportPersonalize extends BaseClass { const ModuleClass = this.moduleInstanceMapper[module]; if (ModuleClass) { - progress.startProcess(processName).updateStatus(`Exporting ${module}...`, processName); + progress + .startProcess(processName) + .updateStatus( + (PROCESS_STATUS as any)[processName]?.EXPORTING || `Exporting ${module}...`, + processName, + ); log.debug(`Starting export for module: ${module}`, this.exportConfig.context); if (this.exportConfig.personalizationEnabled) { diff --git a/packages/contentstack-export/src/export/modules/stack.ts b/packages/contentstack-export/src/export/modules/stack.ts index 2bb9e19149..d2bb149b06 100644 --- a/packages/contentstack-export/src/export/modules/stack.ts +++ b/packages/contentstack-export/src/export/modules/stack.ts @@ -3,7 +3,13 @@ import { resolve as pResolve } from 'node:path'; import { handleAndLogError, isAuthenticated, managementSDKClient, log } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil } from '../../utils'; +import { + fsUtil, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, +} from '../../utils'; import { StackConfig, ModuleClassParams } from '../../types'; export default class ExportStack extends BaseClass { @@ -23,8 +29,8 @@ export default class ExportStack extends BaseClass { this.exportConfig.branchName || '', this.stackConfig.dirName, ); - this.exportConfig.context.module = 'stack'; - this.currentModuleName = 'Stack'; + this.exportConfig.context.module = MODULE_CONTEXTS.STACK; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.STACK]; } async start(): Promise { @@ -32,17 +38,10 @@ export default class ExportStack extends BaseClass { log.debug('Starting stack export process...', this.exportConfig.context); // Initial analysis with loading spinner - const [stackData, localesCount] = await this.withLoadingSpinner( - 'STACK: Analyzing stack configuration...', - async () => { - const stackData = isAuthenticated() ? await this.getStack() : null; - const localesCount = - !this.exportConfig.preserveStackVersion && !this.exportConfig.hasOwnProperty('master_locale') - ? await this.getLocalesCount() - : 0; - return [stackData, localesCount]; - }, - ); + const [stackData] = await this.withLoadingSpinner('STACK: Analyzing stack configuration...', async () => { + const stackData = isAuthenticated() ? await this.getStack() : null; + return [stackData]; + }); // Create nested progress manager const progress = this.createNestedProgress(this.currentModuleName); @@ -58,27 +57,28 @@ export default class ExportStack extends BaseClass { } if (!this.exportConfig.management_token) { - progress.addProcess('Settings', 1); + progress.addProcess(PROCESS_NAMES.STACK_SETTINGS, 1); processCount++; } - if ( - !this.exportConfig.preserveStackVersion && - !this.exportConfig.hasOwnProperty('master_locale') && - localesCount > 0 - ) { - progress.addProcess('Locale', localesCount); + if (!this.exportConfig.preserveStackVersion && !this.exportConfig.hasOwnProperty('master_locale')) { + progress.addProcess(PROCESS_NAMES.STACK_LOCALE, 1); processCount++; } else if (this.exportConfig.preserveStackVersion) { - progress.addProcess('Details', 1); + progress.addProcess(PROCESS_NAMES.STACK_DETAILS, 1); processCount++; } // Execute processes if (!this.exportConfig.management_token) { - progress.startProcess('Settings').updateStatus('Exporting stack settings...', 'Settings'); + progress + .startProcess(PROCESS_NAMES.STACK_SETTINGS) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.STACK_SETTINGS].EXPORTING, + PROCESS_NAMES.STACK_SETTINGS, + ); await this.exportStackSettings(); - progress.completeProcess('Settings', true); + progress.completeProcess(PROCESS_NAMES.STACK_SETTINGS, true); } else { log.info( 'Skipping stack settings export: Operation is not supported when using a management token.', @@ -86,14 +86,15 @@ export default class ExportStack extends BaseClass { ); } - if ( - !this.exportConfig.preserveStackVersion && - !this.exportConfig.hasOwnProperty('master_locale') && - localesCount > 0 - ) { - progress.startProcess('Locale').updateStatus('Fetching master locale...', 'Locale'); + if (!this.exportConfig.preserveStackVersion && !this.exportConfig.hasOwnProperty('master_locale')) { + progress + .startProcess(PROCESS_NAMES.STACK_LOCALE) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.STACK_LOCALE].FETCHING, + PROCESS_NAMES.STACK_LOCALE, + ); const masterLocale = await this.getLocales(); - progress.completeProcess('Locale', true); + progress.completeProcess(PROCESS_NAMES.STACK_LOCALE, true); if (masterLocale?.code) { this.exportConfig.master_locale = { code: masterLocale.code }; @@ -103,9 +104,14 @@ export default class ExportStack extends BaseClass { this.completeProgress(true); return masterLocale; } else if (this.exportConfig.preserveStackVersion) { - progress.startProcess('Details').updateStatus('Exporting stack data...', 'Details'); + progress + .startProcess(PROCESS_NAMES.STACK_DETAILS) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.STACK_DETAILS].EXPORTING, + PROCESS_NAMES.STACK_DETAILS, + ); const stackResult = await this.exportStack(); - progress.completeProcess('Details', true); + progress.completeProcess(PROCESS_NAMES.STACK_DETAILS, true); this.completeProgress(true); return stackResult; @@ -142,26 +148,6 @@ export default class ExportStack extends BaseClass { }); } - async getLocalesCount(): Promise { - log.debug('Fetching locales count...', this.exportConfig.context); - - try { - const countQuery = { - ...this.qs, - limit: 1, - }; - - const data = await this.stack.locale().query(countQuery).find(); - - const count = data.count || 0; - log.debug(`Total locales count: ${count}`, this.exportConfig.context); - return count; - } catch (error) { - log.debug('Failed to fetch locales count', this.exportConfig.context); - return 0; - } - } - async getLocales(skip: number = 0) { if (skip) { this.qs.skip = skip; @@ -184,9 +170,7 @@ export default class ExportStack extends BaseClass { log.debug(`Processing ${items.length} locales to find master locale`, this.exportConfig.context); // Track progress for each locale processed - items.forEach((locale: any) => { - this.progressManager?.tick(true, `locale: ${locale.name || locale.code}`, null, 'Locale'); - }); + this.progressManager?.tick(true, 'Fetch locale', null, PROCESS_NAMES.STACK_LOCALE); skip += this.stackConfig.limit || 100; const masterLocalObj = find(items, (locale: any) => { @@ -221,7 +205,12 @@ export default class ExportStack extends BaseClass { `Error occurred while fetching locales for stack: ${this.exportConfig.source_stack}`, this.exportConfig.context, ); - this.progressManager?.tick(false, 'locale fetch', error?.message || 'Failed to fetch locales', 'Locale'); + this.progressManager?.tick( + false, + 'locale fetch', + error?.message || PROCESS_STATUS[PROCESS_NAMES.STACK_LOCALE].FAILED, + PROCESS_NAMES.STACK_LOCALE, + ); handleAndLogError( error, { ...this.exportConfig.context }, @@ -245,7 +234,12 @@ export default class ExportStack extends BaseClass { fsUtil.writeFile(stackFilePath, resp); // Track progress for stack export completion - this.progressManager?.tick(true, `stack: ${this.exportConfig.source_stack}`, null, 'Details'); + this.progressManager?.tick( + true, + `stack: ${this.exportConfig.source_stack}`, + null, + PROCESS_NAMES.STACK_DETAILS, + ); log.success( `Stack details exported successfully for stack ${this.exportConfig.source_stack}`, @@ -256,7 +250,12 @@ export default class ExportStack extends BaseClass { }) .catch((error: any) => { log.debug(`Error occurred while exporting stack: ${this.exportConfig.source_stack}`, this.exportConfig.context); - this.progressManager?.tick(false, 'stack export', error?.message || 'Failed to export stack', 'Details'); + this.progressManager?.tick( + false, + 'stack export', + error?.message || PROCESS_STATUS[PROCESS_NAMES.STACK_DETAILS].FAILED, + PROCESS_NAMES.STACK_DETAILS, + ); handleAndLogError(error, { ...this.exportConfig.context }); }); } @@ -270,7 +269,7 @@ export default class ExportStack extends BaseClass { fsUtil.writeFile(pResolve(this.stackFolderPath, 'settings.json'), resp); // Track progress for stack settings completion - this.progressManager?.tick(true, 'stack settings', null, 'Settings'); + this.progressManager?.tick(true, 'stack settings', null, PROCESS_NAMES.STACK_SETTINGS); log.success('Exported stack settings successfully!', this.exportConfig.context); return resp; @@ -279,8 +278,8 @@ export default class ExportStack extends BaseClass { this.progressManager?.tick( false, 'stack settings', - error?.message || 'Failed to export stack settings', - 'Settings', + error?.message || PROCESS_STATUS[PROCESS_NAMES.STACK_SETTINGS].FAILED, + PROCESS_NAMES.STACK_SETTINGS, ); handleAndLogError(error, { ...this.exportConfig.context }); }); diff --git a/packages/contentstack-export/src/export/modules/taxonomies.ts b/packages/contentstack-export/src/export/modules/taxonomies.ts index 901dfd3433..f98fe8ab57 100644 --- a/packages/contentstack-export/src/export/modules/taxonomies.ts +++ b/packages/contentstack-export/src/export/modules/taxonomies.ts @@ -5,7 +5,13 @@ import { resolve as pResolve } from 'node:path'; import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil } from '../../utils'; +import { + fsUtil, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, +} from '../../utils'; import { ModuleClassParams, ExportConfig } from '../../types'; export default class ExportTaxonomies extends BaseClass { @@ -25,8 +31,8 @@ export default class ExportTaxonomies extends BaseClass { this.taxonomiesConfig = exportConfig.modules.taxonomies; this.qs = { include_count: true, limit: this.taxonomiesConfig.limit || 100, skip: 0 }; this.applyQueryFilters(this.qs, 'taxonomies'); - this.exportConfig.context.module = 'taxonomies'; - this.currentModuleName = 'Taxonomies'; + this.exportConfig.context.module = MODULE_CONTEXTS.TAXONOMIES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.TAXONOMIES]; } async start(): Promise { @@ -60,30 +66,41 @@ export default class ExportTaxonomies extends BaseClass { const progress = this.createNestedProgress(this.currentModuleName); // Add sub-processes - progress.addProcess('Fetch', totalCount); - progress.addProcess('Taxonomies & Terms', totalCount); + progress.addProcess(PROCESS_NAMES.FETCH_TAXONOMIES, totalCount); + progress.addProcess(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, totalCount); // Fetch taxonomies - progress.startProcess('Fetch').updateStatus('Fetching taxonomy metadata...', 'Fetch'); + progress + .startProcess(PROCESS_NAMES.FETCH_TAXONOMIES) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.FETCH_TAXONOMIES].FETCHING, + PROCESS_NAMES.FETCH_TAXONOMIES, + ); await this.getAllTaxonomies(); - progress.completeProcess('Fetch', true); + progress.completeProcess(PROCESS_NAMES.FETCH_TAXONOMIES, true); const actualTaxonomyCount = Object.keys(this.taxonomies)?.length; - log.debug(`Found ${actualTaxonomyCount} taxonomies to export (API reported ${totalCount})`, this.exportConfig.context); + log.debug( + `Found ${actualTaxonomyCount} taxonomies to export (API reported ${totalCount})`, + this.exportConfig.context, + ); // Update progress for export step if counts differ if (actualTaxonomyCount !== totalCount && actualTaxonomyCount > 0) { // Remove the old process and add with correct count - progress.addProcess('Taxonomies & Terms', actualTaxonomyCount); + progress.addProcess(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, actualTaxonomyCount); } // Export detailed taxonomies if (actualTaxonomyCount > 0) { progress - .startProcess('Taxonomies & Terms') - .updateStatus('Exporting taxonomy details...', 'Taxonomies & Terms'); + .startProcess(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS].EXPORTING, + PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, + ); await this.exportTaxonomies(); - progress.completeProcess('Taxonomies & Terms', true); + progress.completeProcess(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, true); } else { log.info('No taxonomies found to export detailed information', this.exportConfig.context); } @@ -152,7 +169,12 @@ export default class ExportTaxonomies extends BaseClass { } // Track progress for each taxonomy - this.progressManager?.tick(true, `taxonomy: ${taxonomyName || taxonomyUid}`, null, 'Fetch'); + this.progressManager?.tick( + true, + `taxonomy: ${taxonomyName || taxonomyUid}`, + null, + PROCESS_NAMES.FETCH_TAXONOMIES, + ); } log.debug( @@ -184,7 +206,12 @@ export default class ExportTaxonomies extends BaseClass { fsUtil.writeFile(filePath, response); // Track progress for each exported taxonomy - this.progressManager?.tick(true, `taxonomy: ${taxonomyName || uid}`, null, 'Taxonomies & Terms'); + this.progressManager?.tick( + true, + `taxonomy: ${taxonomyName || uid}`, + null, + PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, + ); log.success(messageHandler.parse('TAXONOMY_EXPORT_SUCCESS', taxonomyName || uid), this.exportConfig.context); }; @@ -196,8 +223,8 @@ export default class ExportTaxonomies extends BaseClass { this.progressManager?.tick( false, `taxonomy: ${taxonomyName || uid}`, - error?.message || 'Export failed', - 'Taxonomies & Terms', + error?.message || PROCESS_STATUS[PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS].FAILED, + PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, ); handleAndLogError( @@ -209,7 +236,7 @@ export default class ExportTaxonomies extends BaseClass { const taxonomyUids = keys(this.taxonomies); log.debug(`Starting detailed export for ${taxonomyUids.length} taxonomies`, this.exportConfig.context); - + // Export each taxonomy individually for (const uid of taxonomyUids) { try { @@ -224,7 +251,7 @@ export default class ExportTaxonomies extends BaseClass { onReject({ error, uid }); } } - + // Write the taxonomies index file const taxonomiesFilePath = pResolve(this.taxonomiesFolderPath, this.taxonomiesConfig.fileName); log.debug(`Writing taxonomies index to: ${taxonomiesFilePath}`, this.exportConfig.context); diff --git a/packages/contentstack-export/src/export/modules/webhooks.ts b/packages/contentstack-export/src/export/modules/webhooks.ts index 90a89b04c9..354527982f 100644 --- a/packages/contentstack-export/src/export/modules/webhooks.ts +++ b/packages/contentstack-export/src/export/modules/webhooks.ts @@ -4,8 +4,8 @@ import { resolve as pResolve } from 'node:path'; import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil } from '../../utils'; import { WebhookConfig, ModuleClassParams } from '../../types'; +import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportWebhooks extends BaseClass { private webhooks: Record>; @@ -22,8 +22,8 @@ export default class ExportWebhooks extends BaseClass { this.webhooks = {}; this.webhookConfig = exportConfig.modules.webhooks; this.qs = { include_count: true, asc: 'updated_at' }; - this.exportConfig.context.module = 'webhooks'; - this.currentModuleName = 'Webhooks'; + this.exportConfig.context.module = MODULE_CONTEXTS.WEBHOOKS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.WEBHOOKS]; } async start(): Promise { diff --git a/packages/contentstack-export/src/export/modules/workflows.ts b/packages/contentstack-export/src/export/modules/workflows.ts index b09d546a4b..3e38886662 100644 --- a/packages/contentstack-export/src/export/modules/workflows.ts +++ b/packages/contentstack-export/src/export/modules/workflows.ts @@ -4,8 +4,8 @@ import { resolve as pResolve } from 'node:path'; import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; -import { fsUtil } from '../../utils'; import { WorkflowConfig, ModuleClassParams } from '../../types'; +import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportWorkFlows extends BaseClass { private workflows: Record>; @@ -21,8 +21,8 @@ export default class ExportWorkFlows extends BaseClass { this.workflows = {}; this.workflowConfig = exportConfig.modules.workflows; this.qs = { include_count: true }; - this.exportConfig.context.module = 'workflows'; - this.currentModuleName = 'Workflows'; + this.exportConfig.context.module = MODULE_CONTEXTS.WORKFLOWS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.WORKFLOWS]; } async start(): Promise { @@ -52,7 +52,7 @@ export default class ExportWorkFlows extends BaseClass { } // Create nested progress manager for complex workflow processing - const progress = this.createSimpleProgress(this.currentModuleName, totalCount) + const progress = this.createSimpleProgress(this.currentModuleName, totalCount); // Fetch workflows progress.updateStatus('Fetching workflow definitions...'); @@ -129,15 +129,10 @@ export default class ExportWorkFlows extends BaseClass { log.success(messageHandler.parse('WORKFLOW_EXPORT_SUCCESS', workflowName), this.exportConfig.context); // Track progress for each workflow - this.progressManager?.tick(true, `workflow: ${workflowName}`, null, 'Fetch'); + this.progressManager?.tick(true, `workflow: ${workflowName}`); } catch (error) { log.error(`Failed to process workflow: ${workflowName}`, this.exportConfig.context); - this.progressManager?.tick( - false, - `workflow: ${workflowName}`, - error?.message || 'Processing failed', - 'Fetch', - ); + this.progressManager?.tick(false, `workflow: ${workflowName}`, error?.message || 'Processing failed', 'Fetch'); } } @@ -160,7 +155,6 @@ export default class ExportWorkFlows extends BaseClass { try { const roleData = await this.getRoles(roleUid); stage.SYS_ACL.roles.uids[i] = roleData; - } catch (error) { log.error(`Failed to fetch role ${roleUid}`, this.exportConfig.context); } diff --git a/packages/contentstack-export/src/utils/constants.ts b/packages/contentstack-export/src/utils/constants.ts new file mode 100644 index 0000000000..fc2f7dd287 --- /dev/null +++ b/packages/contentstack-export/src/utils/constants.ts @@ -0,0 +1,170 @@ +export const PROCESS_NAMES = { + // Assets module + ASSET_FOLDERS: 'Folders', + ASSET_METADATA: 'Metadata', + ASSET_DOWNLOADS: 'Downloads', + + // Custom Roles module + FETCH_ROLES: 'Fetch Roles', + FETCH_LOCALES: 'Fetch Locales', + PROCESS_MAPPINGS: 'Process Mappings', + + // Entries module + ENTRIES: 'Entries', + ENTRY_VERSIONS: 'Entry Versions', + VARIANT_ENTRIES: 'Variant Entries', + + // Marketplace Apps module + FETCH_APPS: 'Fetch Apps', + FETCH_CONFIG_MANIFEST: 'Fetch config & manifest', + + // Stack module + STACK_SETTINGS: 'Settings', + STACK_LOCALE: 'Locale', + STACK_DETAILS: 'Details', + + // Taxonomies module + FETCH_TAXONOMIES: 'Fetch Taxonomies', + EXPORT_TAXONOMIES_TERMS: 'Taxonomies & Terms', + + // Personalize module + PERSONALIZE_PROJECTS: 'Projects', + PERSONALIZE_EVENTS: 'Events', + PERSONALIZE_ATTRIBUTES: 'Attributes', + PERSONALIZE_AUDIENCES: 'Audiences', + PERSONALIZE_EXPERIENCES: 'Experiences', +} as const; + +export const MODULE_CONTEXTS = { + ASSETS: 'assets', + CONTENT_TYPES: 'content-types', + CUSTOM_ROLES: 'custom-roles', + ENTRIES: 'entries', + ENVIRONMENTS: 'environments', + EXTENSIONS: 'extensions', + GLOBAL_FIELDS: 'global-fields', + LABELS: 'labels', + LOCALES: 'locales', + MARKETPLACE_APPS: 'marketplace-apps', + PERSONALIZE: 'personalize', + STACK: 'stack', + TAXONOMIES: 'taxonomies', + WEBHOOKS: 'webhooks', + WORKFLOWS: 'workflows', +} as const; + +// Display names for modules to avoid scattering user-facing strings +export const MODULE_NAMES = { + [MODULE_CONTEXTS.ASSETS]: 'Assets', + [MODULE_CONTEXTS.CONTENT_TYPES]: 'Content Types', + [MODULE_CONTEXTS.CUSTOM_ROLES]: 'Custom Roles', + [MODULE_CONTEXTS.ENTRIES]: 'Entries', + [MODULE_CONTEXTS.ENVIRONMENTS]: 'Environments', + [MODULE_CONTEXTS.EXTENSIONS]: 'Extensions', + [MODULE_CONTEXTS.GLOBAL_FIELDS]: 'Global Fields', + [MODULE_CONTEXTS.LABELS]: 'Labels', + [MODULE_CONTEXTS.LOCALES]: 'Locales', + [MODULE_CONTEXTS.MARKETPLACE_APPS]: 'Marketplace Apps', + [MODULE_CONTEXTS.PERSONALIZE]: 'Personalize', + [MODULE_CONTEXTS.STACK]: 'Stack', + [MODULE_CONTEXTS.TAXONOMIES]: 'Taxonomies', + [MODULE_CONTEXTS.WEBHOOKS]: 'Webhooks', + [MODULE_CONTEXTS.WORKFLOWS]: 'Workflows', +} as const; + +export const PROCESS_STATUS = { + [PROCESS_NAMES.ASSET_FOLDERS]: { + FETCHING: 'Fetching folder structure...', + FAILED: 'Failed to fetch folder structure.', + }, + [PROCESS_NAMES.ASSET_METADATA]: { + FETCHING: 'Fetching asset information...', + FAILED: 'Failed to fetch asset', + FETCHING_VERSION: 'Processing versioned assets...', + }, + [PROCESS_NAMES.ASSET_DOWNLOADS]: { + DOWNLOADING: 'Downloading asset file...', + FAILED: 'Failed to download asset:', + }, + // Custom Roles + [PROCESS_NAMES.FETCH_ROLES]: { + FETCHING: 'Fetching custom roles...', + FAILED: 'Failed to fetch custom roles.', + }, + [PROCESS_NAMES.FETCH_LOCALES]: { + FETCHING: 'Fetching locales...', + FAILED: 'Failed to fetch locales.', + }, + [PROCESS_NAMES.PROCESS_MAPPINGS]: { + PROCESSING: 'Processing role-locale mappings...', + FAILED: 'Failed to process role-locale mappings.', + }, + [PROCESS_NAMES.ENTRIES]: { + PROCESSING: 'Processing entry collections...', + FAILED: 'Failed to export entries.', + }, + [PROCESS_NAMES.ENTRY_VERSIONS]: { + PROCESSING: 'Processing entry versions...', + FAILED: 'Failed to export entry versions.', + }, + [PROCESS_NAMES.VARIANT_ENTRIES]: { + PROCESSING: 'Processing variant entries...', + FAILED: 'Failed to export variant entries.', + }, + // Marketplace Apps + [PROCESS_NAMES.FETCH_APPS]: { + FETCHING: 'Fetching marketplace apps...', + FAILED: 'Failed to fetch marketplace apps.', + }, + [PROCESS_NAMES.FETCH_CONFIG_MANIFEST]: { + PROCESSING: 'Processing app manifests and configurations...', + FAILED: 'Failed to process app manifests/configurations.', + }, + // Stack + [PROCESS_NAMES.STACK_SETTINGS]: { + EXPORTING: 'Exporting stack settings...', + FAILED: 'Failed to export stack settings.', + }, + [PROCESS_NAMES.STACK_LOCALE]: { + FETCHING: 'Fetching master locale...', + FAILED: 'Failed to fetch master locale.', + }, + [PROCESS_NAMES.STACK_DETAILS]: { + EXPORTING: 'Exporting stack data...', + FAILED: 'Failed to export stack data.', + }, + // Taxonomies + [PROCESS_NAMES.FETCH_TAXONOMIES]: { + FETCHING: 'Fetching taxonomy metadata...', + FAILED: 'Failed to fetch taxonomies.', + }, + [PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS]: { + EXPORTING: 'Exporting taxonomy details...', + FAILED: 'Failed to export taxonomy details.', + }, + // Personalize + [PROCESS_NAMES.PERSONALIZE_PROJECTS]: { + EXPORTING: 'Exporting personalization projects...', + FAILED: 'Failed to export personalization projects.', + }, + [PROCESS_NAMES.PERSONALIZE_EVENTS]: { + EXPORTING: 'Exporting events...', + FAILED: 'Failed to export events.', + }, + [PROCESS_NAMES.PERSONALIZE_ATTRIBUTES]: { + EXPORTING: 'Exporting attributes...', + FAILED: 'Failed to export attributes.', + }, + [PROCESS_NAMES.PERSONALIZE_AUDIENCES]: { + EXPORTING: 'Exporting audiences...', + FAILED: 'Failed to export audiences.', + }, + [PROCESS_NAMES.PERSONALIZE_EXPERIENCES]: { + EXPORTING: 'Exporting experiences...', + FAILED: 'Failed to export experiences.', + }, +}; + +export type ExportProcessName = (typeof PROCESS_NAMES)[keyof typeof PROCESS_NAMES]; +export type ExportModuleContext = (typeof MODULE_CONTEXTS)[keyof typeof MODULE_CONTEXTS]; +export type ExportProcessStatus = (typeof PROCESS_STATUS)[keyof typeof PROCESS_STATUS]; diff --git a/packages/contentstack-export/src/utils/index.ts b/packages/contentstack-export/src/utils/index.ts index 3ab3476518..9cbd32cac5 100644 --- a/packages/contentstack-export/src/utils/index.ts +++ b/packages/contentstack-export/src/utils/index.ts @@ -8,3 +8,4 @@ export { log, unlinkFileLogger } from './logger'; export { default as login } from './basic-login'; export * from './common-helper'; export * from './marketplace-app-helper'; +export { MODULE_CONTEXTS, MODULE_NAMES, PROCESS_NAMES, PROCESS_STATUS } from './constants'; diff --git a/packages/contentstack-export/src/utils/marketplace-app-helper.ts b/packages/contentstack-export/src/utils/marketplace-app-helper.ts index 1528f078aa..fe08d2812a 100644 --- a/packages/contentstack-export/src/utils/marketplace-app-helper.ts +++ b/packages/contentstack-export/src/utils/marketplace-app-helper.ts @@ -29,23 +29,27 @@ export async function createNodeCryptoInstance(config: ExportConfig): Promise { - if (!url) return "Encryption key can't be empty."; - - return true; - }, - message: 'Enter Marketplace app configurations encryption key', - }); + cryptoArgs['encryptionKey'] = await askEncryptionKey(config); cliux.print(''); } return new NodeCrypto(cryptoArgs); } + +export async function askEncryptionKey(config: ExportConfig): Promise { + return await cliux.inquire({ + type: 'input', + name: 'name', + default: config.marketplaceAppEncryptionKey, + validate: (url: any) => { + if (!url) return "Encryption key can't be empty."; + + return true; + }, + message: 'Enter Marketplace app configurations encryption key', + }); +} diff --git a/packages/contentstack-export/src/utils/strategy-registrations.ts b/packages/contentstack-export/src/utils/strategy-registrations.ts index c4ce26d0c7..295f3a44ec 100644 --- a/packages/contentstack-export/src/utils/strategy-registrations.ts +++ b/packages/contentstack-export/src/utils/strategy-registrations.ts @@ -1,142 +1,113 @@ +import { MODULE_CONTEXTS, MODULE_NAMES, PROCESS_NAMES } from './constants'; /** * Progress Strategy Registrations for Export Modules * This file registers progress calculation strategies for all export modules * to ensure correct item counts in the final summary. */ -import { - ProgressStrategyRegistry, - PrimaryProcessStrategy, +import { + ProgressStrategyRegistry, + PrimaryProcessStrategy, CustomProgressStrategy, - DefaultProgressStrategy + DefaultProgressStrategy, } from '@contentstack/cli-utilities'; -// Register strategy for Content Types - simple module -ProgressStrategyRegistry.register( - 'CONTENT TYPES', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.CONTENT_TYPES], new DefaultProgressStrategy()); -// Register strategy for Assets - use Asset Metadata as primary process +// Register strategy for Assets - use Asset Metadata as primary process ProgressStrategyRegistry.register( - 'ASSETS', - new PrimaryProcessStrategy('Metadata') + MODULE_NAMES[MODULE_CONTEXTS.ASSETS], + new PrimaryProcessStrategy(PROCESS_NAMES.ASSET_METADATA), ); -// Register strategy for Global Fields - simple module -ProgressStrategyRegistry.register( - 'GLOBAL FIELDS', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.GLOBAL_FIELDS], new DefaultProgressStrategy()); -// Register strategy for Extensions - simple module -ProgressStrategyRegistry.register( - 'EXTENSIONS', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.EXTENSIONS], new DefaultProgressStrategy()); // Register strategy for Environments - simple module -ProgressStrategyRegistry.register( - 'ENVIRONMENTS', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.ENVIRONMENTS], new DefaultProgressStrategy()); -// Register strategy for Locales - simple module -ProgressStrategyRegistry.register( - 'LOCALES', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.LOCALES], new DefaultProgressStrategy()); -// Register strategy for Labels - simple module -ProgressStrategyRegistry.register( - 'LABELS', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.LABELS], new DefaultProgressStrategy()); -// Register strategy for Webhooks - simple module -ProgressStrategyRegistry.register( - 'WEBHOOKS', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.WEBHOOKS], new DefaultProgressStrategy()); -// Register strategy for Workflows - simple module -ProgressStrategyRegistry.register( - 'WORKFLOWS', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.WORKFLOWS], new DefaultProgressStrategy()); -// Register strategy for Custom Roles - simple module -ProgressStrategyRegistry.register( - 'CUSTOM ROLES', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.CUSTOM_ROLES], new DefaultProgressStrategy()); // Register strategy for Taxonomies - use Taxonomies & Terms as primary process ProgressStrategyRegistry.register( - 'TAXONOMIES', - new PrimaryProcessStrategy('Taxonomies & Terms') + MODULE_NAMES[MODULE_CONTEXTS.TAXONOMIES], + new PrimaryProcessStrategy(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS), ); // Register strategy for Marketplace Apps - complex module with app installations ProgressStrategyRegistry.register( - 'MARKETPLACE APPS', + MODULE_NAMES[MODULE_CONTEXTS.MARKETPLACE_APPS], new CustomProgressStrategy((processes) => { // For marketplace apps, count the actual apps exported - const appsExport = processes.get('Fetch'); + const appsExport = processes.get(PROCESS_NAMES.FETCH_APPS); if (appsExport) { return { total: appsExport.total, success: appsExport.successCount, - failures: appsExport.failureCount + failures: appsExport.failureCount, }; } - - // Fallback to setup process if no export process - const setup = processes.get('Setup'); + + const setup = processes.get(PROCESS_NAMES.FETCH_CONFIG_MANIFEST); if (setup) { return { total: setup.total, success: setup.successCount, - failures: setup.failureCount + failures: setup.failureCount, }; } - + return null; - }) + }), ); // Register strategy for Stack Settings - use Settings as primary process ProgressStrategyRegistry.register( - 'STACK', - new PrimaryProcessStrategy('Settings') + MODULE_NAMES[MODULE_CONTEXTS.STACK], + new PrimaryProcessStrategy(PROCESS_NAMES.STACK_SETTINGS), ); // Register strategy for Personalize - complex module with projects/experiences ProgressStrategyRegistry.register( - 'PERSONALIZE', + MODULE_NAMES[MODULE_CONTEXTS.PERSONALIZE], new CustomProgressStrategy((processes) => { // For personalize, we want to count projects as the main metric - const projectExport = processes.get('Project Export'); + const projectExport = processes.get(PROCESS_NAMES.PERSONALIZE_PROJECTS); if (projectExport) { return { total: projectExport.total, success: projectExport.successCount, - failures: projectExport.failureCount + failures: projectExport.failureCount, }; } - + // Fallback to any other main process const mainProcess = Array.from(processes.values())[0]; if (mainProcess) { return { total: mainProcess.total, success: mainProcess.successCount, - failures: mainProcess.failureCount + failures: mainProcess.failureCount, }; } - + return null; - }) + }), +); + +// Register strategy for Entries - use Entries as primary process +ProgressStrategyRegistry.register( + MODULE_NAMES[MODULE_CONTEXTS.ENTRIES], + new PrimaryProcessStrategy(PROCESS_NAMES.ENTRIES), ); -export default ProgressStrategyRegistry; \ No newline at end of file +export default ProgressStrategyRegistry; diff --git a/packages/contentstack-import/src/import/modules/assets.ts b/packages/contentstack-import/src/import/modules/assets.ts index e149612b37..7009dcba50 100644 --- a/packages/contentstack-import/src/import/modules/assets.ts +++ b/packages/contentstack-import/src/import/modules/assets.ts @@ -13,7 +13,7 @@ import { FsUtility, log, handleAndLogError } from '@contentstack/cli-utilities'; import config from '../../config'; import { ModuleClassParams } from '../../types'; -import { formatDate } from '../../utils'; +import { formatDate, PROCESS_NAMES, MODULE_CONTEXTS, MODULE_NAMES, PROCESS_STATUS } from '../../utils'; import BaseClass, { ApiOptions } from './base-class'; export default class ImportAssets extends BaseClass { @@ -33,8 +33,8 @@ export default class ImportAssets extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'assets'; - this.currentModuleName = 'Assets'; + this.importConfig.context.module = MODULE_CONTEXTS.ASSETS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.ASSETS]; this.assetsPath = join(this.importConfig.backupDir, 'assets'); this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'assets'); @@ -74,21 +74,39 @@ export default class ImportAssets extends BaseClass { // Step 3: Perform import steps based on data if (foldersCount > 0) { - await this.executeStep(progress, 'Folders', 'Importing folder structure...', () => this.importFolders()); + await this.executeStep( + progress, + PROCESS_NAMES.ASSET_FOLDERS, + PROCESS_STATUS[PROCESS_NAMES.ASSET_FOLDERS].CREATING, + () => this.importFolders(), + ); } if (this.assetConfig.includeVersionedAssets && versionedAssetsCount > 0) { - await this.executeStep(progress, 'Versions', 'Importing versioned assets...', () => - this.importAssets(true), + await this.executeStep( + progress, + PROCESS_NAMES.ASSET_VERSIONS, + PROCESS_STATUS[PROCESS_NAMES.ASSET_VERSIONS].IMPORTING, + () => this.importAssets(true), ); } if (assetsCount > 0) { - await this.executeStep(progress, 'Upload', 'Uploading asset files...', () => this.importAssets()); + await this.executeStep( + progress, + PROCESS_NAMES.ASSET_UPLOAD, + PROCESS_STATUS[PROCESS_NAMES.ASSET_UPLOAD].UPLOADING, + () => this.importAssets(), + ); } if (!this.importConfig.skipAssetsPublish && publishableAssetsCount > 0) { - await this.executeStep(progress, 'Publish', 'Publishing assets...', () => this.publish()); + await this.executeStep( + progress, + PROCESS_NAMES.ASSET_PUBLISH, + PROCESS_STATUS[PROCESS_NAMES.ASSET_PUBLISH].PUBLISHING, + () => this.publish(), + ); } this.completeProgress(true); @@ -119,7 +137,7 @@ export default class ImportAssets extends BaseClass { const onSuccess = ({ response, apiData: { uid, name } = { uid: null, name: '' } }: any) => { this.assetsFolderMap[uid] = response.uid; - this.progressManager?.tick(true, `folder: ${name || uid}`, null, 'Folders'); + this.progressManager?.tick(true, `folder: ${name || uid}`, null, PROCESS_NAMES.ASSET_FOLDERS); log.debug(`Created folder: ${name} (Mapped ${uid} → ${response.uid})`, this.importConfig.context); log.success(`Created folder: '${name}'`, this.importConfig.context); }; @@ -128,8 +146,8 @@ export default class ImportAssets extends BaseClass { this.progressManager?.tick( false, `folder: ${name || uid}`, - error?.message || 'Failed to create folder', - 'Folders', + error?.message || PROCESS_STATUS[PROCESS_NAMES.ASSET_FOLDERS].FAILED, + PROCESS_NAMES.ASSET_FOLDERS, ); log.error(`${name} folder creation failed.!`, this.importConfig.context); handleAndLogError(error, { ...this.importConfig.context, name }); @@ -192,7 +210,7 @@ export default class ImportAssets extends BaseClass { const processName = isVersion ? 'import versioned assets' : 'import assets'; const indexFileName = isVersion ? 'versioned-assets.json' : 'assets.json'; const basePath = isVersion ? join(this.assetsPath, 'versions') : this.assetsPath; - const progressProcessName = isVersion ? 'Versions' : 'Upload'; + const progressProcessName = isVersion ? PROCESS_NAMES.ASSET_VERSIONS : PROCESS_NAMES.ASSET_UPLOAD; log.debug(`Importing ${processName} from ${basePath}`, this.importConfig.context); @@ -214,7 +232,7 @@ export default class ImportAssets extends BaseClass { this.progressManager?.tick( false, `asset: ${title || uid}`, - error?.message || 'Failed to upload asset', + error?.message || PROCESS_STATUS[PROCESS_NAMES.ASSET_UPLOAD].FAILED, progressProcessName, ); log.error(`${title} asset upload failed.!`, this.importConfig.context); @@ -348,7 +366,7 @@ export default class ImportAssets extends BaseClass { log.debug(`Found ${indexerCount} asset chunks to publish`, this.importConfig.context); const onSuccess = ({ apiData: { uid, title } = undefined }: any) => { - this.progressManager?.tick(true, `published: ${title || uid}`, null, 'Publish'); + this.progressManager?.tick(true, `published: ${title || uid}`, null, PROCESS_NAMES.ASSET_PUBLISH); log.success(`Asset '${uid}: ${title}' published successfully`, this.importConfig.context); }; @@ -356,8 +374,8 @@ export default class ImportAssets extends BaseClass { this.progressManager?.tick( false, `publish failed: ${title || uid}`, - error?.message || 'Failed to publish asset', - 'Publish', + error?.message || PROCESS_STATUS[PROCESS_NAMES.ASSET_PUBLISH].FAILED, + PROCESS_NAMES.ASSET_PUBLISH, ); log.error(`Asset '${uid}: ${title}' not published`, this.importConfig.context); handleAndLogError(error, { ...this.importConfig.context, uid, title }); @@ -513,16 +531,16 @@ export default class ImportAssets extends BaseClass { const { foldersCount, assetsCount, versionedAssetsCount, publishableAssetsCount } = counts; if (foldersCount > 0) { - progress.addProcess('Folders', foldersCount); + progress.addProcess(PROCESS_NAMES.ASSET_FOLDERS, foldersCount); } if (versionedAssetsCount > 0) { - progress.addProcess('Versions', versionedAssetsCount); + progress.addProcess(PROCESS_NAMES.ASSET_VERSIONS, versionedAssetsCount); } if (assetsCount > 0) { - progress.addProcess('Upload', assetsCount); + progress.addProcess(PROCESS_NAMES.ASSET_UPLOAD, assetsCount); } if (publishableAssetsCount > 0) { - progress.addProcess('Publish', publishableAssetsCount); + progress.addProcess(PROCESS_NAMES.ASSET_PUBLISH, publishableAssetsCount); } } diff --git a/packages/contentstack-import/src/import/modules/content-types.ts b/packages/contentstack-import/src/import/modules/content-types.ts index 659087a94e..669cb0534c 100644 --- a/packages/contentstack-import/src/import/modules/content-types.ts +++ b/packages/contentstack-import/src/import/modules/content-types.ts @@ -5,7 +5,16 @@ import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilitie import { ModuleClassParams } from '../../types'; import BaseClass, { ApiOptions } from './base-class'; import { updateFieldRules } from '../../utils/content-type-helper'; -import { fsUtil, schemaTemplate, lookupExtension, lookUpTaxonomy } from '../../utils'; +import { + fsUtil, + schemaTemplate, + lookupExtension, + lookUpTaxonomy, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, +} from '../../utils'; export default class ContentTypesImport extends BaseClass { private cTsMapperPath: string; @@ -47,8 +56,8 @@ export default class ContentTypesImport extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'content-types'; - this.currentModuleName = 'Content Types'; + this.importConfig.context.module = MODULE_CONTEXTS.CONTENT_TYPES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.CONTENT_TYPES]; this.cTsConfig = importConfig.modules['content-types']; this.gFsConfig = importConfig.modules['global-fields']; this.reqConcurrency = this.cTsConfig.writeConcurrency || this.importConfig.writeConcurrency; @@ -130,7 +139,7 @@ export default class ContentTypesImport extends BaseClass { async seedCTs(): Promise { const onSuccess = ({ response: globalField, apiData: { content_type: { uid = null } = {} } = {} }: any) => { this.createdCTs.push(uid); - this.progressManager?.tick(true, `content type: ${uid}`, null, 'Create'); + this.progressManager?.tick(true, `content type: ${uid}`, null, PROCESS_NAMES.CONTENT_TYPES_CREATE); log.success(`Content type '${uid}' created successfully`, this.importConfig.context); log.debug(`Successfully seeded content type: ${uid}`, this.importConfig.context); }; @@ -139,7 +148,7 @@ export default class ContentTypesImport extends BaseClass { false, `content type: ${uid}`, error?.message || 'Failed to create content type', - 'Create', + PROCESS_NAMES.CONTENT_TYPES_CREATE, ); if (error.errorCode === 115 && (error.errors.uid || error.errors.title)) { log.info(`${uid} content type already exist`, this.importConfig.context); @@ -181,7 +190,7 @@ export default class ContentTypesImport extends BaseClass { async updateCTs(): Promise { const onSuccess = ({ response: contentType, apiData: { uid } }: any) => { - this.progressManager?.tick(true, `content type: ${uid}`, null, 'Update'); + this.progressManager?.tick(true, `content type: ${uid}`, null, PROCESS_NAMES.CONTENT_TYPES_UPDATE); log.success(`'${uid}' updated with references`, this.importConfig.context); log.debug(`Content type update completed for: ${uid}`, this.importConfig.context); }; @@ -191,7 +200,7 @@ export default class ContentTypesImport extends BaseClass { false, `content type: ${uid}`, error?.message || 'Failed to update content type', - 'Update', + PROCESS_NAMES.CONTENT_TYPES_UPDATE, ); handleAndLogError(error, { ...this.importConfig.context, uid }, `Content type '${uid}' update failed`); }; @@ -267,7 +276,7 @@ export default class ContentTypesImport extends BaseClass { log.debug(`Loaded ${this.gFs?.length || 0} global fields from file`, this.importConfig.context); const onSuccess = ({ response: globalField, apiData: { uid } = undefined }: any) => { - this.progressManager?.tick(true, `global field: ${uid}`, null, 'GF Update'); + this.progressManager?.tick(true, `global field: ${uid}`, null, PROCESS_NAMES.CONTENT_TYPES_GF_UPDATE); log.success(`Updated the global field ${uid} with content type references`, this.importConfig.context); log.debug(`Global field update completed for: ${uid}`, this.importConfig.context); }; @@ -276,7 +285,7 @@ export default class ContentTypesImport extends BaseClass { false, `global field: ${uid}`, error?.message || 'Failed to update global field', - 'GF Update', + PROCESS_NAMES.CONTENT_TYPES_GF_UPDATE, ); handleAndLogError(error, { ...this.importConfig.context, uid }, `Failed to update the global field '${uid}'`); }; @@ -350,7 +359,12 @@ export default class ContentTypesImport extends BaseClass { this.isExtensionsUpdate = true; const onSuccess = ({ response, apiData: { uid, title } = { uid: null, title: '' } }: any) => { - this.progressManager?.tick(true, `extension: ${response.title || title || uid}`, null, 'Ext Update'); + this.progressManager?.tick( + true, + `extension: ${response.title || title || uid}`, + null, + PROCESS_NAMES.CONTENT_TYPES_EXT_UPDATE, + ); log.success(`Successfully updated the '${response.title}' extension.`, this.importConfig.context); log.debug(`Extension update completed for: ${uid}`, this.importConfig.context); }; @@ -361,7 +375,7 @@ export default class ContentTypesImport extends BaseClass { false, `extension: ${title || uid}`, error?.message || 'Failed to update extension', - 'Ext Update', + PROCESS_NAMES.CONTENT_TYPES_EXT_UPDATE, ); if (error?.errors?.title) { if (!this.importConfig.skipExisting) { @@ -425,30 +439,35 @@ export default class ContentTypesImport extends BaseClass { initializeProgress() { const progress = this.createNestedProgress(this.currentModuleName); if (this.cTs.length) { - progress.addProcess('Create', this.cTs.length); - progress.addProcess('Update', this.cTs.length); + progress.addProcess(PROCESS_NAMES.CONTENT_TYPES_CREATE, this.cTs.length); + progress.addProcess(PROCESS_NAMES.CONTENT_TYPES_UPDATE, this.cTs.length); } if (this.pendingGFs.length) { - progress.addProcess('GF Update', this.pendingGFs.length); + progress.addProcess(PROCESS_NAMES.CONTENT_TYPES_GF_UPDATE, this.pendingGFs.length); } if (this.pendingExts.length) { - progress.addProcess('Ext Update', this.pendingExts.length); + progress.addProcess(PROCESS_NAMES.CONTENT_TYPES_EXT_UPDATE, this.pendingExts.length); } return progress; } async handlePendingGlobalFields(progress: any) { progress - .startProcess('GF Update') - .updateStatus('Updating global fields with content type references...', 'GF Update'); + .startProcess(PROCESS_NAMES.CONTENT_TYPES_GF_UPDATE) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.CONTENT_TYPES_GF_UPDATE].UPDATING, + PROCESS_NAMES.CONTENT_TYPES_GF_UPDATE, + ); log.info('Starting pending global fields update process', this.importConfig.context); await this.updatePendingGFs(); - progress.completeProcess('GF Update', true); + progress.completeProcess(PROCESS_NAMES.CONTENT_TYPES_GF_UPDATE, true); } async handleContentTypesCreation(progress: any) { - progress.startProcess('Create').updateStatus('Creating content types...', 'Create'); + progress + .startProcess(PROCESS_NAMES.CONTENT_TYPES_CREATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.CONTENT_TYPES_CREATE].CREATING, PROCESS_NAMES.CONTENT_TYPES_CREATE); log.info('Starting content types seeding process', this.importConfig.context); await this.seedCTs(); @@ -458,24 +477,31 @@ export default class ContentTypesImport extends BaseClass { log.debug(`Written ${this.createdCTs.length} successful content types to file`, this.importConfig.context); } - progress.completeProcess('Create', true); + progress.completeProcess(PROCESS_NAMES.CONTENT_TYPES_CREATE, true); } async handleContentTypesUpdate(progress: any) { - progress.startProcess('Update').updateStatus('Updating content types with references...', 'Update'); + progress + .startProcess(PROCESS_NAMES.CONTENT_TYPES_UPDATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.CONTENT_TYPES_UPDATE].UPDATING, PROCESS_NAMES.CONTENT_TYPES_UPDATE); log.info('Starting Update process', this.importConfig.context); await this.updateCTs(); - progress.completeProcess('Update', true); + progress.completeProcess(PROCESS_NAMES.CONTENT_TYPES_UPDATE, true); } async handlePendingExtensions(progress: any) { - progress.startProcess('Ext Update').updateStatus('Updating extensions...', 'Ext Update'); + progress + .startProcess(PROCESS_NAMES.CONTENT_TYPES_EXT_UPDATE) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.CONTENT_TYPES_EXT_UPDATE].UPDATING, + PROCESS_NAMES.CONTENT_TYPES_EXT_UPDATE, + ); log.info('Starting pending extensions update process', this.importConfig.context); await this.updatePendingExtensions(); - progress.completeProcess('Ext Update', true); + progress.completeProcess(PROCESS_NAMES.CONTENT_TYPES_EXT_UPDATE, true); if (this.isExtensionsUpdate) { log.success('Successfully updated the extensions.', this.importConfig.context); diff --git a/packages/contentstack-import/src/import/modules/custom-roles.ts b/packages/contentstack-import/src/import/modules/custom-roles.ts index 040197dfd5..cb8973ffaf 100644 --- a/packages/contentstack-import/src/import/modules/custom-roles.ts +++ b/packages/contentstack-import/src/import/modules/custom-roles.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; import { forEach, map } from 'lodash'; import { log, handleAndLogError } from '@contentstack/cli-utilities'; -import { fsUtil, fileHelper } from '../../utils'; +import { fsUtil, fileHelper, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; import BaseClass, { ApiOptions } from './base-class'; import { ModuleClassParams, CustomRoleConfig } from '../../types'; @@ -30,8 +30,8 @@ export default class ImportCustomRoles extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'custom-roles'; - this.currentModuleName = 'Custom Roles'; + this.importConfig.context.module = MODULE_CONTEXTS.CUSTOM_ROLES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.CUSTOM_ROLES]; this.customRolesConfig = importConfig.modules.customRoles; this.customRolesMapperPath = join(this.importConfig.backupDir, 'mapper', 'custom-roles'); this.customRolesFolderPath = join(this.importConfig.backupDir, this.customRolesConfig.dirName); @@ -66,10 +66,10 @@ export default class ImportCustomRoles extends BaseClass { const progress = this.createSimpleProgress(this.currentModuleName, customRolesCount); await this.prepareForImport(); - progress.updateStatus('Building locale mappings...'); + progress.updateStatus(PROCESS_STATUS[PROCESS_NAMES.CUSTOM_ROLES_BUILD_MAPPINGS].BUILDING); await this.getLocalesUidMap(); - progress.updateStatus('Importing custom roles...'); + progress.updateStatus(PROCESS_STATUS[PROCESS_NAMES.CUSTOM_ROLES_IMPORT].IMPORTING); await this.importCustomRoles(); this.handleImportResults(); @@ -134,7 +134,12 @@ export default class ImportCustomRoles extends BaseClass { const onSuccess = ({ response, apiData: { uid, name } = { uid: null, name: '' } }: any) => { this.createdCustomRoles.push(response); this.customRolesUidMapper[uid] = response.uid; - this.progressManager?.tick(true, `custom role: ${name || uid}`); + this.progressManager?.tick( + true, + `custom role: ${name}`, + `custom role: ${name || uid}`, + PROCESS_NAMES.CUSTOM_ROLES_IMPORT, + ); log.success(`custom-role '${name}' imported successfully`, this.importConfig.context); log.debug(`Custom role import completed: ${name} (${uid})`, this.importConfig.context); fsUtil.writeFile(this.customRolesUidMapperPath, this.customRolesUidMapper); @@ -146,11 +151,21 @@ export default class ImportCustomRoles extends BaseClass { log.debug(`Custom role '${name}' import failed`, this.importConfig.context); if (err?.errors?.name) { - this.progressManager?.tick(true, `custom role: ${name} (already exists)`); + this.progressManager?.tick( + true, + `custom role: ${name}`, + `custom role: ${name} (already exists)`, + PROCESS_NAMES.CUSTOM_ROLES_IMPORT, + ); log.info(`custom-role '${name}' already exists`, this.importConfig.context); } else { this.failedCustomRoles.push(apiData); - this.progressManager?.tick(false, `custom role: ${name}`, error?.message || 'Failed to import custom role'); + this.progressManager?.tick( + false, + `custom role: ${name}`, + error?.message || 'Failed to import custom role', + PROCESS_NAMES.CUSTOM_ROLES_IMPORT, + ); handleAndLogError(error, { ...this.importConfig.context, name }, `custom-role '${name}' failed to be import`); } }; @@ -192,7 +207,11 @@ export default class ImportCustomRoles extends BaseClass { ); log.debug(`Skipping custom role serialization for: ${customRole.uid}`, this.importConfig.context); // Still tick progress for skipped custom roles - this.progressManager?.tick(true, `custom role: ${customRole.name} (skipped - already exists)`); + this.progressManager?.tick( + true, + `custom role: ${customRole.name} (skipped - already exists)`, + PROCESS_NAMES.CUSTOM_ROLES_IMPORT, + ); apiOptions.entity = undefined; } else { log.debug(`Processing custom role: ${customRole.name}`, this.importConfig.context); diff --git a/packages/contentstack-import/src/import/modules/entries.ts b/packages/contentstack-import/src/import/modules/entries.ts index d897c0235f..56cccd745a 100644 --- a/packages/contentstack-import/src/import/modules/entries.ts +++ b/packages/contentstack-import/src/import/modules/entries.ts @@ -10,7 +10,6 @@ import { isEmpty, values, cloneDeep, find, indexOf, forEach, remove } from 'loda import { FsUtility, sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; import { fsUtil, - formatError, lookupExtension, suppressSchemaReference, removeUidsFromJsonRteFields, @@ -20,6 +19,10 @@ import { lookupAssets, fileHelper, lookUpTerms, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, } from '../../utils'; import { ModuleClassParams } from '../../types'; import BaseClass, { ApiOptions } from './base-class'; @@ -61,8 +64,8 @@ export default class EntriesImport extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'entries'; - this.currentModuleName = 'Entries'; + this.importConfig.context.module = MODULE_CONTEXTS.ENTRIES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.ENTRIES]; this.assetUidMapperPath = path.resolve(sanitizePath(importConfig.data), 'mapper', 'assets', 'uid-mapping.json'); this.assetUrlMapperPath = path.resolve(sanitizePath(importConfig.data), 'mapper', 'assets', 'url-mapping.json'); this.entriesMapperPath = path.resolve(sanitizePath(importConfig.data), 'mapper', 'entries'); @@ -111,7 +114,8 @@ export default class EntriesImport extends BaseClass { try { log.debug('Starting entries import process...', this.importConfig.context); - const [contentTypesCount, localesCount, totalEntryTasks] = await this.analyzeEntryData(); + const [contentTypesCount, localesCount, totalEntryChunks, totalActualEntries, totalEntriesForPublishing] = + await this.analyzeEntryData(); if (contentTypesCount === 0) { log.info('No content types found for entry import', this.importConfig.context); return; @@ -121,54 +125,73 @@ export default class EntriesImport extends BaseClass { this.initializeProgress(progress, { contentTypesCount, localesCount, - totalEntryTasks, + totalEntryChunks, + totalActualEntries, + totalEntriesForPublishing, }); // Step 1: Prepare content types progress - .startProcess('CT Preparation') - .updateStatus('Preparing content types for entry import...', 'CT Preparation'); + .startProcess(PROCESS_NAMES.CT_PREPARATION) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.CT_PREPARATION].PREPARING, PROCESS_NAMES.CT_PREPARATION); await this.disableMandatoryCTReferences(); - progress.completeProcess('CT Preparation', true); + progress.completeProcess(PROCESS_NAMES.CT_PREPARATION, true); // Step 2: Create entries - progress.startProcess('Create').updateStatus('Creating entries...', 'Create'); + progress + .startProcess(PROCESS_NAMES.ENTRIES_CREATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.ENTRIES_CREATE].CREATING, PROCESS_NAMES.ENTRIES_CREATE); await this.processEntryCreation(); - progress.completeProcess('Create', true); + progress.completeProcess(PROCESS_NAMES.ENTRIES_CREATE, true); // Step 3: Replace existing entries if needed if (this.importConfig.replaceExisting) { - progress.startProcess('Replace Existing').updateStatus('Replacing existing entries...', 'Replace Existing'); + progress + .startProcess(PROCESS_NAMES.ENTRIES_REPLACE_EXISTING) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.ENTRIES_REPLACE_EXISTING].REPLACING, + PROCESS_NAMES.ENTRIES_REPLACE_EXISTING, + ); await this.processEntryReplacement(); - progress.completeProcess('Replace Existing', true); + progress.completeProcess(PROCESS_NAMES.ENTRIES_REPLACE_EXISTING, true); } // Step 4: Update entries with references - progress.startProcess('Reference Updates').updateStatus('Updating entry references...', 'Reference Updates'); + progress + .startProcess(PROCESS_NAMES.REFERENCE_UPDATES) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.REFERENCE_UPDATES].UPDATING, PROCESS_NAMES.REFERENCE_UPDATES); await this.processEntryReferenceUpdates(); - progress.completeProcess('Reference Updates', true); + progress.completeProcess(PROCESS_NAMES.REFERENCE_UPDATES, true); // Step 5: Restore content types - progress.startProcess('CT Restoration').updateStatus('Restoring content type references...', 'CT Restoration'); + progress + .startProcess(PROCESS_NAMES.CT_RESTORATION) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.CT_RESTORATION].RESTORING, PROCESS_NAMES.CT_RESTORATION); await this.enableMandatoryCTReferences(); - progress.completeProcess('CT Restoration', true); + progress.completeProcess(PROCESS_NAMES.CT_RESTORATION, true); // Step 6: Update field rules - progress.startProcess('Field Rules Update').updateStatus('Updating field rules...', 'Field Rules Update'); + progress + .startProcess(PROCESS_NAMES.FIELD_RULES_UPDATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.FIELD_RULES_UPDATE].UPDATING, PROCESS_NAMES.FIELD_RULES_UPDATE); await this.updateFieldRules(); - progress.completeProcess('Field Rules Update', true); + progress.completeProcess(PROCESS_NAMES.FIELD_RULES_UPDATE, true); // Step 7: Publish entries if not skipped if (!this.importConfig.skipEntriesPublish) { - progress.startProcess('Publish').updateStatus('Publishing entries...', 'Publish'); + progress + .startProcess(PROCESS_NAMES.ENTRIES_PUBLISH) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.ENTRIES_PUBLISH].PUBLISHING, PROCESS_NAMES.ENTRIES_PUBLISH); await this.processEntryPublishing(); - progress.completeProcess('Publish', true); + progress.completeProcess(PROCESS_NAMES.ENTRIES_PUBLISH, true); } // Step 8: Cleanup and finalization - progress.startProcess('Cleanup').updateStatus('Cleaning up auto-created entries...', 'Cleanup'); + progress + .startProcess(PROCESS_NAMES.CLEANUP) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.CLEANUP].CLEANING, PROCESS_NAMES.CLEANUP); await this.processCleanup(); - progress.completeProcess('Cleanup', true); + progress.completeProcess(PROCESS_NAMES.CLEANUP, true); this.completeProgress(true); log.success('Entries imported successfully', this.importConfig.context); @@ -180,13 +203,13 @@ export default class EntriesImport extends BaseClass { } } - private async analyzeEntryData(): Promise<[number, number, number]> { + private async analyzeEntryData(): Promise<[number, number, number, number, number]> { return this.withLoadingSpinner('ENTRIES: Analyzing import data...', async () => { log.debug('Loading content types for entry analysis', this.importConfig.context); this.cTs = fsUtil.readFile(path.join(this.cTsPath, 'schema.json')) as Record[]; if (!this.cTs || isEmpty(this.cTs)) { - return [0, 0, 0]; + return [0, 0, 0, 0, 0]; } log.debug('Loading installed extensions for entry processing', this.importConfig.context); @@ -210,43 +233,81 @@ export default class EntriesImport extends BaseClass { const contentTypesCount = this.cTs.length; const localesCount = this.locales.length; - const totalEntryTasks = contentTypesCount * localesCount; + let totalEntryChunks = 0; + let totalActualEntries = 0; + let totalEntriesForPublishing = 0; + + for (let locale of this.locales) { + for (let contentType of this.cTs) { + const basePath = path.join(this.entriesPath, contentType.uid, locale.code); + const fs = new FsUtility({ basePath, indexFileName: 'index.json' }); + const indexer = fs.indexFileContent; + const chunksInThisCTLocale = values(indexer).length; + totalEntryChunks += chunksInThisCTLocale; + + for (const _ in indexer) { + try { + const chunk = await fs.readChunkFiles.next(); + if (chunk) { + const entriesInChunk = values(chunk as Record).length; + totalActualEntries += entriesInChunk; + + // Count entries with publish details + if (!this.importConfig.skipEntriesPublish) { + const publishableEntries = values(chunk as Record).filter( + (entry: any) => entry.publish_details && entry.publish_details.length > 0, + ); + totalEntriesForPublishing += publishableEntries.length; + } + } + } catch (error) { + log.debug(`Error reading chunk for ${contentType.uid}/${locale.code}`, this.importConfig.context); + } + } + } + } log.debug( - `Analysis complete: ${contentTypesCount} content types, ${localesCount} locales, ${totalEntryTasks} total tasks`, + `Analysis complete: ${contentTypesCount} content types, ${localesCount} locales, ${totalEntryChunks} total chunks, ${totalActualEntries} total entries, ${totalEntriesForPublishing} total publishable entries`, this.importConfig.context, ); - return [contentTypesCount, localesCount, totalEntryTasks]; + return [contentTypesCount, localesCount, totalEntryChunks, totalActualEntries, totalEntriesForPublishing]; }); } private initializeProgress( progress: any, - counts: { contentTypesCount: number; localesCount: number; totalEntryTasks: number }, + counts: { + contentTypesCount: number; + localesCount: number; + totalEntryChunks: number; + totalActualEntries: number; + totalEntriesForPublishing: number; + }, ) { - const { contentTypesCount, localesCount, totalEntryTasks } = counts; + const { contentTypesCount, totalEntryChunks, totalActualEntries, totalEntriesForPublishing } = counts; - // Add main processes - progress.addProcess('CT Preparation', contentTypesCount); - progress.addProcess('Create', totalEntryTasks); + // Use appropriate counts for each process + progress.addProcess(PROCESS_NAMES.CT_PREPARATION, contentTypesCount); + progress.addProcess(PROCESS_NAMES.ENTRIES_CREATE, totalActualEntries); // Use actual entries if (this.importConfig.replaceExisting) { - progress.addProcess('Replace Existing', totalEntryTasks); + progress.addProcess(PROCESS_NAMES.ENTRIES_REPLACE_EXISTING, totalActualEntries); } - progress.addProcess('Reference Updates', totalEntryTasks); - progress.addProcess('CT Restoration', contentTypesCount); - progress.addProcess('Field Rules Update', 1); + progress.addProcess(PROCESS_NAMES.REFERENCE_UPDATES, totalActualEntries); + progress.addProcess(PROCESS_NAMES.CT_RESTORATION, contentTypesCount); + progress.addProcess(PROCESS_NAMES.FIELD_RULES_UPDATE, 1); if (!this.importConfig.skipEntriesPublish) { - progress.addProcess('Publish', totalEntryTasks); + progress.addProcess(PROCESS_NAMES.ENTRIES_PUBLISH, totalEntriesForPublishing); } - progress.addProcess('Cleanup', 1); + progress.addProcess(PROCESS_NAMES.CLEANUP, 1); log.debug( - `Initialized progress tracking for ${contentTypesCount} content types across ${localesCount} locales`, + `Initialized progress tracking for ${contentTypesCount} content types`, this.importConfig.context, ); } @@ -344,6 +405,7 @@ export default class EntriesImport extends BaseClass { log.debug('Creating entry data for variant entries', this.importConfig.context); this.createEntryDataForVariantEntry(); + this.progressManager?.tick(true, 'Cleanup completed', null, PROCESS_NAMES.CLEANUP); } /** @@ -365,7 +427,7 @@ export default class EntriesImport extends BaseClass { ); const onSuccess = ({ response: contentType, apiData: { uid } }: any) => { - this.progressManager?.tick(true, `content type: ${uid}`, null, 'CT Preparation'); + this.progressManager?.tick(true, `content type: ${uid}`, null, PROCESS_NAMES.CT_PREPARATION); log.success(`${uid} content type references removed temporarily`, this.importConfig.context); }; const onReject = ({ error, apiData: { uid } }: any) => { @@ -373,7 +435,7 @@ export default class EntriesImport extends BaseClass { false, `content type: ${uid}`, error?.message || 'Failed to update content type', - 'CT Preparation', + PROCESS_NAMES.CT_PREPARATION, ); handleAndLogError(error, { ...this.importConfig.context, uid }); throw new Error(`${uid} content type references removal failed`); @@ -485,7 +547,7 @@ export default class EntriesImport extends BaseClass { if (indexerCount === 0) { log.debug(`No entries found for content type ${cTUid} in locale ${locale}`, this.importConfig.context); - this.progressManager?.tick(true, `${cTUid} - ${locale} (no entries)`, null, 'Create'); + //this.progressManager?.tick(true, `${cTUid} - ${locale} (no entries)`, null, PROCESS_NAMES.ENTRIES_CREATE); return Promise.resolve(); } @@ -521,7 +583,7 @@ export default class EntriesImport extends BaseClass { log.debug(`Found content type schema for ${cTUid}`, this.importConfig.context); const onSuccess = ({ response, apiData: entry, additionalInfo }: any) => { - this.progressManager?.tick(true, `${entry?.title} - ${entry?.uid}`, null, 'Create'); + this.progressManager?.tick(true, `${entry?.title} - ${entry?.uid}`, null, PROCESS_NAMES.ENTRIES_CREATE); if (additionalInfo[entry.uid]?.isLocalized) { let oldUid = additionalInfo[entry.uid].entryOldUid; this.entriesForVariant.push({ content_type: cTUid, entry_uid: oldUid, locale }); @@ -541,7 +603,10 @@ export default class EntriesImport extends BaseClass { ); log.debug(`Created entry UID mapping: ${entry.uid} → ${response.uid}`, this.importConfig.context); this.entriesForVariant.push({ content_type: cTUid, entry_uid: entry.uid, locale }); - + // This is for creating localized entries that do not have a counterpart in master locale. + // For example : To create entry1 in fr-fr, where en-us is the master locale + // entry1 will get created in en-us first, then fr-fr version will be created + // thus entry1 has to be removed from en-us at the end. if (!isMasterLocale && !additionalInfo[entry.uid]?.isLocalized) { this.autoCreatedEntries.push({ cTUid, locale, entryUid: response.uid }); log.debug(`Marked entry for auto-cleanup: ${response.uid} in master locale`, this.importConfig.context); @@ -555,7 +620,12 @@ export default class EntriesImport extends BaseClass { const onReject = ({ error, apiData: entry, additionalInfo }: any) => { const { title, uid } = entry; - this.progressManager?.tick(false, `${title} - ${uid}`, 'Error while creating entries', 'Create'); + this.progressManager?.tick( + false, + `${title} - ${uid}`, + 'Error while creating entries', + PROCESS_NAMES.ENTRIES_CREATE, + ); this.entriesForVariant = this.entriesForVariant.filter( (item) => !(item.locale === locale && item.entry_uid === uid), ); @@ -873,6 +943,7 @@ export default class EntriesImport extends BaseClass { const onSuccess = ({ response, apiData: { uid, url, title } }: any) => { log.info(`Updated entry: '${title}' of content type ${cTUid} in locale ${locale}`, this.importConfig.context); log.debug(`Updated entry references for: ${uid}`, this.importConfig.context); + this.progressManager?.tick(true, `${title} - ${uid}`, null, PROCESS_NAMES.REFERENCE_UPDATES); }; const onReject = ({ error, apiData: { uid, title } }: any) => { // NOTE Remove from list if any entry import failed @@ -888,6 +959,12 @@ export default class EntriesImport extends BaseClass { entry: { uid: this.entriesUidMapper[uid], title }, entryId: uid, }); + this.progressManager?.tick( + false, + `content type: ${cTUid}`, + error?.message || 'Failed to update references', + PROCESS_NAMES.REFERENCE_UPDATES, + ); }; for (const index in indexer) { @@ -998,7 +1075,7 @@ export default class EntriesImport extends BaseClass { async enableMandatoryCTReferences(): Promise { const onSuccess = ({ response: contentType, apiData: { uid } }: any) => { - this.progressManager?.tick(true, `content type: ${uid}`, null, 'CT Restoration'); + this.progressManager?.tick(true, `content type: ${uid}`, null, PROCESS_NAMES.CT_RESTORATION); log.success(`${uid} content type references updated`, this.importConfig.context); }; @@ -1007,7 +1084,7 @@ export default class EntriesImport extends BaseClass { false, `content type: ${uid}`, error?.message || 'Failed to restore content type', - 'CT Restoration', + PROCESS_NAMES.CT_RESTORATION, ); handleAndLogError(error, { ...this.importConfig.context, uid }, 'Error'); throw new Error(`Failed to update references of content type ${uid}`); @@ -1089,7 +1166,6 @@ export default class EntriesImport extends BaseClass { let cTsWithFieldRules = fsUtil.readFile(path.join(this.cTsPath + '/field_rules_uid.json')) as Record[]; if (!cTsWithFieldRules || cTsWithFieldRules?.length === 0) { log.debug('No content types with field rules found to update', this.importConfig.context); - this.progressManager?.tick(true, 'Field rules update completed (no rules found)', null, 'Field Rules Update'); return; } @@ -1172,19 +1248,18 @@ export default class EntriesImport extends BaseClass { log.info(`No field rules found in content type ${cTUid} to update`, this.importConfig.context); } } - this.progressManager?.tick( true, `Updated field rules for ${cTsWithFieldRules.length} content types`, null, - 'Field Rules Update', + PROCESS_NAMES.FIELD_RULES_UPDATE, ); } catch (error) { this.progressManager?.tick( false, 'Field rules update', (error as any)?.message || 'Field rules update failed', - 'Field Rules Update', + PROCESS_NAMES.FIELD_RULES_UPDATE, ); throw error; } @@ -1223,7 +1298,7 @@ export default class EntriesImport extends BaseClass { true, `Published the entry: '${entryUid}' of Content Type '${cTUid}' and Locale '${locale}`, null, - 'Publish', + PROCESS_NAMES.ENTRIES_PUBLISH, ); }; const onReject = ({ error, apiData: { environments, entryUid, locales }, additionalInfo }: any) => { @@ -1238,7 +1313,7 @@ export default class EntriesImport extends BaseClass { false, `Failed to publish: '${entryUid}' entry of Content Type '${cTUid}' and Locale '${locale}'`, `Failed to publish: '${entryUid}' entry of Content Type '${cTUid}' and Locale '${locale}'`, - 'Publish', + PROCESS_NAMES.ENTRIES_PUBLISH, ); }; diff --git a/packages/contentstack-import/src/import/modules/environments.ts b/packages/contentstack-import/src/import/modules/environments.ts index bd4f92b7f9..9db5fad33f 100644 --- a/packages/contentstack-import/src/import/modules/environments.ts +++ b/packages/contentstack-import/src/import/modules/environments.ts @@ -3,7 +3,7 @@ import values from 'lodash/values'; import { join } from 'node:path'; import { log, handleAndLogError } from '@contentstack/cli-utilities'; -import { fsUtil, fileHelper } from '../../utils'; +import { fsUtil, fileHelper, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; import BaseClass, { ApiOptions } from './base-class'; import { ModuleClassParams, EnvironmentConfig } from '../../types'; @@ -21,8 +21,8 @@ export default class ImportEnvironments extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'environments'; - this.currentModuleName = 'Environments'; + this.importConfig.context.module = MODULE_CONTEXTS.ENVIRONMENTS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.ENVIRONMENTS]; this.environmentsConfig = importConfig.modules.environments; this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'environments'); this.environmentsFolderPath = join(this.importConfig.backupDir, this.environmentsConfig.dirName); @@ -51,7 +51,7 @@ export default class ImportEnvironments extends BaseClass { const progress = this.createSimpleProgress(this.currentModuleName, environmentsCount); await this.prepareEnvironmentMapper(); - progress.updateStatus('Importing environments...'); + progress.updateStatus(PROCESS_STATUS[PROCESS_NAMES.ENVIRONMENTS_IMPORT].IMPORTING); await this.importEnvironments(); await this.processImportResults(); @@ -78,7 +78,7 @@ export default class ImportEnvironments extends BaseClass { const onSuccess = ({ response, apiData: { uid, name } = { uid: null, name: '' } }: any) => { this.envSuccess.push(response); this.envUidMapper[uid] = response.uid; - this.progressManager?.tick(true, `environment: ${name || uid}`); + this.progressManager?.tick(true, `environment: ${name || uid}`, null, PROCESS_NAMES.ENVIRONMENTS_IMPORT); log.success(`Environment '${name}' imported successfully`, this.importConfig.context); log.debug(`Environment UID mapping: ${uid} → ${response.uid}`, this.importConfig.context); fsUtil.writeFile(this.envUidMapperPath, this.envUidMapper); @@ -94,7 +94,12 @@ export default class ImportEnvironments extends BaseClass { const res = await this.getEnvDetails(name); this.envUidMapper[uid] = res?.uid || ' '; fsUtil.writeFile(this.envUidMapperPath, this.envUidMapper); - this.progressManager?.tick(true, `environment: ${name || uid} (already exists)`); + this.progressManager?.tick( + true, + null, + `environment: ${name || uid} (already exists)`, + PROCESS_NAMES.ENVIRONMENTS_IMPORT, + ); log.info(`Environment '${name}' already exists`, this.importConfig.context); log.debug(`Added existing environment UID mapping: ${uid} → ${res?.uid}`, this.importConfig.context); } else { @@ -103,6 +108,7 @@ export default class ImportEnvironments extends BaseClass { false, `environment: ${name || uid}`, error?.message || 'Failed to import environment', + PROCESS_NAMES.ENVIRONMENTS_IMPORT, ); handleAndLogError(error, { ...this.importConfig.context, name }, `Environment '${name}' failed to be import`); } @@ -145,7 +151,12 @@ export default class ImportEnvironments extends BaseClass { ); log.debug(`Skipping environment serialization for: ${environment.uid}`, this.importConfig.context); // Still tick progress for skipped environments - this.progressManager?.tick(true, `environment: ${environment.name} (skipped - already exists)`); + this.progressManager?.tick( + true, + `environment: ${environment.name}`, + `environment: ${environment.name} (skipped - already exists)`, + PROCESS_NAMES.ENVIRONMENTS_IMPORT, + ); apiOptions.entity = undefined; } else { log.debug(`Processing environment: ${environment.name}`, this.importConfig.context); diff --git a/packages/contentstack-import/src/import/modules/extensions.ts b/packages/contentstack-import/src/import/modules/extensions.ts index 0cec3980e2..ab0e874e88 100644 --- a/packages/contentstack-import/src/import/modules/extensions.ts +++ b/packages/contentstack-import/src/import/modules/extensions.ts @@ -4,7 +4,7 @@ import cloneDeep from 'lodash/cloneDeep'; import { join } from 'node:path'; import { log, handleAndLogError } from '@contentstack/cli-utilities'; -import { fsUtil, fileHelper } from '../../utils'; +import { fsUtil, fileHelper, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; import BaseClass, { ApiOptions } from './base-class'; import { ModuleClassParams, Extensions, ExtensionType } from '../../types'; @@ -25,8 +25,8 @@ export default class ImportExtensions extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'extensions'; - this.currentModuleName = 'Extensions'; + this.importConfig.context.module = MODULE_CONTEXTS.EXTENSIONS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.EXTENSIONS]; this.extensionsConfig = importConfig.modules.extensions; this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'extensions'); this.extensionsFolderPath = join(this.importConfig.backupDir, this.extensionsConfig.dirName); @@ -55,32 +55,39 @@ export default class ImportExtensions extends BaseClass { } const progress = this.createNestedProgress(this.currentModuleName); - progress.addProcess('Create', extensionsCount); + progress.addProcess(PROCESS_NAMES.EXTENSIONS_CREATE, extensionsCount); await this.prepareExtensionMapper(); log.debug('Checking content types in extension scope', this.importConfig.context); this.getContentTypesInScope(); - progress.startProcess('Create').updateStatus('Importing extensions...', 'Create'); + progress + .startProcess(PROCESS_NAMES.EXTENSIONS_CREATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.EXTENSIONS_CREATE].CREATING, PROCESS_NAMES.EXTENSIONS_CREATE); log.debug('Starting Create', this.importConfig.context); await this.importExtensions(); - progress.completeProcess('Create', true); + progress.completeProcess(PROCESS_NAMES.EXTENSIONS_CREATE, true); log.debug('Updating extension UIDs', this.importConfig.context); this.updateUidExtension(); if (this.importConfig.replaceExisting && this.existingExtensions.length > 0) { - progress.addProcess('Replace existing', this.existingExtensions.length); - progress.startProcess('Replace existing').updateStatus('Updating existing extensions...', 'Replace existing'); + progress.addProcess(PROCESS_NAMES.EXTENSIONS_REPLACE_EXISTING, this.existingExtensions.length); + progress + .startProcess(PROCESS_NAMES.EXTENSIONS_REPLACE_EXISTING) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.EXTENSIONS_REPLACE_EXISTING].REPLACING, + PROCESS_NAMES.EXTENSIONS_REPLACE_EXISTING, + ); await this.replaceExtensions(); - progress.completeProcess('Replace existing', true); + progress.completeProcess(PROCESS_NAMES.EXTENSIONS_REPLACE_EXISTING, true); } await this.processExtensionResults(); this.completeProgress(true); - log.success('Extensions have been imported successfully!', this.importConfig.context); + log.success('Extensions have been imported successfully!', this.importConfig.context); } catch (error) { this.completeProgress(false, error?.message || 'Create failed'); handleAndLogError(error, { ...this.importConfig.context }); @@ -100,7 +107,7 @@ export default class ImportExtensions extends BaseClass { const onSuccess = ({ response, apiData: { uid, title } = { uid: null, title: '' } }: any) => { this.extSuccess.push(response); this.extUidMapper[uid] = response.uid; - this.progressManager?.tick(true, `extension: ${title || uid}`, null, 'Create'); + this.progressManager?.tick(true, `extension: ${title || uid}`, null, PROCESS_NAMES.EXTENSIONS_CREATE); log.success(`Extension '${title}' imported successfully`, this.importConfig.context); log.debug(`Extension import completed: ${title} (${uid})`, this.importConfig.context); fsUtil.writeFile(this.extUidMapperPath, this.extUidMapper); @@ -117,11 +124,16 @@ export default class ImportExtensions extends BaseClass { true, `extension: ${title || uid} (marked for replacement)`, null, - 'Create', + PROCESS_NAMES.EXTENSIONS_CREATE, ); log.debug(`Extension '${title}' marked for replacement`, this.importConfig.context); } else { - this.progressManager?.tick(true, `extension: ${title || uid} (already exists)`, null, 'Create'); + this.progressManager?.tick( + true, + `extension: ${title || uid} (already exists)`, + null, + PROCESS_NAMES.EXTENSIONS_CREATE, + ); } if (!this.importConfig.skipExisting) { log.info(`Extension '${title}' already exists`, this.importConfig.context); @@ -132,7 +144,7 @@ export default class ImportExtensions extends BaseClass { false, `extension: ${title || uid}`, error?.message || 'Failed to import extension', - 'Create', + PROCESS_NAMES.EXTENSIONS_CREATE, ); handleAndLogError(error, { ...this.importConfig.context, title }, `Extension '${title}' failed to be import`); } @@ -167,7 +179,12 @@ export default class ImportExtensions extends BaseClass { const onSuccess = ({ response, apiData: { uid, title } = { uid: null, title: '' } }: any) => { this.extSuccess.push(response); this.extUidMapper[uid] = response.uid; - this.progressManager?.tick(true, `extension: ${title || uid} (updated)`, null, 'Replace existing'); + this.progressManager?.tick( + true, + `extension: ${title || uid} (updated)`, + null, + PROCESS_NAMES.EXTENSIONS_REPLACE_EXISTING, + ); log.success(`Extension '${title}' updated successfully`, this.importConfig.context); log.debug(`Extension update completed: ${title} (${uid})`, this.importConfig.context); fsUtil.writeFile(this.extUidMapperPath, this.extUidMapper); @@ -180,7 +197,7 @@ export default class ImportExtensions extends BaseClass { false, `extension: ${title || uid}`, error?.message || 'Failed to update extension', - 'Replace existing', + PROCESS_NAMES.EXTENSIONS_REPLACE_EXISTING, ); log.debug(`Extension '${title}' update failed`, this.importConfig.context); handleAndLogError(error, { ...this.importConfig.context, title }, `Extension '${title}' failed to be updated`); diff --git a/packages/contentstack-import/src/import/modules/global-fields.ts b/packages/contentstack-import/src/import/modules/global-fields.ts index ad72e22f05..422f65e9f4 100644 --- a/packages/contentstack-import/src/import/modules/global-fields.ts +++ b/packages/contentstack-import/src/import/modules/global-fields.ts @@ -7,9 +7,19 @@ import * as path from 'path'; import { isEmpty, cloneDeep } from 'lodash'; -import { cliux, sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; -import { GlobalFieldData, GlobalField } from '@contentstack/management/types/stack/globalField'; -import { fsUtil, fileHelper, lookupExtension, removeReferenceFields } from '../../utils'; +import { GlobalField } from '@contentstack/management/types/stack/globalField'; +import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; + +import { + fsUtil, + fileHelper, + lookupExtension, + removeReferenceFields, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, +} from '../../utils'; import { ImportConfig, ModuleClassParams } from '../../types'; import BaseClass, { ApiOptions } from './base-class'; import { gfSchemaTemplate } from '../../utils/global-field-helper'; @@ -42,8 +52,8 @@ export default class ImportGlobalFields extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'global-fields'; - this.currentModuleName = 'Global Fields'; + this.importConfig.context.module = MODULE_CONTEXTS.GLOBAL_FIELDS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.GLOBAL_FIELDS]; this.config = importConfig; this.gFsConfig = importConfig.modules['global-fields']; this.gFs = []; @@ -86,34 +96,39 @@ export default class ImportGlobalFields extends BaseClass { } const progress = this.createNestedProgress(this.currentModuleName); - progress.addProcess('Create', globalFieldsCount); - progress.addProcess('Update', globalFieldsCount); + progress.addProcess(PROCESS_NAMES.GLOBAL_FIELDS_CREATE, globalFieldsCount); + progress.addProcess(PROCESS_NAMES.GLOBAL_FIELDS_UPDATE, globalFieldsCount); await this.prepareGlobalFieldMapper(); // Step 1: Create global fields progress - .startProcess('Create') - .updateStatus('Creating global fields...', 'Create'); + .startProcess(PROCESS_NAMES.GLOBAL_FIELDS_CREATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.GLOBAL_FIELDS_CREATE].CREATING, PROCESS_NAMES.GLOBAL_FIELDS_CREATE); log.info('Starting Create process', this.importConfig.context); await this.seedGFs(); - progress.completeProcess('Create', true); + progress.completeProcess(PROCESS_NAMES.GLOBAL_FIELDS_CREATE, true); // Step 2: Update global fields with references - progress.startProcess('Update').updateStatus('Updating global fields', 'Update'); + progress + .startProcess(PROCESS_NAMES.GLOBAL_FIELDS_UPDATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.GLOBAL_FIELDS_UPDATE].UPDATING, PROCESS_NAMES.GLOBAL_FIELDS_UPDATE); log.info('Starting Update process', this.importConfig.context); await this.updateGFs(); - progress.completeProcess('Update', true); + progress.completeProcess(PROCESS_NAMES.GLOBAL_FIELDS_UPDATE, true); // Step 3: Replace existing global fields if needed if (this.importConfig.replaceExisting && this.existingGFs.length > 0) { - progress.addProcess('Replace Existing', this.existingGFs.length); + progress.addProcess(PROCESS_NAMES.GLOBAL_FIELDS_REPLACE_EXISTING, this.existingGFs.length); progress - .startProcess('Replace Existing') - .updateStatus('Replacing existing global fields...', 'Replace Existing'); + .startProcess(PROCESS_NAMES.GLOBAL_FIELDS_REPLACE_EXISTING) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.GLOBAL_FIELDS_REPLACE_EXISTING].REPLACING, + PROCESS_NAMES.GLOBAL_FIELDS_REPLACE_EXISTING, + ); log.info('Starting Replace Existing process', this.importConfig.context); await this.replaceGFs(); - progress.completeProcess('Replace Existing', true); + progress.completeProcess(PROCESS_NAMES.GLOBAL_FIELDS_REPLACE_EXISTING, true); } await this.processGlobalFieldResults(); @@ -135,7 +150,7 @@ export default class ImportGlobalFields extends BaseClass { const onSuccess = ({ response: globalField, apiData: { uid } = undefined }: any) => { this.createdGFs.push(globalField); this.gFsUidMapper[uid] = globalField; - this.progressManager?.tick(true, `global field: ${globalField.uid}`, null, 'Create'); + this.progressManager?.tick(true, `global field: ${globalField.uid}`, null, PROCESS_NAMES.GLOBAL_FIELDS_CREATE); log.success(`Global field ${globalField.uid} created successfully`, this.importConfig.context); log.debug(`Global field Create completed: ${globalField.uid}`, this.importConfig.context); }; @@ -151,11 +166,16 @@ export default class ImportGlobalFields extends BaseClass { true, `global field: ${uid} (marked for replacement)`, null, - 'Create', + PROCESS_NAMES.GLOBAL_FIELDS_CREATE, ); log.debug(`Global field '${uid}' marked for replacement`, this.importConfig.context); } else { - this.progressManager?.tick(true, `global field: ${uid} (already exists)`, null, 'Create'); + this.progressManager?.tick( + true, + `global field: ${uid} (already exists)`, + null, + PROCESS_NAMES.GLOBAL_FIELDS_CREATE, + ); } if (!this.importConfig.skipExisting) { log.info(`Global fields '${uid}' already exist`, this.importConfig.context); @@ -165,7 +185,7 @@ export default class ImportGlobalFields extends BaseClass { false, `global field: ${uid}`, error?.message || 'Failed to create global field', - 'Create', + PROCESS_NAMES.GLOBAL_FIELDS_CREATE, ); handleAndLogError(error, { ...this.importConfig.context, uid }, `Global fields '${uid}' failed to import`); this.failedGFs.push({ uid }); @@ -215,7 +235,7 @@ export default class ImportGlobalFields extends BaseClass { log.debug(`Updating ${gfsToUpdate} global fields`, this.importConfig.context); const onSuccess = ({ response: globalField, apiData: { uid } = undefined }: any) => { - this.progressManager?.tick(true, `global field: ${uid}`, null, 'Update'); + this.progressManager?.tick(true, `global field: ${uid}`, null, PROCESS_NAMES.GLOBAL_FIELDS_UPDATE); log.success(`Updated the global field ${uid}`, this.importConfig.context); log.debug(`Global field update completed: ${uid}`, this.importConfig.context); }; @@ -225,7 +245,7 @@ export default class ImportGlobalFields extends BaseClass { false, `global field: ${uid}`, error?.message || 'Failed to update global field', - 'Update', + PROCESS_NAMES.GLOBAL_FIELDS_UPDATE, ); log.debug(`Global field '${uid}' update failed`, this.importConfig.context); handleAndLogError(error, { ...this.importConfig.context, uid }, `Failed to update the global field '${uid}'`); @@ -315,7 +335,12 @@ export default class ImportGlobalFields extends BaseClass { const uid = apiData?.uid ?? apiData?.global_field?.uid ?? 'unknown'; this.createdGFs.push(globalField); this.gFsUidMapper[uid] = globalField; - this.progressManager?.tick(true, `global field: ${uid} (replaced)`, null, 'Replace Existing'); + this.progressManager?.tick( + true, + `global field: ${uid} (replaced)`, + null, + PROCESS_NAMES.GLOBAL_FIELDS_REPLACE_EXISTING, + ); fsUtil.writeFile(this.gFsUidMapperPath, this.gFsUidMapper); log.success(`Global field '${uid}' replaced successfully`, this.importConfig.context); log.debug(`Global field replacement completed: ${uid}`, this.importConfig.context); @@ -327,7 +352,7 @@ export default class ImportGlobalFields extends BaseClass { false, `global field: ${uid}`, error?.message || 'Failed to replace global field', - 'Replace Existing', + PROCESS_NAMES.GLOBAL_FIELDS_REPLACE_EXISTING, ); log.debug(`Global field '${uid}' replacement failed`, this.importConfig.context); handleAndLogError(error, { ...this.importConfig.context, uid }, `Global fields '${uid}' failed to replace`); diff --git a/packages/contentstack-import/src/import/modules/labels.ts b/packages/contentstack-import/src/import/modules/labels.ts index 1cea3d8dda..397a860987 100644 --- a/packages/contentstack-import/src/import/modules/labels.ts +++ b/packages/contentstack-import/src/import/modules/labels.ts @@ -4,7 +4,7 @@ import isEmpty from 'lodash/isEmpty'; import values from 'lodash/values'; import { log, handleAndLogError } from '@contentstack/cli-utilities'; -import { fsUtil, fileHelper } from '../../utils'; +import { fsUtil, fileHelper, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; import BaseClass, { ApiOptions } from './base-class'; import { ModuleClassParams, LabelConfig } from '../../types'; @@ -22,8 +22,8 @@ export default class ImportLabels extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'labels'; - this.currentModuleName = 'Labels'; + this.importConfig.context.module = MODULE_CONTEXTS.LABELS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.LABELS]; this.labelsConfig = importConfig.modules.labels; this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'labels'); this.labelsFolderPath = join(this.importConfig.backupDir, this.labelsConfig.dirName); @@ -50,22 +50,26 @@ export default class ImportLabels extends BaseClass { } const progress = this.createNestedProgress(this.currentModuleName); - progress.addProcess('Create', labelsCount); - progress.addProcess('Update', labelsCount); + progress.addProcess(PROCESS_NAMES.LABELS_CREATE, labelsCount); + progress.addProcess(PROCESS_NAMES.LABELS_UPDATE, labelsCount); await this.prepareLabelMapper(); // Step 1: Import labels (without parent references) - progress.startProcess('Create').updateStatus('Creating labels...', 'Create'); + progress + .startProcess(PROCESS_NAMES.LABELS_CREATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.LABELS_CREATE].CREATING, PROCESS_NAMES.LABELS_CREATE); log.info('Starting labels creation process', this.importConfig.context); await this.importLabels(); - progress.completeProcess('Create', true); + progress.completeProcess(PROCESS_NAMES.LABELS_CREATE, true); // Step 2: Update labels with parent references - progress.startProcess('Update').updateStatus('Updating labels with parent references...', 'Update'); + progress + .startProcess(PROCESS_NAMES.LABELS_UPDATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.LABELS_UPDATE].UPDATING, PROCESS_NAMES.LABELS_UPDATE); log.info('Starting labels update process', this.importConfig.context); await this.updateLabels(); - progress.completeProcess('Update', true); + progress.completeProcess(PROCESS_NAMES.LABELS_UPDATE, true); this.processLabelResults(); @@ -89,7 +93,7 @@ export default class ImportLabels extends BaseClass { const onSuccess = ({ response, apiData: { uid, name } = { uid: null, name: '' } }: any) => { this.labelUidMapper[uid] = response; - this.progressManager?.tick(true, `label: ${name || uid}`, null, 'Create'); + this.progressManager?.tick(true, `label: ${name || uid}`, null, PROCESS_NAMES.LABELS_CREATE); log.success(`Label '${name}' imported successfully`, this.importConfig.context); log.debug(`Label UID mapping: ${uid} → ${response.uid}`, this.importConfig.context); fsUtil.writeFile(this.labelUidMapperPath, this.labelUidMapper); @@ -101,7 +105,7 @@ export default class ImportLabels extends BaseClass { log.debug(`Label '${name}' (${uid}) failed to import`, this.importConfig.context); if (err?.errors?.name) { - this.progressManager?.tick(true, `label: ${name || uid} (already exists)`, null, 'Create'); + this.progressManager?.tick(true, `label: ${name || uid} (already exists)`, null, PROCESS_NAMES.LABELS_CREATE); log.info(`Label '${name}' already exists`, this.importConfig.context); } else { this.failedLabel.push(apiData); @@ -109,7 +113,7 @@ export default class ImportLabels extends BaseClass { false, `label: ${name || uid}`, error?.message || 'Failed to import label', - 'Create', + PROCESS_NAMES.LABELS_CREATE, ); handleAndLogError(error, { ...this.importConfig.context, name }, `Label '${name}' failed to be import`); } @@ -148,7 +152,12 @@ export default class ImportLabels extends BaseClass { if (this.labelUidMapper.hasOwnProperty(label.uid)) { log.info(`Label '${label.name}' already exists. Skipping it to avoid duplicates!`, this.importConfig.context); log.debug(`Skipping label serialization for: ${label.uid}`, this.importConfig.context); - this.progressManager?.tick(true, `label: ${label.name} (skipped - already exists)`, null, 'Create'); + this.progressManager?.tick( + true, + `label: ${label.name} (skipped - already exists)`, + null, + PROCESS_NAMES.LABELS_CREATE, + ); apiOptions.entity = undefined; } else { let labelReq = label; @@ -172,7 +181,7 @@ export default class ImportLabels extends BaseClass { const onSuccess = ({ response, apiData: { uid, name } = { uid: null, name: '' } }: any) => { this.createdLabel.push(response); - this.progressManager?.tick(true, `label: ${name || uid}`, null, 'Update'); + this.progressManager?.tick(true, `label: ${name || uid}`, null, PROCESS_NAMES.LABELS_UPDATE); log.success(`Label '${name}' updated successfully`, this.importConfig.context); log.debug(`Label update completed: ${name} (${uid})`, this.importConfig.context); }; @@ -183,7 +192,7 @@ export default class ImportLabels extends BaseClass { false, `label: ${name || uid}`, error?.message || 'Failed to update label', - 'Update', + PROCESS_NAMES.LABELS_UPDATE, ); log.debug(`Label '${name}' update failed`, this.importConfig.context); handleAndLogError(error, { ...this.importConfig.context, name: name }, `Failed to update label '${name}'`); @@ -247,13 +256,18 @@ export default class ImportLabels extends BaseClass { log.debug(`Updated label '${label.name}' with parent references`, this.importConfig.context); } else { log.debug(`Label '${label.name}' has no parent labels, adding to created list`, this.importConfig.context); - this.progressManager?.tick(true, `label: ${label.name} (no parent update needed)`, null, 'Update'); + this.progressManager?.tick( + true, + `label: ${label.name} (no parent update needed)`, + null, + PROCESS_NAMES.LABELS_UPDATE, + ); apiOptions.entity = undefined; this.createdLabel.push(newLabel); } } else { log.debug(`Label '${label.name}' not found in UID mapper, skipping update`, this.importConfig.context); - this.progressManager?.tick(true, `label: ${label.name} (skipped - not found)`, null, 'Update'); + this.progressManager?.tick(true, `label: ${label.name} (skipped - not found)`, null, PROCESS_NAMES.LABELS_UPDATE); apiOptions.entity = undefined; } return apiOptions; diff --git a/packages/contentstack-import/src/import/modules/locales.ts b/packages/contentstack-import/src/import/modules/locales.ts index c262d007c6..d51756a743 100644 --- a/packages/contentstack-import/src/import/modules/locales.ts +++ b/packages/contentstack-import/src/import/modules/locales.ts @@ -8,9 +8,18 @@ import * as path from 'path'; import { values, isEmpty, filter, pick, keys } from 'lodash'; import { cliux, sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; -import { fsUtil, formatError, fileHelper } from '../../utils'; -import { ImportConfig, ModuleClassParams } from '../../types'; + import BaseClass from './base-class'; +import { + fsUtil, + formatError, + fileHelper, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, +} from '../../utils'; +import { ImportConfig, ModuleClassParams } from '../../types'; export default class ImportLocales extends BaseClass { private langMapperPath: string; @@ -40,8 +49,8 @@ export default class ImportLocales extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); this.config = importConfig; - this.config.context.module = 'locales'; - this.currentModuleName = 'Locales'; + this.config.context.module = MODULE_CONTEXTS.LOCALES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.LOCALES]; this.localeConfig = importConfig.modules.locales; this.masterLanguage = importConfig.masterLocale; this.masterLanguageConfig = importConfig.modules.masterLocale; @@ -123,14 +132,19 @@ export default class ImportLocales extends BaseClass { const onSuccess = ({ response = {}, apiData: { uid, code } = undefined }: any) => { this.langUidMapper[uid] = response.uid; this.createdLocales.push(pick(response, [...this.localeConfig.requiredKeys])); - this.progressManager?.tick(true, `locale: ${code}`, null, 'Create'); + this.progressManager?.tick(true, `locale: ${code}`, null, PROCESS_NAMES.LOCALES_CREATE); log.info(`Created locale: '${code}'`, this.config.context); log.debug(`Locale UID mapping: ${uid} → ${response.uid}`, this.config.context); fsUtil.writeFile(this.langUidMapperPath, this.langUidMapper); }; const onReject = ({ error, apiData: { uid, code } = undefined }: any) => { - this.progressManager?.tick(false, `locale: ${code}`, error?.message || 'Failed to create locale', 'Create'); + this.progressManager?.tick( + false, + `locale: ${code}`, + error?.message || 'Failed to create locale', + PROCESS_NAMES.LOCALES_CREATE, + ); if (error?.errorCode === 247) { log.info(formatError(error), this.config.context); } else { @@ -159,12 +173,12 @@ export default class ImportLocales extends BaseClass { const onSuccess = ({ response = {}, apiData: { uid, code } = undefined }: any) => { log.info(`Updated locale: '${code}'`, this.config.context); log.debug(`Locale update completed for: ${code}`, this.config.context); - this.progressManager?.tick(true, `locale: ${code}`, null, 'Update'); + this.progressManager?.tick(true, `locale: ${code}`, null, PROCESS_NAMES.LOCALES_UPDATE); fsUtil.writeFile(this.langSuccessPath, this.createdLocales); }; const onReject = ({ error, apiData: { uid, code } = undefined }: any) => { - this.progressManager?.tick(false, `locale: ${code}`, 'Failed to update locale', 'Update'); + this.progressManager?.tick(false, `locale: ${code}`, 'Failed to update locale', PROCESS_NAMES.LOCALES_UPDATE); log.error(`Language '${code}' failed to update`, this.config.context); handleAndLogError(error, { ...this.config.context, code }); fsUtil.writeFile(this.langFailsPath, this.failedLocales); @@ -211,10 +225,10 @@ export default class ImportLocales extends BaseClass { private setupLocalesProgress(localesCount: number) { const progress = this.createNestedProgress(this.currentModuleName); - progress.addProcess('Master Locale ', 1); + progress.addProcess(PROCESS_NAMES.MASTER_LOCALE, 1); if (localesCount > 0) { - progress.addProcess('Create', localesCount); - progress.addProcess('Update', localesCount); + progress.addProcess(PROCESS_NAMES.LOCALES_CREATE, localesCount); + progress.addProcess(PROCESS_NAMES.LOCALES_UPDATE, localesCount); } return progress; } @@ -234,41 +248,47 @@ export default class ImportLocales extends BaseClass { } private async processMasterLocale(progress: any): Promise { - progress.startProcess('Master Locale ').updateStatus('Checking master locale...', 'Master Locale '); + progress + .startProcess(PROCESS_NAMES.MASTER_LOCALE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.MASTER_LOCALE].PROCESSING, PROCESS_NAMES.MASTER_LOCALE); log.debug('Checking and updating master locale', this.config.context); try { await this.checkAndUpdateMasterLocale(); - progress.completeProcess('Master Locale ', true); + progress.completeProcess(PROCESS_NAMES.MASTER_LOCALE, true); } catch (error) { - progress.completeProcess('Master Locale ', false); + progress.completeProcess(PROCESS_NAMES.MASTER_LOCALE, false); //NOTE:- Continue locale creation in case of master locale error handleAndLogError(error, { ...this.config.context }); } } private async processLocaleCreation(progress: any): Promise { - progress.startProcess('Create').updateStatus('Creating locales...', 'Create'); + progress + .startProcess(PROCESS_NAMES.LOCALES_CREATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.LOCALES_CREATE].CREATING, PROCESS_NAMES.LOCALES_CREATE); log.debug('Creating locales', this.config.context); try { await this.createLocales(); - progress.completeProcess('Create', true); + progress.completeProcess(PROCESS_NAMES.LOCALES_CREATE, true); } catch (error) { - progress.completeProcess('Create', false); + progress.completeProcess(PROCESS_NAMES.LOCALES_CREATE, false); throw error; } } private async processLocaleUpdate(progress: any): Promise { - progress.startProcess('Update').updateStatus('Updating locales...', 'Update'); + progress + .startProcess(PROCESS_NAMES.LOCALES_UPDATE) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.LOCALES_UPDATE].UPDATING, PROCESS_NAMES.LOCALES_UPDATE); log.debug('Updating locales', this.config.context); try { await this.updateLocales(); - progress.completeProcess('Update', true); + progress.completeProcess(PROCESS_NAMES.LOCALES_UPDATE, true); } catch (error) { - progress.completeProcess('Update', false); + progress.completeProcess(PROCESS_NAMES.LOCALES_UPDATE, false); throw error; } } @@ -350,6 +370,6 @@ export default class ImportLocales extends BaseClass { } private tickProgress(success: boolean, message: string, error?: string): void { - this.progressManager?.tick(success, `master locale: ${message}`, error || null, 'Master Locale '); + this.progressManager?.tick(success, `master locale: ${message}`, error || null, PROCESS_NAMES.MASTER_LOCALE); } } diff --git a/packages/contentstack-import/src/import/modules/marketplace-apps.ts b/packages/contentstack-import/src/import/modules/marketplace-apps.ts index 056e6acc0e..cfcb41de5b 100644 --- a/packages/contentstack-import/src/import/modules/marketplace-apps.ts +++ b/packages/contentstack-import/src/import/modules/marketplace-apps.ts @@ -35,6 +35,10 @@ import { getAllStackSpecificApps, getConfirmationToCreateApps, getDeveloperHubUrl, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, } from '../../utils'; import BaseClass from './base-class'; @@ -56,8 +60,8 @@ export default class ImportMarketplaceApps extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'marketplace-apps'; - this.currentModuleName = 'Marketplace Apps'; + this.importConfig.context.module = MODULE_CONTEXTS.MARKETPLACE_APPS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.MARKETPLACE_APPS]; this.marketPlaceAppConfig = importConfig.modules.marketplace_apps; this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'marketplace_apps'); this.marketPlaceFolderPath = join(this.importConfig.backupDir, this.marketPlaceAppConfig.dirName); @@ -93,38 +97,49 @@ export default class ImportMarketplaceApps extends BaseClass { return; } + // Handle encryption key prompt BEFORE starting progress + if (!this.importConfig.forceStopMarketplaceAppsPrompt) { + log.debug('Validating security configuration before progress start', this.importConfig.context); + await this.getAndValidateEncryptionKey(this.importConfig.marketplaceAppEncryptionKey); + } + const progress = this.createNestedProgress(this.currentModuleName); const privateAppsCount = filter(this.marketplaceApps, { manifest: { visibility: 'private' } }).length; - progress.addProcess('Setup Environment', 1); + progress.addProcess(PROCESS_NAMES.SETUP_ENVIRONMENT, 1); if (privateAppsCount > 0) { - progress.addProcess('Create Apps', privateAppsCount); + progress.addProcess(PROCESS_NAMES.CREATE_APPS, privateAppsCount); } - progress.addProcess('Install', marketplaceAppsCount); + progress.addProcess(PROCESS_NAMES.INSTALL_APPS, marketplaceAppsCount); - await this.prepareMarketplaceAppMapper(); + this.prepareMarketplaceAppMapper(); // Step 1: Setup Environment SDK and authentication - progress.startProcess('Setup Environment').updateStatus('Setting up marketplace SDK and authentication...', 'Setup Environment'); log.info('Setting up marketplace SDK and authentication', this.importConfig.context); + progress + .startProcess(PROCESS_NAMES.SETUP_ENVIRONMENT) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.SETUP_ENVIRONMENT].SETTING_UP, PROCESS_NAMES.SETUP_ENVIRONMENT); await this.setupMarketplaceEnvironment(); - progress.completeProcess('Setup Environment', true); + progress.completeProcess(PROCESS_NAMES.SETUP_ENVIRONMENT, true); // Step 2: Handle private apps creation (if any) if (privateAppsCount > 0) { - progress - .startProcess('Create Apps') - .updateStatus('Creating private apps...', 'Create Apps'); log.info('Starting private apps creation process', this.importConfig.context); + progress + .startProcess(PROCESS_NAMES.CREATE_APPS) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.CREATE_APPS].CREATING, PROCESS_NAMES.CREATE_APPS); await this.handleAllPrivateAppsCreationProcess(); - progress.completeProcess('Create Apps', true); + progress.completeProcess(PROCESS_NAMES.CREATE_APPS, true); } - // Step 3: Install marketplace apps - progress.startProcess('Install').updateStatus('Installing marketplace apps...', 'Install'); + // Step 3: Install marketplace apps - FIXED THIS PART log.info('Starting marketplace apps installation process', this.importConfig.context); + progress + .startProcess(PROCESS_NAMES.INSTALL_APPS) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.INSTALL_APPS].INSTALLING, PROCESS_NAMES.INSTALL_APPS); + await this.importMarketplaceApps(); - progress.completeProcess('Install', true); + progress.completeProcess(PROCESS_NAMES.INSTALL_APPS, true); this.completeProgress(true); log.success('Marketplace apps have been imported successfully!', this.importConfig.context); @@ -146,11 +161,7 @@ export default class ImportMarketplaceApps extends BaseClass { if (this.importConfig.forceStopMarketplaceAppsPrompt) { log.debug('Using forced security configuration without validation', this.importConfig.context); this.nodeCrypto = new NodeCrypto(cryptoArgs); - } else { - log.debug('Validating security configuration', this.importConfig.context); - await this.getAndValidateEncryptionKey(this.importConfig.marketplaceAppEncryptionKey); } - // NOTE getting all apps to validate if it's already installed in the stack to manage conflict log.debug('Getting all stack-specific apps for validation', this.importConfig.context); this.installedApps = await getAllStackSpecificApps(this.importConfig); @@ -258,6 +269,7 @@ export default class ImportMarketplaceApps extends BaseClass { } log.debug('Found app configuration requiring security setup, asking for input', this.importConfig.context); + cliux.print('\n'); const encryptionKey = await askEncryptionKey(defaultValue); try { @@ -310,6 +322,7 @@ export default class ImportMarketplaceApps extends BaseClass { } log.debug('Getting confirmation to create private apps', this.importConfig.context); + cliux.print('\n'); let canCreatePrivateApp = await getConfirmationToCreateApps(privateApps, this.importConfig); this.importConfig.canCreatePrivateApp = canCreatePrivateApp; @@ -322,12 +335,7 @@ export default class ImportMarketplaceApps extends BaseClass { if (await this.isPrivateAppExistInDeveloperHub(app)) { // NOTE Found app already exist in the same org this.appUidMapping[app.uid] = app.uid; - this.progressManager?.tick( - true, - `${app.manifest.name} (already exists)`, - null, - 'Create Apps', - ); + this.progressManager?.tick(true, `${app.manifest.name} (already exists)`, null, PROCESS_NAMES.CREATE_APPS); cliux.print(`App '${app.manifest.name}' already exist. skipping app recreation.!`, { color: 'yellow' }); log.debug(`App '${app.manifest.name}' already exists, skipping recreation`, this.importConfig.context); continue; @@ -359,12 +367,7 @@ export default class ImportMarketplaceApps extends BaseClass { log.info('Skipping private apps creation on Developer Hub...', this.importConfig.context); // Mark all private apps as skipped in progress for (let app of privateApps) { - this.progressManager?.tick( - true, - `${app.manifest.name} (creation skipped)`, - null, - 'Create Apps', - ); + this.progressManager?.tick(true, `${app.manifest.name} (creation skipped)`, null, PROCESS_NAMES.CREATE_APPS); } } @@ -534,7 +537,7 @@ export default class ImportMarketplaceApps extends BaseClass { log.debug(`Retrying app creation with updated name: ${updatedApp.name}`, this.importConfig.context); return this.createPrivateApp(updatedApp, appSuffix + 1, true); } else { - this.progressManager?.tick(false, `${app.name}`, message, 'Create Apps'); + this.progressManager?.tick(false, `${app.name}`, message, PROCESS_NAMES.CREATE_APPS); trace(response, 'error', true); log.error(formatError(message), this.importConfig.context); @@ -559,19 +562,14 @@ export default class ImportMarketplaceApps extends BaseClass { } } else if (response.uid) { // NOTE new app installation - this.progressManager?.tick(true, `${response.name}`, null, 'Create Apps'); + this.progressManager?.tick(true, `${response.name}`, null, PROCESS_NAMES.CREATE_APPS); log.success(`${response.name} app created successfully.!`, this.importConfig.context); log.debug(`App UID mapping: ${app.uid} → ${response.uid}`, this.importConfig.context); this.appUidMapping[app.uid] = response.uid; this.appNameMapping[this.appOriginalName] = response.name; log.debug(`App name mapping: ${this.appOriginalName} → ${response.name}`, this.importConfig.context); } else { - this.progressManager?.tick( - false, - `${app.name}`, - 'Unexpected response format', - 'Create Apps', - ); + this.progressManager?.tick(false, `${app.name}`, 'Unexpected response format', PROCESS_NAMES.CREATE_APPS); log.debug(`Unexpected response format for app: ${app.name}`, this.importConfig.context); } } @@ -584,86 +582,102 @@ export default class ImportMarketplaceApps extends BaseClass { * @returns {Promise} */ async installApps(app: any): Promise { - log.debug(`Installing app: ${app.manifest?.name || app.manifest?.uid}`, this.importConfig.context); - let updateParam; - const { configuration, server_configuration } = app; - const currentStackApp = find(this.installedApps, { manifest: { uid: app?.manifest?.uid } }); - - if (!currentStackApp) { - log.debug(`App not found in current stack, installing new app: ${app.manifest?.name}`, this.importConfig.context); - // NOTE install new app - if (app.manifest.visibility === 'private' && !this.importConfig.canCreatePrivateApp) { + try { + log.debug(`Installing app: ${app.manifest?.name || app.manifest?.uid}`, this.importConfig.context); + let updateParam; + const { configuration, server_configuration } = app; + const currentStackApp = find(this.installedApps, { manifest: { uid: app?.manifest?.uid } }); + + if (!currentStackApp) { + log.debug( + `App not found in current stack, installing new app: ${app.manifest?.name}`, + this.importConfig.context, + ); + if (app.manifest.visibility === 'private' && !this.importConfig.canCreatePrivateApp) { + this.progressManager?.tick( + true, + `${app.manifest.name} (skipped - private app not allowed)`, + null, + PROCESS_NAMES.INSTALL_APPS, + ); + log.info(`Skipping the installation of the private app ${app.manifest.name}...`, this.importConfig.context); + return Promise.resolve(); + } + + log.debug( + `Installing app with manifest UID: ${this.appUidMapping[app.manifest.uid] || app.manifest.uid}`, + this.importConfig.context, + ); + const installation = await this.installApp( + this.importConfig, + // NOTE if it's private app it should get uid from mapper else will use manifest uid + this.appUidMapping[app.manifest.uid] || app.manifest.uid, + ); + + if (installation.installation_uid) { + const appName = this.appNameMapping[app.manifest.name] || app.manifest.name || app.manifest.uid; + this.progressManager?.tick(true, `${appName}`, null, PROCESS_NAMES.INSTALL_APPS); + log.success(`${appName} app installed successfully.!`, this.importConfig.context); + log.debug(`Installation UID: ${installation.installation_uid}`, this.importConfig.context); + + log.debug(`Making redirect URL call for app: ${appName}`, this.importConfig.context); + await makeRedirectUrlCall(installation, appName, this.importConfig); + + this.installationUidMapping[app.uid] = installation.installation_uid; + log.debug( + `Installation UID mapping: ${app.uid} → ${installation.installation_uid}`, + this.importConfig.context, + ); + updateParam = { manifest: app.manifest, ...installation, configuration, server_configuration }; + } else if (installation.message) { + this.progressManager?.tick(false, `${app.manifest?.name}`, installation.message, PROCESS_NAMES.INSTALL_APPS); + log.info(formatError(installation.message), this.importConfig.context); + log.debug(`Installation failed for app: ${app.manifest?.name}`, this.importConfig.context); + cliux.print('\n'); + await confirmToCloseProcess(installation, this.importConfig); + } + } else if (!isEmpty(configuration) || !isEmpty(server_configuration)) { + const appName = app.manifest.name || app.manifest.uid; this.progressManager?.tick( true, - `${app.manifest.name} (skipped - private app not allowed)`, + `${appName} (already installed, updating config)`, null, - 'Install', + PROCESS_NAMES.INSTALL_APPS, + ); + log.info(`${appName} is already installed`, this.importConfig.context); + log.debug(`Handling existing app configuration for: ${appName}`, this.importConfig.context); + updateParam = await ifAppAlreadyExist(app, currentStackApp, this.importConfig); + } else { + this.progressManager?.tick(true, `${app.manifest?.name} (already installed)`, null, PROCESS_NAMES.INSTALL_APPS); + log.debug( + `App ${app.manifest?.name} is already installed with no configuration to update`, + this.importConfig.context, ); - log.info(`Skipping the installation of the private app ${app.manifest.name}...`, this.importConfig.context); - return Promise.resolve(); } - log.debug( - `Installing app with manifest UID: ${this.appUidMapping[app.manifest.uid] || app.manifest.uid}`, - this.importConfig.context, - ); - const installation = await this.installApp( - this.importConfig, - // NOTE if it's private app it should get uid from mapper else will use manifest uid - this.appUidMapping[app.manifest.uid] || app.manifest.uid, - ); + if (!this.appUidMapping[app.manifest.uid]) { + this.appUidMapping[app.manifest.uid] = currentStackApp ? currentStackApp.manifest.uid : app.manifest.uid; + log.debug( + `App UID mapping: ${app.manifest.uid} → ${this.appUidMapping[app.manifest.uid]}`, + this.importConfig.context, + ); + } - if (installation.installation_uid) { - const appName = this.appNameMapping[app.manifest.name] || app.manifest.name || app.manifest.uid; - this.progressManager?.tick(true, `${appName}`, null, 'Install'); - log.success(`${appName} app installed successfully.!`, this.importConfig.context); - log.debug(`Installation UID: ${installation.installation_uid}`, this.importConfig.context); - - log.debug(`Making redirect URL call for app: ${appName}`, this.importConfig.context); - await makeRedirectUrlCall(installation, appName, this.importConfig); - - this.installationUidMapping[app.uid] = installation.installation_uid; - log.debug(`Installation UID mapping: ${app.uid} → ${installation.installation_uid}`, this.importConfig.context); - updateParam = { manifest: app.manifest, ...installation, configuration, server_configuration }; - } else if (installation.message) { - this.progressManager?.tick(false, `${app.manifest?.name}`, installation.message, 'Install'); - log.info(formatError(installation.message), this.importConfig.context); - log.debug(`Installation failed for app: ${app.manifest?.name}`, this.importConfig.context); - await confirmToCloseProcess(installation, this.importConfig); + // NOTE update configurations + if (updateParam && (!isEmpty(updateParam.configuration) || !isEmpty(updateParam.server_configuration))) { + log.debug(`Updating app configuration for: ${app.manifest?.name}`, this.importConfig.context); + await this.updateAppsConfig(updateParam); + } else { + log.debug(`No configuration update needed for: ${app.manifest?.name}`, this.importConfig.context); } - } else if (!isEmpty(configuration) || !isEmpty(server_configuration)) { - const appName = app.manifest.name || app.manifest.uid; + } catch (error) { this.progressManager?.tick( - true, - `${appName} (already installed, updating config)`, - null, - 'Install', - ); - log.info(`${appName} is already installed`, this.importConfig.context); - log.debug(`Handling existing app configuration for: ${appName}`, this.importConfig.context); - updateParam = await ifAppAlreadyExist(app, currentStackApp, this.importConfig); - } else { - this.progressManager?.tick(true, `${app.manifest?.name} (already installed)`, null, 'Install'); - log.debug( - `App ${app.manifest?.name} is already installed with no configuration to update`, - this.importConfig.context, - ); - } - - if (!this.appUidMapping[app.manifest.uid]) { - this.appUidMapping[app.manifest.uid] = currentStackApp ? currentStackApp.manifest.uid : app.manifest.uid; - log.debug( - `App UID mapping: ${app.manifest.uid} → ${this.appUidMapping[app.manifest.uid]}`, - this.importConfig.context, + false, + `${app.manifest?.name}`, + error?.message || 'Failed to install apps', + PROCESS_NAMES.INSTALL_APPS, ); - } - - // NOTE update configurations - if (updateParam && (!isEmpty(updateParam.configuration) || !isEmpty(updateParam.server_configuration))) { - log.debug(`Updating app configuration for: ${app.manifest?.name}`, this.importConfig.context); - await this.updateAppsConfig(updateParam); - } else { - log.debug(`No configuration update needed for: ${app.manifest?.name}`, this.importConfig.context); + throw error; } } @@ -750,10 +764,10 @@ export default class ImportMarketplaceApps extends BaseClass { }); } - private async prepareMarketplaceAppMapper(): Promise { + private prepareMarketplaceAppMapper() { log.debug('Creating marketplace apps mapper directory', this.importConfig.context); fsUtil.makeDirectory(this.mapperDirPath); - log.debug('Created marketplace apps mapper directory', this.importConfig.context); + log.debug(`Created marketplace apps mapper directory, ${this.mapperDirPath}`, this.importConfig.context); } private async setupMarketplaceEnvironment(): Promise { diff --git a/packages/contentstack-import/src/import/modules/personalize.ts b/packages/contentstack-import/src/import/modules/personalize.ts index 3e4444b9fe..9a54c47c21 100644 --- a/packages/contentstack-import/src/import/modules/personalize.ts +++ b/packages/contentstack-import/src/import/modules/personalize.ts @@ -2,23 +2,24 @@ import { Import } from '@contentstack/cli-variants'; import { log, handleAndLogError } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; import { ImportConfig, ModuleClassParams } from '../../types'; +import { PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; export default class ImportPersonalize extends BaseClass { private config: ImportConfig; public personalizeConfig: ImportConfig['modules']['personalize']; private readonly moduleDisplayMapper = { - events: 'Events', - attributes: 'Attributes', - audiences: 'Audiences', - experiences: 'Experiences', + events: 'Events', + attributes: 'Attributes' , + audiences: 'Audiences', + experiences: 'Experiences', }; constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); this.config = importConfig; - this.config.context.module = 'personalize'; - this.currentModuleName = 'Personalize'; + this.config.context.module = MODULE_CONTEXTS.PERSONALIZE; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.PERSONALIZE]; this.personalizeConfig = importConfig.modules.personalize; } @@ -65,8 +66,8 @@ export default class ImportPersonalize extends BaseClass { } private addProjectProcess(progress: any) { - progress.addProcess('Projects', 1); - log.debug('Added Projects process to personalize progress', this.config.context); + progress.addProcess(PROCESS_NAMES.PERSONALIZE_PROJECTS, 1); + log.debug(`Added ${PROCESS_NAMES.PERSONALIZE_PROJECTS} process to personalize progress`, this.config.context); } private addModuleProcesses(progress: any, moduleCount: number) { @@ -87,14 +88,14 @@ export default class ImportPersonalize extends BaseClass { } private async importProjects(progress: any): Promise { - progress.startProcess('Projects').updateStatus('Importing personalization projects...', 'Projects'); + progress.updateStatus(PROCESS_STATUS[PROCESS_NAMES.PERSONALIZE_PROJECTS].IMPORTING); log.debug('Starting projects import for personalization...', this.config.context); const projectInstance = new Import.Project(this.config); projectInstance.setParentProgressManager(progress); await projectInstance.import(); - progress.completeProcess('Projects', true); + progress.completeProcess(PROCESS_NAMES.PERSONALIZE_PROJECTS, true); } private async importModules(progress: any): Promise { @@ -119,12 +120,18 @@ export default class ImportPersonalize extends BaseClass { log.debug(`Starting import for module: ${module}`, this.config.context); if (this.personalizeConfig.importData) { - const importer = new ModuleClass(this.config); - importer.setParentProgressManager(progress); - await importer.import(); - - progress.completeProcess(processName, true); - log.debug(`Completed import for module: ${module}`, this.config.context); + try { + const importer = new ModuleClass(this.config); + importer.setParentProgressManager(progress); + await importer.import(); + + progress.completeProcess(processName, true); + log.debug(`Completed import for module: ${module}`, this.config.context); + } catch (error) { + progress.completeProcess(processName, false); + log.debug(`Failed to import module: ${module} - ${(error as any)?.message}`, this.config.context); + handleAndLogError(error, { ...this.config.context, module }); + } } else { log.debug(`Skipping ${module} - personalization not enabled`, this.config.context); this.progressManager?.tick(true, `${module} skipped (no project)`, null, processName); diff --git a/packages/contentstack-import/src/import/modules/stack.ts b/packages/contentstack-import/src/import/modules/stack.ts index 26010dbba9..0ac9e58bc7 100644 --- a/packages/contentstack-import/src/import/modules/stack.ts +++ b/packages/contentstack-import/src/import/modules/stack.ts @@ -1,8 +1,9 @@ import { join } from 'node:path'; -import { fileHelper, fsUtil } from '../../utils'; +import { log, handleAndLogError } from '@contentstack/cli-utilities'; + import BaseClass from './base-class'; +import { fileHelper, fsUtil, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; import { ModuleClassParams } from '../../types'; -import { log, handleAndLogError, messageHandler } from '@contentstack/cli-utilities'; export default class ImportStack extends BaseClass { private stackSettingsPath: string; @@ -12,8 +13,8 @@ export default class ImportStack extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'stack'; - this.currentModuleName = 'Stack'; + this.importConfig.context.module = MODULE_CONTEXTS.STACK; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.STACK]; this.stackSettingsPath = join(this.importConfig.backupDir, 'stack', 'settings.json'); this.envUidMapperPath = join(this.importConfig.backupDir, 'mapper', 'environments', 'uid-mapping.json'); } @@ -43,7 +44,7 @@ export default class ImportStack extends BaseClass { const progress = this.createSimpleProgress(this.currentModuleName, 1); - progress.updateStatus('Importing stack settings...'); + progress.updateStatus(PROCESS_STATUS[PROCESS_NAMES.STACK_IMPORT].IMPORTING); log.info('Starting stack settings import process', this.importConfig.context); await this.importStackSettings(); @@ -74,7 +75,7 @@ export default class ImportStack extends BaseClass { log.debug('Applying stack settings to target stack', this.importConfig.context); await this.stack.addSettings(this.stackSettings); - this.progressManager?.tick(true, 'stack settings applied'); + this.progressManager?.tick(true, 'stack settings applied', null, PROCESS_NAMES.STACK_IMPORT); log.debug('Stack settings applied successfully', this.importConfig.context); } diff --git a/packages/contentstack-import/src/import/modules/taxonomies.ts b/packages/contentstack-import/src/import/modules/taxonomies.ts index ab9410015a..1516d6f115 100644 --- a/packages/contentstack-import/src/import/modules/taxonomies.ts +++ b/packages/contentstack-import/src/import/modules/taxonomies.ts @@ -4,7 +4,7 @@ import isEmpty from 'lodash/isEmpty'; import { log, handleAndLogError } from '@contentstack/cli-utilities'; import BaseClass, { ApiOptions } from './base-class'; -import { fsUtil, fileHelper } from '../../utils'; +import { fsUtil, fileHelper, MODULE_CONTEXTS, MODULE_NAMES, PROCESS_STATUS, PROCESS_NAMES } from '../../utils'; import { ModuleClassParams, TaxonomiesConfig } from '../../types'; export default class ImportTaxonomies extends BaseClass { @@ -24,8 +24,8 @@ export default class ImportTaxonomies extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'taxonomies'; - this.currentModuleName = 'Taxonomies'; + this.importConfig.context.module = MODULE_CONTEXTS.TAXONOMIES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.TAXONOMIES]; this.taxonomiesConfig = importConfig.modules.taxonomies; this.taxonomiesMapperDirPath = join(importConfig.backupDir, 'mapper', 'taxonomies'); this.termsMapperDirPath = join(this.taxonomiesMapperDirPath, 'terms'); @@ -52,7 +52,7 @@ export default class ImportTaxonomies extends BaseClass { const progress = this.createSimpleProgress(this.currentModuleName, taxonomiesCount); await this.prepareMapperDirectories(); - progress.updateStatus('Importing taxonomies...'); + progress.updateStatus(PROCESS_STATUS[PROCESS_NAMES.TAXONOMIES_IMPORT].IMPORTING); log.debug('Starting taxonomies import', this.importConfig.context); await this.importTaxonomies(); this.createSuccessAndFailedFile(); @@ -89,7 +89,12 @@ export default class ImportTaxonomies extends BaseClass { this.createdTaxonomies[taxonomyUID] = apiData?.taxonomy; this.createdTerms[taxonomyUID] = apiData?.terms; - this.progressManager?.tick(true, `taxonomy: ${taxonomyName || taxonomyUID} (${termsCount} terms)`); + this.progressManager?.tick( + true, + null, + `taxonomy: ${taxonomyName || taxonomyUID} (${termsCount} terms)`, + PROCESS_NAMES.TAXONOMIES_IMPORT, + ); log.success(`Taxonomy '${taxonomyUID}' imported successfully!`, this.importConfig.context); log.debug( `Taxonomy '${taxonomyName}' imported with ${termsCount} terms successfully!`, @@ -105,7 +110,12 @@ export default class ImportTaxonomies extends BaseClass { log.debug(`Adding existing taxonomy '${taxonomyUID}' to created list`, this.importConfig.context); this.createdTaxonomies[taxonomyUID] = apiData?.taxonomy; this.createdTerms[taxonomyUID] = apiData?.terms; - this.progressManager?.tick(true, `taxonomy: ${taxonomyName || taxonomyUID}`); + this.progressManager?.tick( + true, + null, + `taxonomy: ${taxonomyName || taxonomyUID} already exists`, + PROCESS_NAMES.TAXONOMIES_IMPORT, + ); } else { this.failedTaxonomies[taxonomyUID] = apiData?.taxonomy; this.failedTerms[taxonomyUID] = apiData?.terms; @@ -114,6 +124,7 @@ export default class ImportTaxonomies extends BaseClass { false, `taxonomy: ${taxonomyName || taxonomyUID}`, error?.message || 'Failed to import taxonomy', + PROCESS_NAMES.TAXONOMIES_IMPORT, ); handleAndLogError( error, diff --git a/packages/contentstack-import/src/import/modules/variant-entries.ts b/packages/contentstack-import/src/import/modules/variant-entries.ts index 03047f6537..9bbf140c0a 100644 --- a/packages/contentstack-import/src/import/modules/variant-entries.ts +++ b/packages/contentstack-import/src/import/modules/variant-entries.ts @@ -1,6 +1,6 @@ import path from 'path'; -import { Import, ImportHelperMethodsConfig, ProjectStruct } from '@contentstack/cli-variants'; import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; +import { Import, ImportHelperMethodsConfig, ProjectStruct } from '@contentstack/cli-variants'; import { ImportConfig, ModuleClassParams } from '../../types'; import { lookUpTerms, @@ -10,6 +10,10 @@ import { restoreJsonRteEntryRefs, fsUtil, fileHelper, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, } from '../../utils'; import BaseClass from './base-class'; @@ -21,8 +25,8 @@ export default class ImportVariantEntries extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); this.config = importConfig; - this.config.context.module = 'variant-entries'; - this.currentModuleName = 'Variant Entries'; + this.config.context.module = MODULE_CONTEXTS.VARIANT_ENTRIES; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.VARIANT_ENTRIES]; this.personalize = importConfig.modules.personalize; this.projectMapperFilePath = path.resolve( sanitizePath(this.config.data), @@ -50,7 +54,7 @@ export default class ImportVariantEntries extends BaseClass { const progress = this.createSimpleProgress(this.currentModuleName); - progress.updateStatus('Importing variant entries...'); + progress.updateStatus(PROCESS_STATUS[PROCESS_NAMES.VARIANT_ENTRIES_IMPORT].IMPORTING); log.info('Starting variant entries import process', this.config.context); await this.importVariantEntries(); @@ -86,38 +90,60 @@ export default class ImportVariantEntries extends BaseClass { log.debug('Creating VariantEntries instance', this.config.context); const variantEntriesImporter = new Import.VariantEntries(Object.assign(this.config, { helpers })); + variantEntriesImporter.setParentProgressManager(this.progressManager); + log.debug('Starting variant entries import', this.config.context); await variantEntriesImporter.import(); - this.progressManager?.tick(true, 'variant entries import completed'); + // this.progressManager?.tick(true, 'variant entries import completed', null, PROCESS_NAMES.VARIANT_ENTRIES_IMPORT); log.debug('Variant entries import completed successfully', this.config.context); } else { log.debug('No valid project found in mapper file', this.config.context); - this.progressManager?.tick(false, 'variant entries import', 'No personalize project linked'); + this.progressManager?.tick( + false, + 'variant entries import', + 'No personalize project linked', + PROCESS_NAMES.VARIANT_ENTRIES_IMPORT, + ); log.info('Skipping entry variants import because no personalize project is linked.', this.config.context); } } - private async analyzeVariantEntries(): Promise<[boolean]> { + private async analyzeVariantEntries(): Promise<[boolean, number]> { return this.withLoadingSpinner('VARIANT ENTRIES: Analyzing import data...', async () => { log.debug(`Reading project mapper from: ${this.projectMapperFilePath}`, this.config.context); if (!fileHelper.fileExistsSync(this.projectMapperFilePath)) { log.debug('Project mapper file does not exist', this.config.context); log.info('Skipping entry variants import because no personalize project mapper found.', this.config.context); - return [false] as [boolean]; + return [false, 0] as [boolean, number]; } const project = fsUtil.readFile(this.projectMapperFilePath) as ProjectStruct; - const hasValidProject = !!(project && project.uid); // Convert to boolean + const hasValidProject = !!(project && project.uid); - if (hasValidProject) { - log.debug(`Found valid personalize project: ${project.uid}`, this.config.context); - } else { + if (!hasValidProject) { log.debug('No valid project found in mapper file', this.config.context); + return [false, 0] as [boolean, number]; } - return [hasValidProject] as [boolean]; + // Basic validation - check if data file exists + const dataFilePath = path.resolve( + sanitizePath(this.config.data), + 'mapper', + 'entries', + 'data-for-variant-entry.json', + ); + + const hasVariantData = fileHelper.fileExistsSync(dataFilePath); + + log.debug( + `Found valid personalize project: ${project.uid} with variant data: ${hasVariantData}`, + this.config.context, + ); + + // Return 0 count - let the variant module update it dynamically + return [hasValidProject && hasVariantData, 0] as [boolean, number]; }); } } diff --git a/packages/contentstack-import/src/import/modules/webhooks.ts b/packages/contentstack-import/src/import/modules/webhooks.ts index 6426fff4de..54d0621f01 100644 --- a/packages/contentstack-import/src/import/modules/webhooks.ts +++ b/packages/contentstack-import/src/import/modules/webhooks.ts @@ -3,7 +3,7 @@ import values from 'lodash/values'; import { join } from 'node:path'; import { log, handleAndLogError } from '@contentstack/cli-utilities'; -import { fsUtil, fileHelper } from '../../utils'; +import { fsUtil, fileHelper, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; import BaseClass, { ApiOptions } from './base-class'; import { ModuleClassParams, WebhookConfig } from '../../types'; @@ -21,8 +21,8 @@ export default class ImportWebhooks extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'webhooks'; - this.currentModuleName = 'Webhooks'; + this.importConfig.context.module = MODULE_CONTEXTS.WEBHOOKS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.WEBHOOKS]; this.webhooksConfig = importConfig.modules.webhooks; this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'webhooks'); this.webhooksFolderPath = join(this.importConfig.backupDir, this.webhooksConfig.dirName); @@ -53,7 +53,7 @@ export default class ImportWebhooks extends BaseClass { const progress = this.createSimpleProgress(this.currentModuleName, webhooksCount); await this.prepareWebhookMapper(); - progress.updateStatus('Importing webhooks...'); + progress.updateStatus(PROCESS_STATUS[PROCESS_NAMES.WEBHOOKS_IMPORT].IMPORTING); await this.importWebhooks(); this.processWebhookResults(); @@ -79,7 +79,7 @@ export default class ImportWebhooks extends BaseClass { const onSuccess = ({ response, apiData: { uid, name } = { uid: null, name: '' } }: any) => { this.createdWebhooks.push(response); this.webhookUidMapper[uid] = response.uid; - this.progressManager?.tick(true, `webhook: ${name || uid}`); + this.progressManager?.tick(true, `webhook: ${name || uid}`, null, PROCESS_NAMES.WEBHOOKS_IMPORT); log.success(`Webhook '${name}' imported successfully`, this.importConfig.context); log.debug(`Webhook UID mapping: ${uid} → ${response.uid}`, this.importConfig.context); fsUtil.writeFile(this.webhookUidMapperPath, this.webhookUidMapper); @@ -91,11 +91,21 @@ export default class ImportWebhooks extends BaseClass { log.debug(`Webhook '${name}' (${uid}) failed to import`, this.importConfig.context); if (err?.errors?.name) { - this.progressManager?.tick(true, `webhook: ${name || uid} (already exists)`); + this.progressManager?.tick( + true, + `webhook: ${name || uid} (already exists)`, + null, + PROCESS_NAMES.WEBHOOKS_IMPORT, + ); log.info(`Webhook '${name}' already exists`, this.importConfig.context); } else { this.failedWebhooks.push(apiData); - this.progressManager?.tick(false, `webhook: ${name || uid}`, error?.message || 'Failed to import webhook'); + this.progressManager?.tick( + false, + `webhook: ${name || uid}`, + error?.message || PROCESS_STATUS[PROCESS_NAMES.WEBHOOKS_IMPORT].FAILED, + PROCESS_NAMES.WEBHOOKS_IMPORT, + ); handleAndLogError( error, { ...this.importConfig.context, webhookName: name }, @@ -137,7 +147,12 @@ export default class ImportWebhooks extends BaseClass { if (this.webhookUidMapper.hasOwnProperty(webhook.uid)) { log.info(`Webhook '${webhook.name}' already exists. Skipping it to avoid duplicates!`, this.importConfig.context); log.debug(`Skipping webhook serialization for: ${webhook.uid}`, this.importConfig.context); - this.progressManager?.tick(true, `webhook: ${webhook.name} (skipped - already exists)`); + this.progressManager?.tick( + true, + `webhook: ${webhook.name} (skipped - already exists)`, + null, + PROCESS_NAMES.WEBHOOKS_IMPORT, + ); apiOptions.entity = undefined; } else { log.debug(`Processing webhook status configuration`, this.importConfig.context); diff --git a/packages/contentstack-import/src/import/modules/workflows.ts b/packages/contentstack-import/src/import/modules/workflows.ts index b0059026bf..ee2d372209 100644 --- a/packages/contentstack-import/src/import/modules/workflows.ts +++ b/packages/contentstack-import/src/import/modules/workflows.ts @@ -7,10 +7,17 @@ import filter from 'lodash/filter'; import isEmpty from 'lodash/isEmpty'; import cloneDeep from 'lodash/cloneDeep'; import findIndex from 'lodash/findIndex'; +import { log, handleAndLogError } from '@contentstack/cli-utilities'; import BaseClass, { ApiOptions } from './base-class'; -import { fsUtil, fileHelper } from '../../utils'; -import { log, handleAndLogError } from '@contentstack/cli-utilities'; +import { + fsUtil, + fileHelper, + PROCESS_NAMES, + MODULE_CONTEXTS, + PROCESS_STATUS, + MODULE_NAMES, +} from '../../utils'; import { ModuleClassParams, WorkflowConfig } from '../../types'; export default class ImportWorkflows extends BaseClass { @@ -28,8 +35,8 @@ export default class ImportWorkflows extends BaseClass { constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); - this.importConfig.context.module = 'workflows'; - this.currentModuleName = 'Workflows'; + this.importConfig.context.module = MODULE_CONTEXTS.WORKFLOWS; + this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.WORKFLOWS]; this.workflowsConfig = importConfig.modules.workflows; this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'workflows'); this.workflowsFolderPath = join(this.importConfig.backupDir, this.workflowsConfig.dirName); @@ -59,22 +66,29 @@ export default class ImportWorkflows extends BaseClass { } const progress = this.createNestedProgress(this.currentModuleName); - progress.addProcess('Get Roles', 1); - progress.addProcess('Create', workflowsCount); + progress.addProcess(PROCESS_NAMES.GET_ROLES, 1); + progress.addProcess(PROCESS_NAMES.WORKFLOWS_CREATE, workflowsCount); await this.prepareWorkflowMapper(); // Step 1: Fetch and setup roles - progress.startProcess('Get Roles').updateStatus('Fetching roles for workflow processing...', 'Get Roles'); + progress + .startProcess(PROCESS_NAMES.GET_ROLES) + .updateStatus(PROCESS_STATUS[PROCESS_NAMES.GET_ROLES].FETCHING, PROCESS_NAMES.GET_ROLES); log.info('Fetching all roles for workflow processing', this.importConfig.context); await this.getRoles(); - progress.completeProcess('Get Roles', true); + progress.completeProcess(PROCESS_NAMES.GET_ROLES, true); // Step 2: Import workflows - progress.startProcess('Create').updateStatus('Importing workflows...', 'Create'); + progress + .startProcess(PROCESS_NAMES.WORKFLOWS_CREATE) + .updateStatus( + PROCESS_STATUS[PROCESS_NAMES.WORKFLOWS_CREATE].IMPORTING, + PROCESS_NAMES.WORKFLOWS_CREATE, + ); log.info('Starting workflows import process', this.importConfig.context); await this.importWorkflows(); - progress.completeProcess('Create', true); + progress.completeProcess(PROCESS_NAMES.WORKFLOWS_CREATE, true); this.processWorkflowResults(); @@ -144,7 +158,7 @@ export default class ImportWorkflows extends BaseClass { false, `workflow: ${name || uid}`, error?.message || 'Failed to update next available stages', - 'Create', + PROCESS_NAMES.WORKFLOWS_CREATE, ); handleAndLogError(error, { ...this.importConfig.context, name }, `Workflow '${name}' update failed`); }); @@ -157,7 +171,7 @@ export default class ImportWorkflows extends BaseClass { this.createdWorkflows.push(response); this.workflowUidMapper[uid] = response.uid; - this.progressManager?.tick(true, `workflow: ${name || uid}`, null, 'Create'); + this.progressManager?.tick(true, `workflow: ${name || uid}`, null, PROCESS_NAMES.WORKFLOWS_CREATE); log.success(`Workflow '${name}' imported successfully`, this.importConfig.context); log.debug(`Workflow UID mapping: ${uid} → ${response.uid}`, this.importConfig.context); fsUtil.writeFile(this.workflowUidMapperPath, this.workflowUidMapper); @@ -170,7 +184,12 @@ export default class ImportWorkflows extends BaseClass { const workflowExists = err?.errors?.name || err?.errors?.['workflow.name']; if (workflowExists) { - this.progressManager?.tick(true, `workflow: ${name || uid} (already exists)`, null, 'Create'); + this.progressManager?.tick( + true, + `workflow: ${name || uid} (already exists)`, + null, + PROCESS_NAMES.WORKFLOWS_CREATE, + ); log.info(`Workflow '${name}' already exists`, this.importConfig.context); } else { this.failedWebhooks.push(apiData); @@ -178,7 +197,7 @@ export default class ImportWorkflows extends BaseClass { false, `workflow: ${name || uid}`, error?.message || 'Failed to import workflow', - 'Create', + PROCESS_NAMES.WORKFLOWS_CREATE, ); if (error.errors['workflow_stages.0.users']) { log.error( @@ -260,7 +279,7 @@ export default class ImportWorkflows extends BaseClass { true, `workflow: ${workflow.name} (skipped - already exists)`, null, - 'Create', + PROCESS_NAMES.WORKFLOWS_CREATE, ); apiOptions.entity = undefined; } else { diff --git a/packages/contentstack-import/src/utils/constants.ts b/packages/contentstack-import/src/utils/constants.ts new file mode 100644 index 0000000000..c751bcfc8f --- /dev/null +++ b/packages/contentstack-import/src/utils/constants.ts @@ -0,0 +1,278 @@ +export const PROCESS_NAMES = { + // Assets module + ASSET_FOLDERS: 'Folders', + ASSET_VERSIONS: 'Versions', + ASSET_UPLOAD: 'Upload', + ASSET_PUBLISH: 'Publish', + + // Content Types module + CONTENT_TYPES_CREATE: 'Content Types Create', + CONTENT_TYPES_UPDATE: 'Content Types Update', + CONTENT_TYPES_REPLACE_EXISTING: 'Content Types Replace Existing', + + // Entries module + CT_PREPARATION: 'CT Preparation', + ENTRIES_CREATE: 'Entries Create', + ENTRIES_REPLACE_EXISTING: 'Entries Replace Existing', + REFERENCE_UPDATES: 'Reference Updates', + CT_RESTORATION: 'CT Restoration', + FIELD_RULES_UPDATE: 'Field Rules Update', + ENTRIES_PUBLISH: 'Entries Publish', + CLEANUP: 'Cleanup', + + // Extensions module + EXTENSIONS_CREATE: 'Extensions Create', + EXTENSIONS_REPLACE_EXISTING: 'Extensions Replace Existing', + + // Global Fields module + GLOBAL_FIELDS_CREATE: 'Global Fields Create', + GLOBAL_FIELDS_UPDATE: 'Global Fields Update', + GLOBAL_FIELDS_REPLACE_EXISTING: 'Global Fields Replace Existing', + + // Labels module + LABELS_CREATE: 'Labels Create', + LABELS_UPDATE: 'Labels Update', + + // Locales module + MASTER_LOCALE: 'Master Locale', + LOCALES_CREATE: 'Locales Create', + LOCALES_UPDATE: 'Locales Update', + + // Marketplace Apps module + SETUP_ENVIRONMENT: 'Setup Environment', + CREATE_APPS: 'Create Apps', + INSTALL_APPS: 'Install Apps', + + // Workflows module + GET_ROLES: 'Get Roles', + WORKFLOWS_CREATE: 'Workflows Create', + + // Additional processes for import modules + VARIANT_ENTRIES_IMPORT: 'Variant Entries', + ENVIRONMENTS_IMPORT: 'Environments Import', + CUSTOM_ROLES_BUILD_MAPPINGS: 'Custom Roles Build Mappings', + CUSTOM_ROLES_IMPORT: 'Custom Roles Import', + STACK_IMPORT: 'Stack Import', + CONTENT_TYPES_GF_UPDATE: 'Content Types GF Update', + CONTENT_TYPES_EXT_UPDATE: 'Content Types Ext Update', + WEBHOOKS_IMPORT: 'Webhooks Import', + TAXONOMIES_IMPORT: 'Taxonomies Import', + PERSONALIZE_PROJECTS: 'Projects', +} as const; + +export const MODULE_CONTEXTS = { + ASSETS: 'assets', + CONTENT_TYPES: 'content-types', + CUSTOM_ROLES: 'custom-roles', + ENTRIES: 'entries', + ENVIRONMENTS: 'environments', + EXTENSIONS: 'extensions', + GLOBAL_FIELDS: 'global-fields', + LABELS: 'labels', + LOCALES: 'locales', + MARKETPLACE_APPS: 'marketplace-apps', + PERSONALIZE: 'personalize', + STACK: 'stack', + TAXONOMIES: 'taxonomies', + VARIANT_ENTRIES: 'variant-entries', + WEBHOOKS: 'webhooks', + WORKFLOWS: 'workflows', +} as const; + +// Display names for modules to avoid scattering user-facing strings +export const MODULE_NAMES = { + [MODULE_CONTEXTS.ASSETS]: 'Assets', + [MODULE_CONTEXTS.CONTENT_TYPES]: 'Content Types', + [MODULE_CONTEXTS.CUSTOM_ROLES]: 'Custom Roles', + [MODULE_CONTEXTS.ENTRIES]: 'Entries', + [MODULE_CONTEXTS.ENVIRONMENTS]: 'Environments', + [MODULE_CONTEXTS.EXTENSIONS]: 'Extensions', + [MODULE_CONTEXTS.GLOBAL_FIELDS]: 'Global Fields', + [MODULE_CONTEXTS.LABELS]: 'Labels', + [MODULE_CONTEXTS.LOCALES]: 'Locales', + [MODULE_CONTEXTS.MARKETPLACE_APPS]: 'Marketplace Apps', + [MODULE_CONTEXTS.PERSONALIZE]: 'Personalize', + [MODULE_CONTEXTS.STACK]: 'Stack', + [MODULE_CONTEXTS.TAXONOMIES]: 'Taxonomies', + [MODULE_CONTEXTS.VARIANT_ENTRIES]: 'Variant Entries', + [MODULE_CONTEXTS.WEBHOOKS]: 'Webhooks', + [MODULE_CONTEXTS.WORKFLOWS]: 'Workflows', +} as const; + +export const PROCESS_STATUS = { + // Assets + [PROCESS_NAMES.ASSET_FOLDERS]: { + CREATING: 'Creating asset folders...', + FAILED: 'Failed to create asset folders.', + }, + [PROCESS_NAMES.ASSET_VERSIONS]: { + IMPORTING: 'Importing asset versions...', + FAILED: 'Failed to process asset versions.', + }, + [PROCESS_NAMES.ASSET_UPLOAD]: { + UPLOADING: 'Uploading asset files...', + FAILED: 'Failed to upload assets.', + }, + [PROCESS_NAMES.ASSET_PUBLISH]: { + PUBLISHING: 'Publishing assets...', + FAILED: 'Failed to publish assets.', + }, + // Content Types + [PROCESS_NAMES.CONTENT_TYPES_CREATE]: { + CREATING: 'Creating content types...', + FAILED: 'Failed to create content types.', + }, + [PROCESS_NAMES.CONTENT_TYPES_UPDATE]: { + UPDATING: 'Updating content types with references...', + FAILED: 'Failed to update content types.', + }, + [PROCESS_NAMES.CONTENT_TYPES_REPLACE_EXISTING]: { + REPLACING: 'Replacing existing content types...', + FAILED: 'Failed to replace existing content types.', + }, + // Entries + [PROCESS_NAMES.CT_PREPARATION]: { + PREPARING: 'Preparing content types for entry import...', + FAILED: 'Failed to prepare content types.', + }, + [PROCESS_NAMES.ENTRIES_CREATE]: { + CREATING: 'Creating entries...', + FAILED: 'Failed to create entries.', + }, + [PROCESS_NAMES.ENTRIES_REPLACE_EXISTING]: { + REPLACING: 'Replacing existing entries...', + FAILED: 'Failed to replace existing entries.', + }, + [PROCESS_NAMES.REFERENCE_UPDATES]: { + UPDATING: 'Updating entry references...', + FAILED: 'Failed to update entry references.', + }, + [PROCESS_NAMES.CT_RESTORATION]: { + RESTORING: 'Restoring content type references...', + FAILED: 'Failed to restore content types.', + }, + [PROCESS_NAMES.FIELD_RULES_UPDATE]: { + UPDATING: 'Updating field rules...', + FAILED: 'Failed to update field rules.', + }, + [PROCESS_NAMES.ENTRIES_PUBLISH]: { + PUBLISHING: 'Publishing entries...', + FAILED: 'Failed to publish entries.', + }, + [PROCESS_NAMES.CLEANUP]: { + CLEANING: 'Cleaning up auto-created entries...', + FAILED: 'Failed to clean up temporary data.', + }, + // Extensions + [PROCESS_NAMES.EXTENSIONS_CREATE]: { + CREATING: 'Creating extensions...', + FAILED: 'Failed to create extensions.', + }, + [PROCESS_NAMES.EXTENSIONS_REPLACE_EXISTING]: { + REPLACING: 'Replacing existing extensions...', + FAILED: 'Failed to replace existing extensions.', + }, + // Global Fields + [PROCESS_NAMES.GLOBAL_FIELDS_CREATE]: { + CREATING: 'Creating global fields...', + FAILED: 'Failed to create global fields.', + }, + [PROCESS_NAMES.GLOBAL_FIELDS_UPDATE]: { + UPDATING: 'Updating global fields...', + FAILED: 'Failed to update global fields.', + }, + [PROCESS_NAMES.GLOBAL_FIELDS_REPLACE_EXISTING]: { + REPLACING: 'Replacing existing global fields...', + FAILED: 'Failed to replace existing global fields.', + }, + // Labels + [PROCESS_NAMES.LABELS_CREATE]: { + CREATING: 'Creating labels...', + FAILED: 'Failed to create labels.', + }, + [PROCESS_NAMES.LABELS_UPDATE]: { + UPDATING: 'Updating labels...', + FAILED: 'Failed to update labels.', + }, + // Locales + [PROCESS_NAMES.MASTER_LOCALE]: { + PROCESSING: 'Processing master locale...', + FAILED: 'Failed to process master locale.', + }, + [PROCESS_NAMES.LOCALES_CREATE]: { + CREATING: 'Creating locales...', + FAILED: 'Failed to create locales.', + }, + [PROCESS_NAMES.LOCALES_UPDATE]: { + UPDATING: 'Updating locales...', + FAILED: 'Failed to update locales.', + }, + // Marketplace Apps + [PROCESS_NAMES.SETUP_ENVIRONMENT]: { + SETTING_UP: 'Setting up marketplace SDK and authentication...', + FAILED: 'Failed to setup environment.', + }, + [PROCESS_NAMES.CREATE_APPS]: { + CREATING: 'Creating private apps...', + FAILED: 'Failed to create marketplace apps.', + }, + [PROCESS_NAMES.INSTALL_APPS]: { + INSTALLING: 'Installing marketplace apps...', + FAILED: 'Failed to install marketplace apps.', + }, + // Workflows + [PROCESS_NAMES.GET_ROLES]: { + FETCHING: 'Fetching roles for workflow processing...', + FAILED: 'Failed to fetch workflow roles.', + }, + [PROCESS_NAMES.WORKFLOWS_CREATE]: { + IMPORTING: 'Importing workflows...', + FAILED: 'Failed to create workflows.', + }, + + // Additional import processes + [PROCESS_NAMES.VARIANT_ENTRIES_IMPORT]: { + IMPORTING: 'Importing variant entries...', + FAILED: 'Failed to import variant entries.', + }, + [PROCESS_NAMES.ENVIRONMENTS_IMPORT]: { + IMPORTING: 'Importing environments...', + FAILED: 'Failed to import environments.', + }, + [PROCESS_NAMES.CUSTOM_ROLES_BUILD_MAPPINGS]: { + BUILDING: 'Building locale mappings...', + FAILED: 'Failed to build locale mappings.', + }, + [PROCESS_NAMES.CUSTOM_ROLES_IMPORT]: { + IMPORTING: 'Importing custom roles...', + FAILED: 'Failed to import custom roles.', + }, + [PROCESS_NAMES.STACK_IMPORT]: { + IMPORTING: 'Importing stack settings...', + FAILED: 'Failed to import stack settings.', + }, + [PROCESS_NAMES.CONTENT_TYPES_GF_UPDATE]: { + UPDATING: 'Updating global fields with content type references...', + FAILED: 'Failed to update global fields.', + }, + [PROCESS_NAMES.CONTENT_TYPES_EXT_UPDATE]: { + UPDATING: 'Updating extensions...', + FAILED: 'Failed to update extensions.', + }, + [PROCESS_NAMES.WEBHOOKS_IMPORT]: { + IMPORTING: 'Importing webhooks...', + FAILED: 'Failed to import webhooks.', + }, + [PROCESS_NAMES.TAXONOMIES_IMPORT]: { + IMPORTING: 'Importing taxonomies...', + FAILED: 'Failed to import taxonomies.', + }, + [PROCESS_NAMES.PERSONALIZE_PROJECTS]: { + IMPORTING: 'Importing personalization projects...', + FAILED: 'Failed to import personalization projects.', + }, +}; + +export type ImportProcessName = (typeof PROCESS_NAMES)[keyof typeof PROCESS_NAMES]; +export type ImportModuleContext = (typeof MODULE_CONTEXTS)[keyof typeof MODULE_CONTEXTS]; +export type ImportProcessStatus = (typeof PROCESS_STATUS)[keyof typeof PROCESS_STATUS]; diff --git a/packages/contentstack-import/src/utils/content-type-helper.ts b/packages/contentstack-import/src/utils/content-type-helper.ts index 2ab71bdbf9..8fa43cb81f 100644 --- a/packages/contentstack-import/src/utils/content-type-helper.ts +++ b/packages/contentstack-import/src/utils/content-type-helper.ts @@ -132,8 +132,7 @@ export const removeReferenceFields = async function ( } catch (error) { // Else warn and modify the schema object. isContentTypeError = true; - log.warn(`Content type does not exist: ${schema[i].reference_to[j]}`); - console.warn(`Content-type ${schema[i].reference_to[j]} does not exist. Removing the field from schema`); + log.warn(`Content-type ${schema[i].reference_to[j]} does not exist. Removing the field from schema`); } } diff --git a/packages/contentstack-import/src/utils/index.ts b/packages/contentstack-import/src/utils/index.ts index d173c00020..c8da9a5154 100644 --- a/packages/contentstack-import/src/utils/index.ts +++ b/packages/contentstack-import/src/utils/index.ts @@ -26,3 +26,4 @@ export { export * from './common-helper'; export * from './log'; export { lookUpTaxonomy, lookUpTerms } from './taxonomies-helper'; +export { MODULE_CONTEXTS, MODULE_NAMES, PROCESS_NAMES, PROCESS_STATUS } from './constants'; diff --git a/packages/contentstack-import/src/utils/marketplace-app-helper.ts b/packages/contentstack-import/src/utils/marketplace-app-helper.ts index 0e4454287a..5061722e59 100644 --- a/packages/contentstack-import/src/utils/marketplace-app-helper.ts +++ b/packages/contentstack-import/src/utils/marketplace-app-helper.ts @@ -79,8 +79,7 @@ export const getOrgUid = async (config: ImportConfig): Promise => { .stack({ api_key: config.target_stack }) .fetch() .catch((error: any) => { - handleAndLogError(error); - trace(error, 'error', true); + throw error; }); const orgUid = tempStackData?.org_uid || ''; @@ -122,7 +121,7 @@ export const getConfirmationToCreateApps = async (privateApps: any, config: Impo log.info('User confirmed to create private apps'); return Promise.resolve(true); } else { - log.debug('User declined to create private apps (second prompt)'); + log.warn('User declined to create private apps (second prompt)'); return Promise.resolve(false); } } @@ -131,7 +130,7 @@ export const getConfirmationToCreateApps = async (privateApps: any, config: Impo return Promise.resolve(true); } } else { - log.debug('Force prompt disabled, automatically creating private apps'); + log.info('Force prompt disabled, automatically creating private apps'); return Promise.resolve(true); } }; diff --git a/packages/contentstack-import/src/utils/strategy-registrations.ts b/packages/contentstack-import/src/utils/strategy-registrations.ts index 1811430673..2d12e789b2 100644 --- a/packages/contentstack-import/src/utils/strategy-registrations.ts +++ b/packages/contentstack-import/src/utils/strategy-registrations.ts @@ -4,129 +4,112 @@ * to ensure correct item counts in the final summary. */ -import { - ProgressStrategyRegistry, - PrimaryProcessStrategy, +import { + ProgressStrategyRegistry, + PrimaryProcessStrategy, CustomProgressStrategy, - DefaultProgressStrategy + DefaultProgressStrategy, } from '@contentstack/cli-utilities'; +import { MODULE_CONTEXTS, MODULE_NAMES, PROCESS_NAMES } from './constants'; // Register strategy for Content Types - use Create as primary process ProgressStrategyRegistry.register( - 'CONTENT TYPES', - new PrimaryProcessStrategy('Create') + MODULE_NAMES[MODULE_CONTEXTS.CONTENT_TYPES], + new PrimaryProcessStrategy(PROCESS_NAMES.CONTENT_TYPES_CREATE), ); -// Register strategy for Assets - use Asset Upload as primary process +// Register strategy for Assets - use Asset Upload as primary process ProgressStrategyRegistry.register( - 'ASSETS', - new PrimaryProcessStrategy('Upload') + MODULE_NAMES[MODULE_CONTEXTS.ASSETS], + new PrimaryProcessStrategy(PROCESS_NAMES.ASSET_UPLOAD), ); // Register strategy for Entries - use Entry Creation as primary process ProgressStrategyRegistry.register( - 'ENTRIES', - new PrimaryProcessStrategy('Create') + MODULE_NAMES[MODULE_CONTEXTS.ENTRIES], + new PrimaryProcessStrategy(PROCESS_NAMES.ENTRIES_CREATE), ); // Register strategy for Global Fields - use Create as primary process ProgressStrategyRegistry.register( - 'GLOBAL FIELDS', - new PrimaryProcessStrategy('Create') + MODULE_NAMES[MODULE_CONTEXTS.GLOBAL_FIELDS], + new PrimaryProcessStrategy(PROCESS_NAMES.GLOBAL_FIELDS_CREATE), ); // Register strategy for Extensions - simple module ProgressStrategyRegistry.register( - 'EXTENSIONS', - new PrimaryProcessStrategy('Create') + MODULE_NAMES[MODULE_CONTEXTS.EXTENSIONS], + new PrimaryProcessStrategy(PROCESS_NAMES.EXTENSIONS_CREATE), ); // Register strategy for Environments - uses default (no nested progress yet) -ProgressStrategyRegistry.register( - 'ENVIRONMENTS', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.ENVIRONMENTS], new DefaultProgressStrategy()); // Register strategy for Locales - uses default (no nested progress yet) ProgressStrategyRegistry.register( - 'LOCALES', - new PrimaryProcessStrategy('Create') + MODULE_NAMES[MODULE_CONTEXTS.LOCALES], + new PrimaryProcessStrategy(PROCESS_NAMES.LOCALES_CREATE), ); // Register strategy for Labels - uses default (no nested progress yet) ProgressStrategyRegistry.register( - 'LABELS', - new PrimaryProcessStrategy('Create') + MODULE_NAMES[MODULE_CONTEXTS.LABELS], + new PrimaryProcessStrategy(PROCESS_NAMES.LABELS_CREATE), ); // Register strategy for Webhooks - uses default (no nested progress yet) -ProgressStrategyRegistry.register( - 'WEBHOOKS', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.WEBHOOKS], new DefaultProgressStrategy()); // Register strategy for Workflows - uses default (no nested progress yet) ProgressStrategyRegistry.register( - 'WORKFLOWS', - new PrimaryProcessStrategy('Create') + MODULE_NAMES[MODULE_CONTEXTS.WORKFLOWS], + new PrimaryProcessStrategy(PROCESS_NAMES.WEBHOOKS_IMPORT), ); // Register strategy for Custom Roles - uses default (no nested progress yet) -ProgressStrategyRegistry.register( - 'CUSTOM ROLES', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.CUSTOM_ROLES], new DefaultProgressStrategy()); // Register strategy for Taxonomies - uses default (no nested progress yet) -ProgressStrategyRegistry.register( - 'TAXONOMIES', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.TAXONOMIES], new DefaultProgressStrategy()); // Register strategy for Marketplace Apps - complex module with app installations ProgressStrategyRegistry.register( - 'MARKETPLACE APPS', - new PrimaryProcessStrategy('Apps Installation') + MODULE_NAMES[MODULE_CONTEXTS.MARKETPLACE_APPS], + new PrimaryProcessStrategy(PROCESS_NAMES.CREATE_APPS), ); // Register strategy for Stack Settings - simple module -ProgressStrategyRegistry.register( - 'STACK', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.STACK], new DefaultProgressStrategy()); // Register strategy for Personalize - complex module with projects/experiences ProgressStrategyRegistry.register( - 'PERSONALIZE', + MODULE_NAMES[MODULE_CONTEXTS.PERSONALIZE], new CustomProgressStrategy((processes) => { // For personalize import, count project imports as primary metric - const projectImport = processes.get('Project'); + const projectImport = processes.get(PROCESS_NAMES.PERSONALIZE_PROJECTS); if (projectImport) { return { total: projectImport.total, success: projectImport.successCount, - failures: projectImport.failureCount + failures: projectImport.failureCount, }; } - + // Fallback to any other main process const mainProcess = Array.from(processes.values())[0]; if (mainProcess) { return { total: mainProcess.total, success: mainProcess.successCount, - failures: mainProcess.failureCount + failures: mainProcess.failureCount, }; } - + return null; - }) + }), ); // Register strategy for Variant Entries - sub-process of entries -ProgressStrategyRegistry.register( - 'VARIANT ENTRIES', - new DefaultProgressStrategy() -); +ProgressStrategyRegistry.register(MODULE_NAMES[MODULE_CONTEXTS.VARIANT_ENTRIES], new DefaultProgressStrategy()); -export default ProgressStrategyRegistry; \ No newline at end of file +export default ProgressStrategyRegistry; diff --git a/packages/contentstack-variants/src/export/attributes.ts b/packages/contentstack-variants/src/export/attributes.ts index 50596d376c..5766be1b1e 100644 --- a/packages/contentstack-variants/src/export/attributes.ts +++ b/packages/contentstack-variants/src/export/attributes.ts @@ -3,6 +3,7 @@ import { resolve as pResolve } from 'node:path'; import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; import { PersonalizeConfig, ExportConfig, AttributesConfig, AttributeStruct } from '../types'; import { fsUtil, PersonalizationAdapter } from '../utils'; +import { PROCESS_NAMES, MODULE_CONTEXTS, EXPORT_PROCESS_STATUS } from '../utils/constants'; export default class ExportAttributes extends PersonalizationAdapter { private attributesConfig: AttributesConfig; @@ -27,7 +28,7 @@ export default class ExportAttributes extends PersonalizationAdapter { private audiencesConfig: AudiencesConfig; @@ -27,7 +28,7 @@ export default class ExportAudiences extends PersonalizationAdapter { private eventsConfig: EventsConfig; @@ -27,7 +28,7 @@ export default class ExportEvents extends PersonalizationAdapter { sanitizePath(this.eventsConfig.dirName), ); this.events = []; - this.exportConfig.context.module = 'events'; + this.exportConfig.context.module = MODULE_CONTEXTS.EVENTS; } async start() { @@ -56,23 +57,22 @@ export default class ExportEvents extends PersonalizationAdapter { } let progress: any; - const processName = 'Events'; if (this.parentProgressManager) { progress = this.parentProgressManager; this.progressManager = this.parentProgressManager; - progress.updateProcessTotal(processName, this.events.length); + progress.updateProcessTotal(PROCESS_NAMES.EVENTS, this.events.length); } else { - progress = this.createSimpleProgress('Events', this.events.length); + progress = this.createSimpleProgress(PROCESS_NAMES.EVENTS, this.events.length); } log.debug(`Processing ${this.events.length} events`, this.exportConfig.context); - progress.updateStatus('Sanitizing events data...', processName); + progress.updateStatus(EXPORT_PROCESS_STATUS[PROCESS_NAMES.EVENTS].EXPORTING, PROCESS_NAMES.EVENTS); this.sanitizeAttribs(); log.debug('Events sanitization completed', this.exportConfig.context); - progress.updateStatus('Writing events data...', processName); + progress.updateStatus(EXPORT_PROCESS_STATUS[PROCESS_NAMES.EVENTS].EXPORTING, PROCESS_NAMES.EVENTS); const eventsFilePath = pResolve(sanitizePath(this.eventsFolderPath), sanitizePath(this.eventsConfig.fileName)); log.debug(`Writing events to: ${eventsFilePath}`, this.exportConfig.context); fsUtil.writeFile(eventsFilePath, this.events); @@ -107,7 +107,7 @@ export default class ExportEvents extends PersonalizationAdapter { const sanitizedEvent = omit(event, this.eventsConfig.invalidKeys); if (this.progressManager) { - const processName = this.parentProgressManager ? 'Events' : undefined; + const processName = this.parentProgressManager ? PROCESS_NAMES.EVENTS : undefined; this.updateProgress( true, `event ${index + 1}/${this.events.length}: ${ diff --git a/packages/contentstack-variants/src/export/experiences.ts b/packages/contentstack-variants/src/export/experiences.ts index 82872a44fc..b39d2e1aeb 100644 --- a/packages/contentstack-variants/src/export/experiences.ts +++ b/packages/contentstack-variants/src/export/experiences.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; import { PersonalizeConfig, ExportConfig, ExperienceStruct } from '../types'; import { fsUtil, PersonalizationAdapter } from '../utils'; +import { PROCESS_NAMES, MODULE_CONTEXTS, EXPORT_PROCESS_STATUS } from '../utils/constants'; export default class ExportExperiences extends PersonalizationAdapter { private experiencesFolderPath: string; @@ -26,7 +27,7 @@ export default class ExportExperiences extends PersonalizationAdapter { private projectsFolderPath: string; @@ -21,9 +22,10 @@ export default class ExportProjects extends PersonalizationAdapter sanitizePath(exportConfig.data), sanitizePath(exportConfig.branchName || ''), sanitizePath(this.personalizeConfig.dirName), + 'projects', ); this.projectsData = []; - this.exportConfig.context.module = 'projects'; + this.exportConfig.context.module = MODULE_CONTEXTS.PROJECTS; } async start() { @@ -67,11 +69,13 @@ export default class ExportProjects extends PersonalizationAdapter if (this.parentProgressManager) { progress = this.parentProgressManager; this.progressManager = this.parentProgressManager; - progress.updateProcessTotal('Projects', this.projectsData?.length); + progress.updateProcessTotal(PROCESS_NAMES.PROJECTS, this.projectsData?.length); } else { - progress = this.createNestedProgress('Projects'); - progress.addProcess('Projects', this.projectsData?.length); - progress.startProcess('Projects').updateStatus('Processing and exporting project data...', 'Projects'); + progress = this.createNestedProgress(PROCESS_NAMES.PROJECTS); + progress.addProcess(PROCESS_NAMES.PROJECTS, this.projectsData?.length); + progress + .startProcess(PROCESS_NAMES.PROJECTS) + .updateStatus(EXPORT_PROCESS_STATUS[PROCESS_NAMES.PROJECTS].EXPORTING, PROCESS_NAMES.PROJECTS); } const projectsFilePath = pResolve(sanitizePath(this.projectsFolderPath), 'projects.json'); @@ -79,12 +83,12 @@ export default class ExportProjects extends PersonalizationAdapter fsUtil.writeFile(projectsFilePath, this.projectsData); log.debug('Projects export completed successfully', this.exportConfig.context); - const processName = this.parentProgressManager ? 'Projects' : 'Projects'; + const processName = PROCESS_NAMES.PROJECTS; this.updateProgress(true, 'project export', undefined, processName); // Complete process only if we're managing our own progress if (!this.parentProgressManager) { - progress.completeProcess('Projects', true); + progress.completeProcess(PROCESS_NAMES.PROJECTS, true); this.completeProgress(true); } diff --git a/packages/contentstack-variants/src/export/variant-entries.ts b/packages/contentstack-variants/src/export/variant-entries.ts index 5cad4994e4..5efa1352ed 100644 --- a/packages/contentstack-variants/src/export/variant-entries.ts +++ b/packages/contentstack-variants/src/export/variant-entries.ts @@ -1,6 +1,7 @@ import { existsSync, mkdirSync } from 'fs'; import { join, resolve } from 'path'; import { FsUtility, sanitizePath, log, handleAndLogError, CLIProgressManager } from '@contentstack/cli-utilities'; +import { PROCESS_NAMES, EXPORT_PROCESS_STATUS } from '../utils/constants'; import { APIConfig, AdapterType, ExportConfig } from '../types'; import VariantAdapter, { VariantHttpClient } from '../utils/variant-api-adapter'; @@ -74,6 +75,8 @@ export default class VariantEntries extends VariantAdapter { private mapperDirPath: string; @@ -31,7 +32,7 @@ export default class Attribute extends PersonalizationAdapter { this.attrMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.attributeConfig.dirName)); this.attributesUidMapperPath = resolve(sanitizePath(this.attrMapperDirPath), 'uid-mapping.json'); this.attributesUidMapper = {}; - this.config.context.module = 'attributes'; + this.config.context.module = MODULE_CONTEXTS.ATTRIBUTES; this.attributeData = []; } @@ -47,19 +48,20 @@ export default class Attribute extends PersonalizationAdapter { log.info('No attributes found to import', this.config.context); // Still need to mark as complete for parent progress if (this.parentProgressManager) { - this.parentProgressManager.tick(true, 'attributes module (no data)', null, 'Attributes'); + this.parentProgressManager.tick(true, 'attributes module (no data)', null, PROCESS_NAMES.ATTRIBUTES); } return; } - // If we have a parent progress manager, use it as a sub-module - // Otherwise create our own simple progress manager + // Don't create own progress manager if we have a parent let progress; if (this.parentProgressManager) { progress = this.parentProgressManager; log.debug('Using parent progress manager for attributes import', this.config.context); + this.parentProgressManager.updateProcessTotal(PROCESS_NAMES.ATTRIBUTES, attributesCount); + } else { - progress = this.createSimpleProgress('Attributes', attributesCount); + progress = this.createSimpleProgress(PROCESS_NAMES.ATTRIBUTES, attributesCount); log.debug('Created standalone progress manager for attributes import', this.config.context); } @@ -73,14 +75,14 @@ export default class Attribute extends PersonalizationAdapter { for (const attribute of this.attributeData) { const { key, name, description, uid } = attribute; if (!this.parentProgressManager) { - progress.updateStatus(`Processing attribute: ${name}...`); + progress.updateStatus(IMPORT_PROCESS_STATUS[PROCESS_NAMES.ATTRIBUTES].CREATING); } log.debug(`Processing attribute: ${name} - ${attribute.__type}`, this.config.context); // skip creating preset attributes, as they are already present in the system if (attribute.__type === 'PRESET') { log.debug(`Skipping preset attribute: ${name}`, this.config.context); - this.updateProgress(true, `attribute: ${name} (preset - skipped)`, undefined, 'Attributes'); + this.updateProgress(true, `attribute: ${name} (preset - skipped)`, undefined, PROCESS_NAMES.ATTRIBUTES); continue; } @@ -91,10 +93,10 @@ export default class Attribute extends PersonalizationAdapter { //mapper file is used to check whether attribute created or not before creating audience this.attributesUidMapper[uid] = attributeRes?.uid ?? ''; - this.updateProgress(true, `attribute: ${name}`, undefined, 'Attributes'); + this.updateProgress(true, `attribute: ${name}`, undefined, PROCESS_NAMES.ATTRIBUTES); log.debug(`Created attribute: ${uid} -> ${attributeRes?.uid}`, this.config.context); } catch (error) { - this.updateProgress(false, `attribute: ${name}`, (error as any)?.message, 'Attributes'); + this.updateProgress(false, `attribute: ${name}`, (error as any)?.message, PROCESS_NAMES.ATTRIBUTES); handleAndLogError(error, this.config.context, `Failed to create attribute: ${name}`); } } diff --git a/packages/contentstack-variants/src/import/audiences.ts b/packages/contentstack-variants/src/import/audiences.ts index 6496bc5811..1d16849631 100644 --- a/packages/contentstack-variants/src/import/audiences.ts +++ b/packages/contentstack-variants/src/import/audiences.ts @@ -3,6 +3,7 @@ import { existsSync } from 'fs'; import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; import { APIConfig, AudienceStruct, ImportConfig } from '../types'; import { PersonalizationAdapter, fsUtil, lookUpAttributes } from '../utils'; +import { PROCESS_NAMES, MODULE_CONTEXTS, IMPORT_PROCESS_STATUS } from '../utils/constants'; export default class Audiences extends PersonalizationAdapter { private mapperDirPath: string; @@ -39,7 +40,7 @@ export default class Audiences extends PersonalizationAdapter { 'uid-mapping.json', ); this.audiencesUidMapper = {}; - this.config.context.module = 'audiences'; + this.config.context.module = MODULE_CONTEXTS.AUDIENCES; this.audiences = []; } @@ -55,19 +56,19 @@ export default class Audiences extends PersonalizationAdapter { log.info('No audiences found to import', this.config.context); // Still need to mark as complete for parent progress if (this.parentProgressManager) { - this.parentProgressManager.tick(true, 'audiences module (no data)', null, 'Audiences'); + this.parentProgressManager.tick(true, 'audiences module (no data)', null, PROCESS_NAMES.AUDIENCES); } return; } - // If we have a parent progress manager, use it as a sub-module - // Otherwise create our own simple progress manager + // Don't create own progress manager if we have a parent let progress; if (this.parentProgressManager) { progress = this.parentProgressManager; log.debug('Using parent progress manager for audiences import', this.config.context); + this.parentProgressManager.updateProcessTotal(PROCESS_NAMES.AUDIENCES, audiencesCount); } else { - progress = this.createSimpleProgress('Audiences', audiencesCount); + progress = this.createSimpleProgress(PROCESS_NAMES.AUDIENCES, audiencesCount); log.debug('Created standalone progress manager for audiences import', this.config.context); } @@ -84,7 +85,7 @@ export default class Audiences extends PersonalizationAdapter { for (const audience of this.audiences) { let { name, definition, description, uid } = audience; if (!this.parentProgressManager) { - progress.updateStatus(`Processing audience: ${name}...`); + progress.updateStatus(IMPORT_PROCESS_STATUS[PROCESS_NAMES.AUDIENCES].CREATING); } log.debug(`Processing audience: ${name} (${uid})`, this.config.context); @@ -107,10 +108,10 @@ export default class Audiences extends PersonalizationAdapter { //mapper file is used to check whether audience created or not before creating experience this.audiencesUidMapper[uid] = audienceRes?.uid ?? ''; - this.updateProgress(true, `audience: ${name}`, undefined, 'Audiences'); + this.updateProgress(true, `audience: ${name}`, undefined, PROCESS_NAMES.AUDIENCES); log.debug(`Created audience: ${uid} -> ${audienceRes?.uid}`, this.config.context); } catch (error) { - this.updateProgress(false, `audience: ${name}`, (error as any)?.message, 'Audiences'); + this.updateProgress(false, `audience: ${name}`, (error as any)?.message, PROCESS_NAMES.AUDIENCES); handleAndLogError(error, this.config.context, `Failed to create audience: ${name} (${uid})`); } } @@ -123,10 +124,7 @@ export default class Audiences extends PersonalizationAdapter { this.completeProgress(true); } - log.success( - `Audiences imported successfully! Total audiences: ${audiencesCount}`, - this.config.context, - ); + log.success(`Audiences imported successfully! Total audiences: ${audiencesCount}`, this.config.context); } catch (error) { if (!this.parentProgressManager) { this.completeProgress(false, (error as any)?.message || 'Audiences import failed'); diff --git a/packages/contentstack-variants/src/import/events.ts b/packages/contentstack-variants/src/import/events.ts index 46647e8722..4792fcb6a9 100644 --- a/packages/contentstack-variants/src/import/events.ts +++ b/packages/contentstack-variants/src/import/events.ts @@ -3,6 +3,7 @@ import { existsSync } from 'fs'; import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; import { PersonalizationAdapter, fsUtil } from '../utils'; import { APIConfig, EventStruct, ImportConfig } from '../types'; +import { PROCESS_NAMES, MODULE_CONTEXTS, IMPORT_PROCESS_STATUS } from '../utils/constants'; export default class Events extends PersonalizationAdapter { private mapperDirPath: string; @@ -32,6 +33,7 @@ export default class Events extends PersonalizationAdapter { this.eventsUidMapperPath = resolve(sanitizePath(this.eventMapperDirPath), 'uid-mapping.json'); this.eventsUidMapper = {}; this.events = []; + this.config.context.module = MODULE_CONTEXTS.EVENTS; } /** @@ -46,7 +48,7 @@ export default class Events extends PersonalizationAdapter { log.info('No events found to import', this.config.context); // Still need to mark as complete for parent progress if (this.parentProgressManager) { - this.parentProgressManager.tick(true, 'events module (no data)', null, 'Events'); + this.parentProgressManager.tick(true, 'events module (no data)', null, PROCESS_NAMES.EVENTS); } return; } @@ -56,8 +58,9 @@ export default class Events extends PersonalizationAdapter { if (this.parentProgressManager) { progress = this.parentProgressManager; log.debug('Using parent progress manager for events import', this.config.context); + this.parentProgressManager.updateProcessTotal(PROCESS_NAMES.EVENTS, eventsCount); } else { - progress = this.createSimpleProgress('Events', eventsCount); + progress = this.createSimpleProgress(PROCESS_NAMES.EVENTS, eventsCount); log.debug('Created standalone progress manager for events import', this.config.context); } @@ -70,7 +73,7 @@ export default class Events extends PersonalizationAdapter { for (const event of this.events) { const { key, description, uid } = event; if (!this.parentProgressManager) { - progress.updateStatus(`Processing event: ${key}...`); + progress.updateStatus(IMPORT_PROCESS_STATUS[PROCESS_NAMES.EVENTS].CREATING); } log.debug(`Processing event: ${key} (${uid})`, this.config.context); @@ -83,14 +86,14 @@ export default class Events extends PersonalizationAdapter { if (this.parentProgressManager) { this.updateProgress(true, `event: ${key}`); } else { - this.updateProgress(true, `event: ${key}`, undefined, 'Events'); + this.updateProgress(true, `event: ${key}`, undefined, PROCESS_NAMES.EVENTS); } log.debug(`Created event: ${uid} -> ${eventRes?.uid}`, this.config.context); } catch (error) { if (this.parentProgressManager) { this.updateProgress(false, `event: ${key}`, (error as any)?.message); } else { - this.updateProgress(false, `event: ${key}`, (error as any)?.message, 'Events'); + this.updateProgress(false, `event: ${key}`, (error as any)?.message, PROCESS_NAMES.EVENTS); } handleAndLogError(error, this.config.context, `Failed to create event: ${key} (${uid})`); } diff --git a/packages/contentstack-variants/src/import/experiences.ts b/packages/contentstack-variants/src/import/experiences.ts index daf3475b20..4d383cdb12 100644 --- a/packages/contentstack-variants/src/import/experiences.ts +++ b/packages/contentstack-variants/src/import/experiences.ts @@ -11,6 +11,7 @@ import { CreateExperienceInput, CreateExperienceVersionInput, } from '../types'; +import { PROCESS_NAMES, MODULE_CONTEXTS } from '../utils/constants'; export default class Experiences extends PersonalizationAdapter { private createdCTs: string[]; @@ -43,7 +44,7 @@ export default class Experiences extends PersonalizationAdapter { private experiences: ExperienceStruct[]; constructor(public readonly config: ImportConfig) { - const conf: APIConfig = { + const conf: APIConfig = { config, baseURL: config.modules.personalize.baseURL[config.region.name], headers: { 'X-Project-Uid': config.modules.personalize.project_id }, @@ -53,7 +54,7 @@ export default class Experiences extends PersonalizationAdapter { }, }; super(Object.assign(config, conf)); - + this.personalizeConfig = this.config.modules.personalize; this.experiencesDirPath = resolve( sanitizePath(this.config.data), @@ -102,14 +103,14 @@ export default class Experiences extends PersonalizationAdapter { this.createdCTs = []; this.audiencesUid = (fsUtil.readFile(this.audiencesMapperPath, true) as Record) || {}; this.eventsUid = (fsUtil.readFile(this.eventsMapperPath, true) as Record) || {}; - this.config.context.module = 'experiences'; + this.config.context.module = MODULE_CONTEXTS.EXPERIENCES; this.experiences = []; } /** * The function asynchronously imports experiences from a JSON file and creates them in the system. */ - async import() { + async import() { try { log.debug('Starting experiences import...', this.config.context); @@ -118,7 +119,7 @@ export default class Experiences extends PersonalizationAdapter { log.info('No experiences found to import', this.config.context); // Still need to mark as complete for parent progress if (this.parentProgressManager) { - this.parentProgressManager.tick(true, 'experiences module (no data)', null, 'Experiences'); + this.parentProgressManager.tick(true, 'experiences module (no data)', null, PROCESS_NAMES.EXPERIENCES); } return; } @@ -129,8 +130,9 @@ export default class Experiences extends PersonalizationAdapter { if (this.parentProgressManager) { progress = this.parentProgressManager; log.debug('Using parent progress manager for experiences import', this.config.context); + this.parentProgressManager.updateProcessTotal(PROCESS_NAMES.EXPERIENCES, experiencesCount); } else { - progress = this.createSimpleProgress('Experiences', experiencesCount); + progress = this.createSimpleProgress(PROCESS_NAMES.EXPERIENCES, experiencesCount); log.debug('Created standalone progress manager for experiences import', this.config.context); } @@ -143,7 +145,7 @@ export default class Experiences extends PersonalizationAdapter { for (const experience of this.experiences) { const { uid, ...restExperienceData } = experience; log.debug(`Processing experience: ${uid}`, this.config.context); - + //check whether reference audience exists or not that referenced in variations having __type equal to AudienceBasedVariation & targeting let experienceReqObj: CreateExperienceInput = lookUpAudiences(restExperienceData, this.audiencesUid); //check whether events exists or not that referenced in metrics @@ -162,23 +164,28 @@ export default class Experiences extends PersonalizationAdapter { handleAndLogError(error, this.config.context, `Failed to import experience versions for ${expRes.uid}`); } - this.updateProgress(true, `experience: ${experience.name || uid}`, undefined, 'Experiences'); + this.updateProgress(true, `experience: ${experience.name || uid}`, undefined, PROCESS_NAMES.EXPERIENCES); log.debug(`Successfully processed experience: ${uid}`, this.config.context); } catch (error) { - this.updateProgress(false, `experience: ${experience.name || uid}`, (error as any)?.message, 'Experiences'); + this.updateProgress( + false, + `experience: ${experience.name || uid}`, + (error as any)?.message, + PROCESS_NAMES.EXPERIENCES, + ); handleAndLogError(error, this.config.context, `Failed to create experience: ${uid}`); } } - + fsUtil.writeFile(this.experiencesUidMapperPath, this.experiencesUidMapper); log.success('Experiences created successfully', this.config.context); - log.info('Validating variant and variant group creation',this.config.context); + log.info('Validating variant and variant group creation', this.config.context); this.pendingVariantAndVariantGrpForExperience = values(cloneDeep(this.experiencesUidMapper)); const jobRes = await this.validateVariantGroupAndVariantsCreated(); fsUtil.writeFile(this.cmsVariantPath, this.cmsVariants); fsUtil.writeFile(this.cmsVariantGroupPath, this.cmsVariantGroups); - + if (jobRes) { log.success('Variant and variant groups created successfully', this.config.context); } else { @@ -239,7 +246,7 @@ export default class Experiences extends PersonalizationAdapter { */ async importExperienceVersions(experience: ExperienceStruct, oldExperienceUid: string) { log.debug(`Importing versions for experience: ${oldExperienceUid}`, this.config.context); - + const versionsPath = resolve( sanitizePath(this.experiencesDirPath), 'versions', @@ -253,7 +260,7 @@ export default class Experiences extends PersonalizationAdapter { const versions = fsUtil.readFile(versionsPath, true) as ExperienceStruct[]; log.debug(`Found ${versions.length} versions for experience: ${oldExperienceUid}`, this.config.context); - + const versionMap: Record = { ACTIVE: undefined, DRAFT: undefined, @@ -320,8 +327,11 @@ export default class Experiences extends PersonalizationAdapter { * @returns */ async validateVariantGroupAndVariantsCreated(retryCount = 0): Promise { - log.debug(`Validating variant groups and variants creation - attempt ${retryCount + 1}/${this.maxValidateRetry}`, this.config.context); - + log.debug( + `Validating variant groups and variants creation - attempt ${retryCount + 1}/${this.maxValidateRetry}`, + this.config.context, + ); + try { const promises = this.pendingVariantAndVariantGrpForExperience.map(async (expUid) => { log.debug(`Checking experience: ${expUid}`, this.config.context); @@ -351,7 +361,10 @@ export default class Experiences extends PersonalizationAdapter { return this.validateVariantGroupAndVariantsCreated(retryCount); } else { log.error('Personalize job failed to create variants and variant groups', this.config.context); - log.error(`Failed experiences: ${this.pendingVariantAndVariantGrpForExperience.join(', ')}`, this.config.context); + log.error( + `Failed experiences: ${this.pendingVariantAndVariantGrpForExperience.join(', ')}`, + this.config.context, + ); fsUtil.writeFile(this.failedCmsExpPath, this.pendingVariantAndVariantGrpForExperience); return false; } @@ -367,7 +380,7 @@ export default class Experiences extends PersonalizationAdapter { async attachCTsInExperience() { log.debug('Attaching content types to experiences', this.config.context); - + try { // Read the created content types from the file this.createdCTs = fsUtil.readFile(this.cTsSuccessPath, true) as any; @@ -375,22 +388,25 @@ export default class Experiences extends PersonalizationAdapter { log.debug('No Content types created, skipping following process', this.config.context); return; } - + log.debug(`Found ${this.createdCTs.length} created content types`, this.config.context); const experienceCTsMap = fsUtil.readFile(this.experienceCTsPath, true) as Record; - + return await Promise.allSettled( Object.entries(this.experiencesUidMapper).map(async ([oldExpUid, newExpUid]) => { if (experienceCTsMap[oldExpUid]?.length) { log.debug(`Processing content types for experience: ${oldExpUid} -> ${newExpUid}`, this.config.context); - + // Filter content types that were created const updatedContentTypes = experienceCTsMap[oldExpUid].filter( (ct: any) => this.createdCTs.includes(ct?.uid) && ct.status === 'linked', ); - + if (updatedContentTypes?.length) { - log.debug(`Attaching ${updatedContentTypes.length} content types to experience: ${newExpUid}`, this.config.context); + log.debug( + `Attaching ${updatedContentTypes.length} content types to experience: ${newExpUid}`, + this.config.context, + ); const { variant_groups: [variantGroup] = [] } = (await this.getVariantGroup({ experienceUid: newExpUid })) || {}; variantGroup.content_types = updatedContentTypes; @@ -411,11 +427,11 @@ export default class Experiences extends PersonalizationAdapter { async createVariantIdMapper() { log.debug('Creating variant ID mapper', this.config.context); - + try { const experienceVariantIds: any = fsUtil.readFile(this.experienceVariantsIdsPath, true) || []; log.debug(`Found ${experienceVariantIds.length} experience variant IDs to process`, this.config.context); - + const variantUIDMapper: Record = {}; for (let experienceVariantId of experienceVariantIds) { const [experienceId, variantShortId, oldVariantId] = experienceVariantId.split('-'); diff --git a/packages/contentstack-variants/src/import/project.ts b/packages/contentstack-variants/src/import/project.ts index dcc0c6c9f9..74a9018e7e 100644 --- a/packages/contentstack-variants/src/import/project.ts +++ b/packages/contentstack-variants/src/import/project.ts @@ -1,8 +1,9 @@ import { join, resolve as pResolve } from 'path'; import { existsSync, readFileSync } from 'fs'; -import { sanitizePath, log } from '@contentstack/cli-utilities'; +import { sanitizePath, log, cliux } from '@contentstack/cli-utilities'; import { PersonalizationAdapter, askProjectName, fsUtil } from '../utils'; import { APIConfig, CreateProjectInput, ImportConfig, ProjectStruct } from '../types'; +import { PROCESS_NAMES, MODULE_CONTEXTS, IMPORT_PROCESS_STATUS } from '../utils/constants'; export default class Project extends PersonalizationAdapter { private projectMapperFolderPath: string; @@ -22,7 +23,7 @@ export default class Project extends PersonalizationAdapter { sanitizePath(this.config.modules.personalize.dirName), 'projects', ); - this.config.context.module = 'project'; + this.config.context.module = MODULE_CONTEXTS.PROJECTS; this.projectsData = []; } @@ -37,17 +38,20 @@ export default class Project extends PersonalizationAdapter { const [canImport, projectsCount] = await this.analyzeProjects(); if (!canImport) { log.info('No projects found to import', this.config.context); + if (this.parentProgressManager) { + this.parentProgressManager.tick(true, 'projects module (no data)', null, PROCESS_NAMES.PROJECTS); + } return; } - // If we have a parent progress manager, use it as a sub-module - // Otherwise create our own simple progress manager + // Fix 1: Always use parent progress manager when available let progress; if (this.parentProgressManager) { progress = this.parentProgressManager; log.debug('Using parent progress manager for projects import', this.config.context); + // Don't create our own progress - use parent's } else { - progress = this.createSimpleProgress('Projects', projectsCount); + progress = this.createSimpleProgress(PROCESS_NAMES.PROJECTS, projectsCount); log.debug('Created standalone progress manager for projects import', this.config.context); } @@ -55,7 +59,7 @@ export default class Project extends PersonalizationAdapter { for (const project of this.projectsData) { if (!this.parentProgressManager) { - progress.updateStatus(`Creating project: ${project.name}...`); + progress.updateStatus(IMPORT_PROCESS_STATUS[PROCESS_NAMES.PROJECTS].CREATING); } log.debug(`Processing project: ${project.name}`, this.config.context); @@ -72,7 +76,18 @@ export default class Project extends PersonalizationAdapter { error.includes('personalize.PROJECTS.DUPLICATE_NAME') ) { log.warn(`Project name already exists, generating new name`, this.config.context); + + // Prevent progress bar corruption with clean newlines + cliux.print('\n'); const projectName = await askProjectName('Copy Of ' + (newName || project.name)); + cliux.print('\n'); + if (this.parentProgressManager) { + this.parentProgressManager.updateStatus( + IMPORT_PROCESS_STATUS[PROCESS_NAMES.PROJECTS].CREATING, + PROCESS_NAMES.PROJECTS, + ); + } + return await createProject(projectName); } throw error; @@ -87,10 +102,10 @@ export default class Project extends PersonalizationAdapter { await fsUtil.makeDirectory(this.projectMapperFolderPath); fsUtil.writeFile(pResolve(sanitizePath(this.projectMapperFolderPath), 'projects.json'), projectRes); - this.updateProgress(true, `project: ${project.name}`, undefined, 'Projects'); + this.updateProgress(true, `project: ${project.name}`, undefined, PROCESS_NAMES.PROJECTS); log.success(`Project created successfully: ${projectRes.uid}`, this.config.context); } catch (error) { - this.updateProgress(false, `project: ${project.name}`, (error as any)?.message, 'Projects'); + this.updateProgress(false, `project: ${project.name}`, (error as any)?.message, PROCESS_NAMES.PROJECTS); throw error; } } @@ -105,7 +120,7 @@ export default class Project extends PersonalizationAdapter { this.config.context, ); } catch (error) { - this.config.modules.personalize.importData = false; + this.config.modules.personalize.importData = false; if (!this.parentProgressManager) { this.completeProgress(false, (error as any)?.message || 'Project import failed'); } diff --git a/packages/contentstack-variants/src/import/variant-entries.ts b/packages/contentstack-variants/src/import/variant-entries.ts index 98dcf51ba4..c183aac207 100644 --- a/packages/contentstack-variants/src/import/variant-entries.ts +++ b/packages/contentstack-variants/src/import/variant-entries.ts @@ -23,6 +23,7 @@ import { PublishVariantEntryDto, } from '../types'; import { fsUtil } from '../utils'; +import { PROCESS_NAMES, MODULE_CONTEXTS } from '../utils/constants'; export default class VariantEntries extends VariantAdapter> { public entriesDirPath: string; @@ -39,6 +40,7 @@ export default class VariantEntries extends VariantAdapter; private environments!: Record; + public progress: any; constructor(readonly config: ImportConfig & { helpers?: ImportHelperMethodsConfig }) { const conf: APIConfig & AdapterType, APIConfig> = { @@ -61,7 +63,7 @@ export default class VariantEntries extends VariantAdapter; - if (isEmpty(this.variantIdList)) { - log.warn('Empty variant UID data found', this.config.context); - return; - } + // NOTE Read and store list of variant IDs + this.variantIdList = (fsUtil.readFile(variantIdPath, true) || {}) as Record; + if (isEmpty(this.variantIdList)) { + log.warn('Empty variant UID data found', this.config.context); + return; + } - // NOTE entry relational data lookup dependencies. - this.entriesUidMapper = (fsUtil.readFile(entriesUidMapperPath, true) || {}) as Record; - this.installedExtensions = ((fsUtil.readFile(marketplaceAppMapperPath) as any) || { extension_uid: {} }) - .extension_uid as Record[]; - this.taxonomies = (fsUtil.readFile(taxonomiesPath, true) || {}) as Record; - this.assetUidMapper = (fsUtil.readFile(assetUidMapperPath, true) || {}) as Record; - this.assetUrlMapper = (fsUtil.readFile(assetUrlMapperPath, true) || {}) as Record; - this.environments = (fsUtil.readFile(envPath, true) || {}) as Record; + // NOTE entry relational data lookup dependencies. + this.entriesUidMapper = (fsUtil.readFile(entriesUidMapperPath, true) || {}) as Record; + this.installedExtensions = ((fsUtil.readFile(marketplaceAppMapperPath) as any) || { extension_uid: {} }) + .extension_uid as Record[]; + this.taxonomies = (fsUtil.readFile(taxonomiesPath, true) || {}) as Record; + this.assetUidMapper = (fsUtil.readFile(assetUidMapperPath, true) || {}) as Record; + this.assetUrlMapper = (fsUtil.readFile(assetUrlMapperPath, true) || {}) as Record; + this.environments = (fsUtil.readFile(envPath, true) || {}) as Record; - log.debug( - `Loaded dependency data - Entries: ${Object.keys(this.entriesUidMapper).length}, Assets: ${ - Object.keys(this.assetUidMapper).length - }, Taxonomies: ${Object.keys(this.taxonomies).length}`, - this.config.context, - ); + log.debug( + `Loaded dependency data - Entries: ${Object.keys(this.entriesUidMapper).length}, Assets: ${ + Object.keys(this.assetUidMapper).length + }, Taxonomies: ${Object.keys(this.taxonomies).length}`, + this.config.context, + ); - // If we have a parent progress manager, use it as a sub-module - // Otherwise create our own simple progress manager - let progress; - if (this.parentProgressManager) { - progress = this.parentProgressManager; - log.debug('Using parent progress manager for variant entries import', this.config.context); - } else { - progress = this.createSimpleProgress('Variant Entries', entriesForVariants.length); - log.debug('Created standalone progress manager for variant entries import', this.config.context); - } + if (this.parentProgressManager) { + this.progress = this.parentProgressManager; + log.debug('Using parent progress manager for variant entries import', this.config.context); + } else { + this.progress = this.createSimpleProgress(PROCESS_NAMES.VARIANT_ENTRIES); + log.debug('Created standalone progress manager for variant entries import', this.config.context); + } - // set the token - await this.variantInstance.init(); + // set the token + await this.variantInstance.init(); - log.info(`Processing ${entriesForVariants.length} entries for variant import`, this.config.context); - for (const entriesForVariant of entriesForVariants) { - try { - await this.importVariantEntries(entriesForVariant); - this.updateProgress(true, `variant entry: ${entriesForVariant.content_type}/${entriesForVariant.locale}/${entriesForVariant.entry_uid}`, undefined, 'Variant Entries'); - log.debug(`Successfully processed variant entry: ${entriesForVariant.content_type}/${entriesForVariant.locale}/${entriesForVariant.entry_uid}`, this.config.context); - } catch (error) { - this.updateProgress(false, `variant entry: ${entriesForVariant.content_type}/${entriesForVariant.locale}/${entriesForVariant.entry_uid}`, (error as any)?.message, 'Variant Entries'); - handleAndLogError(error, this.config.context, `Failed to import variant entry: ${entriesForVariant.content_type}/${entriesForVariant.locale}/${entriesForVariant.entry_uid}`); + log.info(`Processing ${entriesForVariants.length} entries for variant import`, this.config.context); + for (const entriesForVariant of entriesForVariants) { + try { + await this.importVariantEntries(entriesForVariant); + log.debug( + `Successfully processed variant entry: ${entriesForVariant.content_type}/${entriesForVariant.locale}/${entriesForVariant.entry_uid}`, + this.config.context, + ); + } catch (error) { + handleAndLogError( + error, + this.config.context, + `Failed to import variant entry: ${entriesForVariant.content_type}/${entriesForVariant.locale}/${entriesForVariant.entry_uid}`, + ); + } } - } - // Only complete progress if we own the progress manager (no parent) - if (!this.parentProgressManager) { - this.completeProgress(true); - } + // Only complete progress if we own the progress manager (no parent) + if (!this.parentProgressManager) { + this.completeProgress(true); + } - log.success(`Variant entries imported successfully! Total entries: ${entriesForVariants.length} - processing completed`, this.config.context); + log.success( + `Variant entries imported successfully! Total entries: ${entriesForVariants.length} - processing completed`, + this.config.context, + ); } catch (error) { if (!this.parentProgressManager) { this.completeProgress(false, (error as any)?.message || 'Variant entries import failed'); @@ -194,6 +201,7 @@ export default class VariantEntries extends VariantAdapter { @@ -293,6 +309,13 @@ export default class VariantEntries extends VariantAdapter