Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/contentstack-auth/src/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
command: this.context?.info?.command || 'auth',
module: '',
userId: configHandler.get('userUid') || '',
email: configHandler.get('email') || '',
sessionId: this.context?.sessionId,
apiKey: apiKey || '',
orgId: configHandler.get('oauthOrgUid') || '',
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-auth/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface Context {
command: string;
module: string;
userId: string | undefined;
email: string | undefined;
email?: string | undefined;
sessionId: string | undefined;
clientId?: string | undefined;
apiKey: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ export default class ExportCommand extends Command {
command: this.context?.info?.command || 'cm:stacks:export',
module: '',
userId: configHandler.get('userUid') || '',
email: configHandler.get('email') || '',
sessionId: this.context?.sessionId || '',
apiKey: apiKey || '',
orgId: configHandler.get('oauthOrgUid') || '',
Expand Down
13 changes: 5 additions & 8 deletions packages/contentstack-export/src/export/modules/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,23 +353,20 @@ export default class EntriesExport extends BaseClass {
if (this.exportVariantEntry) {
log.debug('Exporting variant entries for base entries', this.exportConfig.context);
try {
// Set parent progress manager for variant entries
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,
});

// Track progress for variant entries
entriesSearchResponse.items.forEach((entry: any) => {
this.progressManager?.tick(true, `variant: ${entry.uid}`, null, 'Variant Entries');
});

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);
entriesSearchResponse.items.forEach((entry: any) => {
this.progressManager?.tick(false, `variant: ${entry.uid}`, error?.message || 'Failed to export variant', 'Variant Entries');
});
}
}

Expand Down
166 changes: 114 additions & 52 deletions packages/contentstack-export/src/export/modules/personalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ExportAudiences,
AnyProperty,
} from '@contentstack/cli-variants';
import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities';
import { handleAndLogError, messageHandler, log, CLIProgressManager } from '@contentstack/cli-utilities';

