Skip to content

Commit 5e1e6af

Browse files
authored
Merge pull request #2046 from contentstack/feat/DX-3290
feat: Integrated CLIProgressManager and SummaryManager in variant export
2 parents e316efc + a739e24 commit 5e1e6af

23 files changed

Lines changed: 1037 additions & 422 deletions

File tree

packages/contentstack-auth/src/base-command.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
5151
command: this.context?.info?.command || 'auth',
5252
module: '',
5353
userId: configHandler.get('userUid') || '',
54-
email: configHandler.get('email') || '',
5554
sessionId: this.context?.sessionId,
5655
apiKey: apiKey || '',
5756
orgId: configHandler.get('oauthOrgUid') || '',

packages/contentstack-auth/src/interfaces/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface Context {
2828
command: string;
2929
module: string;
3030
userId: string | undefined;
31-
email: string | undefined;
31+
email?: string | undefined;
3232
sessionId: string | undefined;
3333
clientId?: string | undefined;
3434
apiKey: string;

packages/contentstack-export/src/commands/cm/stacks/export.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ export default class ExportCommand extends Command {
158158
command: this.context?.info?.command || 'cm:stacks:export',
159159
module: '',
160160
userId: configHandler.get('userUid') || '',
161-
email: configHandler.get('email') || '',
162161
sessionId: this.context?.sessionId || '',
163162
apiKey: apiKey || '',
164163
orgId: configHandler.get('oauthOrgUid') || '',

packages/contentstack-export/src/export/modules/entries.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -353,23 +353,20 @@ export default class EntriesExport extends BaseClass {
353353
if (this.exportVariantEntry) {
354354
log.debug('Exporting variant entries for base entries', this.exportConfig.context);
355355
try {
356+
// Set parent progress manager for variant entries
357+
if (this.variantEntries && typeof this.variantEntries.setParentProgressManager === 'function') {
358+
this.variantEntries.setParentProgressManager(this.progressManager);
359+
}
360+
356361
await this.variantEntries.exportVariantEntry({
357362
locale: options.locale,
358363
contentTypeUid: options.contentType,
359364
entries: entriesSearchResponse.items,
360365
});
361366

362-
// Track progress for variant entries
363-
entriesSearchResponse.items.forEach((entry: any) => {
364-
this.progressManager?.tick(true, `variant: ${entry.uid}`, null, 'Variant Entries');
365-
});
366-
367367
log.debug(`Successfully exported variant entries for ${entriesSearchResponse.items.length} entries`, this.exportConfig.context);
368368
} catch (error) {
369369
log.debug('Failed to export variant entries', this.exportConfig.context);
370-
entriesSearchResponse.items.forEach((entry: any) => {
371-
this.progressManager?.tick(false, `variant: ${entry.uid}`, error?.message || 'Failed to export variant', 'Variant Entries');
372-
});
373370
}
374371
}
375372

packages/contentstack-export/src/export/modules/personalize.ts

Lines changed: 114 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
ExportAudiences,
77
AnyProperty,
88
} from '@contentstack/cli-variants';
9-
import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilities';
9+
import { handleAndLogError, messageHandler, log, CLIProgressManager } from '@contentstack/cli-utilities';
1010

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

18+
private readonly moduleInstanceMapper = {
19+
events: ExportEvents,
20+
attributes: ExportAttributes,
21+
audiences: ExportAudiences,
22+
experiences: ExportExperiences,
23+
};
24+
25+
private readonly moduleDisplayMapper = {
26+
events: 'Events',
27+
attributes: 'Attributes',
28+
audiences: 'Audiences',
29+
experiences: 'Experiences',
30+
};
31+
1832
constructor({ exportConfig, stackAPIClient }: ModuleClassParams) {
1933
super({ exportConfig, stackAPIClient });
2034
this.exportConfig = exportConfig;
@@ -32,6 +46,12 @@ export default class ExportPersonalize extends BaseClass {
3246
async () => {
3347
const canProceed = this.validatePersonalizeSetup();
3448
const moduleCount = canProceed ? this.getPersonalizeModuleCount() : 0;
49+
50+
log.debug(
51+
`Personalize validation - canProceed: ${canProceed}, moduleCount: ${moduleCount}`,
52+
this.exportConfig.context,
53+
);
54+
3555
return [canProceed, moduleCount];
3656
},
3757
);
@@ -41,61 +61,20 @@ export default class ExportPersonalize extends BaseClass {
4161
return;
4262
}
4363

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

46-
// Add projects export process (always runs first)
47-
progress.addProcess('Projects', 1);
48-
49-
// Add personalize modules processes if enabled
50-
if (this.exportConfig.personalizationEnabled && moduleCount > 0) {
51-
progress.addProcess('Personalize Modules', moduleCount);
52-
}
67+
this.addProjectProcess(progress);
68+
this.addModuleProcesses(progress, moduleCount);
5369

5470
try {
55-
// Process projects export
56-
progress.startProcess('Projects').updateStatus('Exporting personalization projects...', 'Projects');
57-
log.debug('Starting projects export for personalization...', this.exportConfig.context);
58-
await new ExportProjects(this.exportConfig).start();
59-
this.progressManager?.tick(true, 'projects export', null, 'Projects');
60-
progress.completeProcess('Projects', true);
61-
62-
if (this.exportConfig.personalizationEnabled && moduleCount > 0) {
63-
progress
64-
.startProcess('Personalize Modules')
65-
.updateStatus('Processing personalize modules...', 'Personalize Modules');
66-
log.debug('Personalization is enabled, processing personalize modules...', this.exportConfig.context);
67-
68-
const moduleMapper = {
69-
events: new ExportEvents(this.exportConfig),
70-
attributes: new ExportAttributes(this.exportConfig),
71-
audiences: new ExportAudiences(this.exportConfig),
72-
experiences: new ExportExperiences(this.exportConfig),
73-
};
74-
75-
const order: (keyof typeof moduleMapper)[] = this.exportConfig.modules.personalize
76-
.exportOrder as (keyof typeof moduleMapper)[];
77-
78-
log.debug(`Personalize export order: ${order.join(', ')}`, this.exportConfig.context);
79-
80-
for (const module of order) {
81-
log.debug(`Processing personalize module: ${module}`, this.exportConfig.context);
82-
83-
if (moduleMapper[module]) {
84-
log.debug(`Starting export for module: ${module}`, this.exportConfig.context);
85-
await moduleMapper[module].start();
86-
this.progressManager?.tick(true, `module: ${module}`, null, 'Personalize Modules');
87-
log.debug(`Completed export for module: ${module}`, this.exportConfig.context);
88-
} else {
89-
log.debug(`Module not implemented: ${module}`, this.exportConfig.context);
90-
this.progressManager?.tick(false, `module: ${module}`, 'Module not implemented', 'Personalize Modules');
91-
log.info(messageHandler.parse('PERSONALIZE_MODULE_NOT_IMPLEMENTED', module), this.exportConfig.context);
92-
}
93-
}
94-
95-
progress.completeProcess('Personalize Modules', true);
96-
log.debug('Completed all personalize module exports', this.exportConfig.context);
71+
await this.exportProjects(progress);
72+
73+
if (moduleCount > 0) {
74+
log.debug('Processing personalize modules...', this.exportConfig.context);
75+
await this.exportModules(progress);
9776
} else {
98-
log.debug('Personalization is disabled, skipping personalize module exports', this.exportConfig.context);
77+
log.debug('No personalize modules configured for processing', this.exportConfig.context);
9978
}
10079

10180
this.completeProgress(true);
@@ -105,7 +84,7 @@ export default class ExportPersonalize extends BaseClass {
10584
log.debug('Personalize access forbidden, personalization not enabled', this.exportConfig.context);
10685
log.info(messageHandler.parse('PERSONALIZE_NOT_ENABLED'), this.exportConfig.context);
10786
this.exportConfig.personalizationEnabled = false;
108-
this.completeProgress(true); // Complete successfully but with personalization disabled
87+
this.completeProgress(true); // considered successful even if skipped
10988
} else {
11089
log.debug('Error occurred during personalize module processing', this.exportConfig.context);
11190
this.completeProgress(false, moduleError?.message || 'Personalize module processing failed');
@@ -142,4 +121,87 @@ export default class ExportPersonalize extends BaseClass {
142121
const order = this.exportConfig.modules?.personalize?.exportOrder;
143122
return Array.isArray(order) ? order.length : 0;
144123
}
124+
125+
private addProjectProcess(progress: CLIProgressManager) {
126+
progress.addProcess('Projects', 1);
127+
log.debug('Added Projects process to personalize progress', this.exportConfig.context);
128+
}
129+
130+
private addModuleProcesses(progress: CLIProgressManager, moduleCount: number) {
131+
if (moduleCount > 0) {
132+
// talisman-ignore-start
133+
const order: (keyof typeof this.moduleDisplayMapper)[] = this.exportConfig.modules.personalize
134+
.exportOrder as (keyof typeof this.moduleDisplayMapper)[];
135+
// talisman-ignore-end
136+
137+
log.debug(`Adding ${order.length} personalize module processes: ${order.join(', ')}`, this.exportConfig.context);
138+
139+
for (const module of order) {
140+
const processName = this.moduleDisplayMapper[module];
141+
progress.addProcess(processName, 1);
142+
log.debug(`Added ${processName} process to personalize progress`, this.exportConfig.context);
143+
}
144+
} else {
145+
log.debug('No personalize modules to add to progress', this.exportConfig.context);
146+
}
147+
}
148+
149+
private async exportProjects(progress: CLIProgressManager) {
150+
progress.startProcess('Projects').updateStatus('Exporting personalization projects...', 'Projects');
151+
log.debug('Starting projects export for personalization...', this.exportConfig.context);
152+
153+
const projectsExporter = new ExportProjects(this.exportConfig);
154+
projectsExporter.setParentProgressManager(progress);
155+
await projectsExporter.start();
156+
157+
progress.completeProcess('Projects', true);
158+
}
159+
160+
private async exportModules(progress: CLIProgressManager) {
161+
// Set parent progress for all module instances
162+
Object.entries(this.moduleInstanceMapper).forEach(([_, ModuleClass]) => {
163+
const instance = new ModuleClass(this.exportConfig);
164+
instance.setParentProgressManager(progress);
165+
});
166+
167+
// talisman-ignore-start
168+
const order: (keyof typeof this.moduleInstanceMapper)[] = this.exportConfig.modules.personalize
169+
.exportOrder as (keyof typeof this.moduleInstanceMapper)[];
170+
// talisman-ignore-end
171+
172+
log.debug(`Personalize export order: ${order.join(', ')}`, this.exportConfig.context);
173+
174+
for (const module of order) {
175+
log.debug(`Processing personalize module: ${module}`, this.exportConfig.context);
176+
const processName = this.moduleDisplayMapper[module];
177+
const ModuleClass = this.moduleInstanceMapper[module];
178+
179+
if (ModuleClass) {
180+
progress.startProcess(processName).updateStatus(`Exporting ${module}...`, processName);
181+
log.debug(`Starting export for module: ${module}`, this.exportConfig.context);
182+
183+
if (this.exportConfig.personalizationEnabled) {
184+
const exporter = new ModuleClass(this.exportConfig);
185+
exporter.setParentProgressManager(progress);
186+
await exporter.start();
187+
188+
progress.completeProcess(processName, true);
189+
log.debug(`Completed export for module: ${module}`, this.exportConfig.context);
190+
} else {
191+
log.debug(`Skipping ${module} - personalization not enabled`, this.exportConfig.context);
192+
this.progressManager?.tick(true, `${module} skipped (no project)`, null, processName);
193+
progress.completeProcess(processName, true);
194+
log.info(`Skipped ${module} export - no personalize project found`, this.exportConfig.context);
195+
}
196+
} else {
197+
log.debug(`Module not implemented: ${module}`, this.exportConfig.context);
198+
progress.startProcess(processName).updateStatus(`Module not implemented: ${module}`, processName);
199+
this.progressManager?.tick(false, `module: ${module}`, 'Module not implemented', processName);
200+
progress.completeProcess(processName, false);
201+
log.info(messageHandler.parse('PERSONALIZE_MODULE_NOT_IMPLEMENTED', module), this.exportConfig.context);
202+
}
203+
}
204+
205+
log.debug('Completed all personalize module processing', this.exportConfig.context);
206+
}
145207
}

packages/contentstack-export/src/export/modules/taxonomies.ts

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,25 @@ export default class ExportTaxonomies extends BaseClass {
6868
await this.getAllTaxonomies();
6969
progress.completeProcess('Fetch Taxonomies', true);
7070

71+
const actualTaxonomyCount = Object.keys(this.taxonomies)?.length;
72+
log.debug(`Found ${actualTaxonomyCount} taxonomies to export (API reported ${totalCount})`, this.exportConfig.context);
73+
74+
// Update progress for export step if counts differ
75+
if (actualTaxonomyCount !== totalCount && actualTaxonomyCount > 0) {
76+
// Remove the old process and add with correct count
77+
progress.addProcess('Export Taxonomies & Terms', actualTaxonomyCount);
78+
}
79+
7180
// Export detailed taxonomies
72-
progress
73-
.startProcess('Export Taxonomies & Terms')
74-
.updateStatus('Exporting taxonomy details...', 'Export Taxonomies & Terms');
75-
await this.exportTaxonomies();
76-
progress.completeProcess('Export Taxonomies & Terms', true);
81+
if (actualTaxonomyCount > 0) {
82+
progress
83+
.startProcess('Export Taxonomies & Terms')
84+
.updateStatus('Exporting taxonomy details...', 'Export Taxonomies & Terms');
85+
await this.exportTaxonomies();
86+
progress.completeProcess('Export Taxonomies & Terms', true);
87+
} else {
88+
log.info('No taxonomies found to export detailed information', this.exportConfig.context);
89+
}
7790

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

197-
return this.makeConcurrentCall({
198-
totalCount: keys(this.taxonomies).length,
199-
apiParams: {
200-
module: 'export-taxonomy',
201-
resolve: onSuccess,
202-
reject: onReject,
203-
},
204-
module: 'taxonomies detailed export',
205-
concurrencyLimit: this.exportConfig?.fetchConcurrency || 1,
206-
}).then(() => {
207-
const taxonomiesFilePath = pResolve(this.taxonomiesFolderPath, this.taxonomiesConfig.fileName);
208-
log.debug(`Writing taxonomies index to: ${taxonomiesFilePath}`, this.exportConfig.context);
209-
fsUtil.writeFile(taxonomiesFilePath, this.taxonomies);
210-
});
210+
const taxonomyUids = keys(this.taxonomies);
211+
log.debug(`Starting detailed export for ${taxonomyUids.length} taxonomies`, this.exportConfig.context);
212+
213+
// Export each taxonomy individually
214+
for (const uid of taxonomyUids) {
215+
try {
216+
log.debug(`Exporting detailed taxonomy: ${uid}`, this.exportConfig.context);
217+
await this.makeAPICall({
218+
module: 'export-taxonomy',
219+
uid,
220+
resolve: onSuccess,
221+
reject: onReject,
222+
});
223+
} catch (error) {
224+
onReject({ error, uid });
225+
}
226+
}
227+
228+
// Write the taxonomies index file
229+
const taxonomiesFilePath = pResolve(this.taxonomiesFolderPath, this.taxonomiesConfig.fileName);
230+
log.debug(`Writing taxonomies index to: ${taxonomiesFilePath}`, this.exportConfig.context);
231+
fsUtil.writeFile(taxonomiesFilePath, this.taxonomies);
211232
}
212233
}

packages/contentstack-export/src/types/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export interface Context {
133133
command: string;
134134
module: string;
135135
userId: string | undefined;
136-
email: string | undefined;
136+
email?: string | undefined;
137137
sessionId: string | undefined;
138138
clientId?: string | undefined;
139139
apiKey: string;

packages/contentstack-export/src/utils/marketplace-app-helper.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { cliux, handleAndLogError, NodeCrypto, managementSDKClient, createDeveloperHubUrl } from '@contentstack/cli-utilities';
1+
import {
2+
cliux,
3+
handleAndLogError,
4+
NodeCrypto,
5+
managementSDKClient,
6+
createDeveloperHubUrl,
7+
} from '@contentstack/cli-utilities';
28

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

@@ -12,7 +18,7 @@ export async function getOrgUid(config: ExportConfig): Promise<string> {
1218
.stack({ api_key: config.source_stack })
1319
.fetch()
1420
.catch((error: any) => {
15-
handleAndLogError(error, {...config.context});
21+
handleAndLogError(error, { ...config.context });
1622
});
1723

1824
return tempStackData?.org_uid;
@@ -24,6 +30,9 @@ export async function createNodeCryptoInstance(config: ExportConfig): Promise<No
2430
if (config.forceStopMarketplaceAppsPrompt) {
2531
cryptoArgs['encryptionKey'] = config.marketplaceAppEncryptionKey;
2632
} else {
33+
// Add spacing
34+
cliux.print('');
35+
2736
cryptoArgs['encryptionKey'] = await cliux.inquire({
2837
type: 'input',
2938
name: 'name',
@@ -35,6 +44,7 @@ export async function createNodeCryptoInstance(config: ExportConfig): Promise<No
3544
},
3645
message: 'Enter Marketplace app configurations encryption key',
3746
});
47+
cliux.print('');
3848
}
3949

4050
return new NodeCrypto(cryptoArgs);

packages/contentstack-import/src/commands/cm/stacks/import.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ export default class ImportCommand extends Command {
205205
command: this.context?.info?.command || 'cm:stacks:import',
206206
module: '',
207207
userId: configHandler.get('userUid') || '',
208-
email: configHandler.get('email') || '',
209208
sessionId: this.context?.sessionId,
210209
apiKey: apiKey || '',
211210
orgId: configHandler.get('oauthOrgUid') || '',

0 commit comments

Comments
 (0)