import { ModuleClassParams, ExportConfig } from '../../types';
import BaseClass from './base-class';
Expand All @@ -15,6 +15,20 @@ export default class ExportPersonalize extends BaseClass {
public exportConfig: ExportConfig;
public personalizeConfig: { dirName: string; baseURL: Record<string, string> } & AnyProperty;

private readonly moduleInstanceMapper = {
events: ExportEvents,
attributes: ExportAttributes,
audiences: ExportAudiences,
experiences: ExportExperiences,
};

private readonly moduleDisplayMapper = {
events: 'Events',
attributes: 'Attributes',
audiences: 'Audiences',
experiences: 'Experiences',
};

constructor({ exportConfig, stackAPIClient }: ModuleClassParams) {
super({ exportConfig, stackAPIClient });
this.exportConfig = exportConfig;
Expand All @@ -32,6 +46,12 @@ export default class ExportPersonalize extends BaseClass {
async () => {
const canProceed = this.validatePersonalizeSetup();
const moduleCount = canProceed ? this.getPersonalizeModuleCount() : 0;

log.debug(
`Personalize validation - canProceed: ${canProceed}, moduleCount: ${moduleCount}`,
this.exportConfig.context,
);

return [canProceed, moduleCount];
},
);
Expand All @@ -41,61 +61,20 @@ export default class ExportPersonalize extends BaseClass {
return;
}

log.debug(`Creating personalize progress with moduleCount: ${moduleCount}`, this.exportConfig.context);
const progress = this.createNestedProgress(this.currentModuleName);

// Add projects export process (always runs first)
progress.addProcess('Projects', 1);

// Add personalize modules processes if enabled
if (this.exportConfig.personalizationEnabled && moduleCount > 0) {
progress.addProcess('Personalize Modules', moduleCount);
}
this.addProjectProcess(progress);
this.addModuleProcesses(progress, moduleCount);

try {
// Process projects export
progress.startProcess('Projects').updateStatus('Exporting personalization projects...', 'Projects');
log.debug('Starting projects export for personalization...', this.exportConfig.context);
await new ExportProjects(this.exportConfig).start();
this.progressManager?.tick(true, 'projects export', null, 'Projects');
progress.completeProcess('Projects', true);

if (this.exportConfig.personalizationEnabled && moduleCount > 0) {
progress
.startProcess('Personalize Modules')
.updateStatus('Processing personalize modules...', 'Personalize Modules');
log.debug('Personalization is enabled, processing personalize modules...', this.exportConfig.context);

const moduleMapper = {
events: new ExportEvents(this.exportConfig),
attributes: new ExportAttributes(this.exportConfig),
audiences: new ExportAudiences(this.exportConfig),
experiences: new ExportExperiences(this.exportConfig),
};

const order: (keyof typeof moduleMapper)[] = this.exportConfig.modules.personalize
.exportOrder as (keyof typeof moduleMapper)[];

log.debug(`Personalize export order: ${order.join(', ')}`, this.exportConfig.context);

for (const module of order) {
log.debug(`Processing personalize module: ${module}`, this.exportConfig.context);

if (moduleMapper[module]) {
log.debug(`Starting export for module: ${module}`, this.exportConfig.context);
await moduleMapper[module].start();
this.progressManager?.tick(true, `module: ${module}`, null, 'Personalize Modules');
log.debug(`Completed export for module: ${module}`, this.exportConfig.context);
} else {
log.debug(`Module not implemented: ${module}`, this.exportConfig.context);
this.progressManager?.tick(false, `module: ${module}`, 'Module not implemented', 'Personalize Modules');
log.info(messageHandler.parse('PERSONALIZE_MODULE_NOT_IMPLEMENTED', module), this.exportConfig.context);
}
}

progress.completeProcess('Personalize Modules', true);
log.debug('Completed all personalize module exports', this.exportConfig.context);
await this.exportProjects(progress);

if (moduleCount > 0) {
log.debug('Processing personalize modules...', this.exportConfig.context);
await this.exportModules(progress);
} else {
log.debug('Personalization is disabled, skipping personalize module exports', this.exportConfig.context);
log.debug('No personalize modules configured for processing', this.exportConfig.context);
}

this.completeProgress(true);
Expand All @@ -105,7 +84,7 @@ export default class ExportPersonalize extends BaseClass {
log.debug('Personalize access forbidden, personalization not enabled', this.exportConfig.context);
log.info(messageHandler.parse('PERSONALIZE_NOT_ENABLED'), this.exportConfig.context);
this.exportConfig.personalizationEnabled = false;
this.completeProgress(true); // Complete successfully but with personalization disabled
this.completeProgress(true); // considered successful even if skipped
} else {
log.debug('Error occurred during personalize module processing', this.exportConfig.context);
this.completeProgress(false, moduleError?.message || 'Personalize module processing failed');
Expand Down Expand Up @@ -142,4 +121,87 @@ export default class ExportPersonalize extends BaseClass {
const order = this.exportConfig.modules?.personalize?.exportOrder;
return Array.isArray(order) ? order.length : 0;
}

private addProjectProcess(progress: CLIProgressManager) {
progress.addProcess('Projects', 1);
log.debug('Added Projects process to personalize progress', this.exportConfig.context);
}

private addModuleProcesses(progress: CLIProgressManager, moduleCount: number) {
if (moduleCount > 0) {
// talisman-ignore-start
const order: (keyof typeof this.moduleDisplayMapper)[] = this.exportConfig.modules.personalize
.exportOrder as (keyof typeof this.moduleDisplayMapper)[];
// talisman-ignore-end

log.debug(`Adding ${order.length} personalize module processes: ${order.join(', ')}`, this.exportConfig.context);

for (const module of order) {
const processName = this.moduleDisplayMapper[module];
progress.addProcess(processName, 1);
log.debug(`Added ${processName} process to personalize progress`, this.exportConfig.context);
}
} else {
log.debug('No personalize modules to add to progress', this.exportConfig.context);
}
}

private async exportProjects(progress: CLIProgressManager) {
progress.startProcess('Projects').updateStatus('Exporting personalization projects...', '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);
}

private async exportModules(progress: CLIProgressManager) {
// Set parent progress for all module instances
Object.entries(this.moduleInstanceMapper).forEach(([_, ModuleClass]) => {
const instance = new ModuleClass(this.exportConfig);
instance.setParentProgressManager(progress);
});

// talisman-ignore-start
const order: (keyof typeof this.moduleInstanceMapper)[] = this.exportConfig.modules.personalize
.exportOrder as (keyof typeof this.moduleInstanceMapper)[];
// talisman-ignore-end

log.debug(`Personalize export order: ${order.join(', ')}`, this.exportConfig.context);

for (const module of order) {
log.debug(`Processing personalize module: ${module}`, this.exportConfig.context);
const processName = this.moduleDisplayMapper[module];
const ModuleClass = this.moduleInstanceMapper[module];

if (ModuleClass) {
progress.startProcess(processName).updateStatus(`Exporting ${module}...`, processName);
log.debug(`Starting export for module: ${module}`, this.exportConfig.context);

if (this.exportConfig.personalizationEnabled) {
const exporter = new ModuleClass(this.exportConfig);
exporter.setParentProgressManager(progress);
await exporter.start();

progress.completeProcess(processName, true);
log.debug(`Completed export for module: ${module}`, this.exportConfig.context);
} else {
log.debug(`Skipping ${module} - personalization not enabled`, this.exportConfig.context);
this.progressManager?.tick(true, `${module} skipped (no project)`, null, processName);
progress.completeProcess(processName, true);
log.info(`Skipped ${module} export - no personalize project found`, this.exportConfig.context);
}
} else {
log.debug(`Module not implemented: ${module}`, this.exportConfig.context);
progress.startProcess(processName).updateStatus(`Module not implemented: ${module}`, processName);
this.progressManager?.tick(false, `module: ${module}`, 'Module not implemented', processName);
progress.completeProcess(processName, false);
log.info(messageHandler.parse('PERSONALIZE_MODULE_NOT_IMPLEMENTED', module), this.exportConfig.context);
}
}

log.debug('Completed all personalize module processing', this.exportConfig.context);
}
}
59 changes: 40 additions & 19 deletions packages/contentstack-export/src/export/modules/taxonomies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,25 @@ export default class ExportTaxonomies extends BaseClass {
await this.getAllTaxonomies();
progress.completeProcess('Fetch Taxonomies', true);

const actualTaxonomyCount = Object.keys(this.taxonomies)?.length;
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('Export Taxonomies & Terms', actualTaxonomyCount);
}

// Export detailed taxonomies
progress
.startProcess('Export Taxonomies & Terms')
.updateStatus('Exporting taxonomy details...', 'Export Taxonomies & Terms');
await this.exportTaxonomies();
progress.completeProcess('Export Taxonomies & Terms', true);
if (actualTaxonomyCount > 0) {
progress
.startProcess('Export Taxonomies & Terms')
.updateStatus('Exporting taxonomy details...', 'Export Taxonomies & Terms');
await this.exportTaxonomies();
progress.completeProcess('Export Taxonomies & Terms', true);
} else {
log.info('No taxonomies found to export detailed information', this.exportConfig.context);
}

const taxonomyCount = Object.keys(this.taxonomies).length;
log.success(messageHandler.parse('TAXONOMY_EXPORT_COMPLETE', taxonomyCount), this.exportConfig.context);
Expand Down Expand Up @@ -194,19 +207,27 @@ export default class ExportTaxonomies extends BaseClass {
);
};

return this.makeConcurrentCall({
totalCount: keys(this.taxonomies).length,
apiParams: {
module: 'export-taxonomy',
resolve: onSuccess,
reject: onReject,
},
module: 'taxonomies detailed export',
concurrencyLimit: this.exportConfig?.fetchConcurrency || 1,
}).then(() => {
const taxonomiesFilePath = pResolve(this.taxonomiesFolderPath, this.taxonomiesConfig.fileName);
log.debug(`Writing taxonomies index to: ${taxonomiesFilePath}`, this.exportConfig.context);
fsUtil.writeFile(taxonomiesFilePath, this.taxonomies);
});
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 {
log.debug(`Exporting detailed taxonomy: ${uid}`, this.exportConfig.context);
await this.makeAPICall({
module: 'export-taxonomy',
uid,
resolve: onSuccess,
reject: onReject,
});
} catch (error) {
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);
fsUtil.writeFile(taxonomiesFilePath, this.taxonomies);
}
}
2 changes: 1 addition & 1 deletion packages/contentstack-export/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export interface Context {
command: string;
module: string;
userId: string | undefined;
email: string | undefined;
email?: string | undefined;
sessionId: string | undefined;
clientId?: string | undefined;
apiKey: string;
Expand Down
14 changes: 12 additions & 2 deletions packages/contentstack-export/src/utils/marketplace-app-helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { cliux, handleAndLogError, NodeCrypto, managementSDKClient, createDeveloperHubUrl } from '@contentstack/cli-utilities';
import {
cliux,
handleAndLogError,
NodeCrypto,
managementSDKClient,
createDeveloperHubUrl,
} from '@contentstack/cli-utilities';

import { ExportConfig } from '../types';

Expand All @@ -12,7 +18,7 @@ export async function getOrgUid(config: ExportConfig): Promise<string> {
.stack({ api_key: config.source_stack })
.fetch()
.catch((error: any) => {
handleAndLogError(error, {...config.context});
handleAndLogError(error, { ...config.context });
});

return tempStackData?.org_uid;
Expand All @@ -24,6 +30,9 @@ export async function createNodeCryptoInstance(config: ExportConfig): Promise<No
if (config.forceStopMarketplaceAppsPrompt) {
cryptoArgs['encryptionKey'] = config.marketplaceAppEncryptionKey;
} else {
// Add spacing
cliux.print('');

cryptoArgs['encryptionKey'] = await cliux.inquire({
type: 'input',
name: 'name',
Expand All @@ -35,6 +44,7 @@ export async function createNodeCryptoInstance(config: ExportConfig): Promise<No
},
message: 'Enter Marketplace app configurations encryption key',
});
cliux.print('');
}

return new NodeCrypto(cryptoArgs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ export default class ImportCommand extends Command {
command: this.context?.info?.command || 'cm:stacks:import',
module: '',
userId: configHandler.get('userUid') || '',
email: configHandler.get('email') || '',
sessionId: this.context?.sessionId,
apiKey: apiKey || '',
orgId: configHandler.get('oauthOrgUid') || '',
Expand Down
Loading