Skip to content

Commit 9b4b270

Browse files
committed
fix: taxonomy export issue, appended session details in log path
1 parent 8b604b0 commit 9b4b270

8 files changed

Lines changed: 272 additions & 137 deletions

File tree

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
configHandler,
1212
log,
1313
handleAndLogError,
14-
getLogPath,
14+
getSessionLogPath,
1515
CLIProgressManager,
1616
clearProgressModuleSetting,
1717
} from '@contentstack/cli-utilities';
@@ -102,26 +102,28 @@ export default class ExportCommand extends Command {
102102
const managementAPIClient: ContentstackClient = await managementSDKClient(exportConfig);
103103
const moduleExporter = new ModuleExporter(managementAPIClient, exportConfig);
104104
await moduleExporter.start();
105+
const sessionLogPath = getSessionLogPath();
105106
log.success(
106107
`The content of the stack ${exportConfig.apiKey} has been exported successfully!`,
107108
);
108109
log.info(`The exported content has been stored at '${exportDir}'`, exportConfig.context);
109-
log.success(`The log has been stored at '${getLogPath()}'`, exportConfig.context);
110+
log.success(`The log has been stored at '${sessionLogPath}'`, exportConfig.context);
110111

111112
// Print comprehensive summary at the end
112113
if (!exportConfig.branches) CLIProgressManager.printGlobalSummary();
113114
if (!configHandler.get('log')?.showConsoleLogs) {
114-
cliux.print(`The log has been stored at '${getLogPath()}'`, { color: 'green' });
115+
cliux.print(`The log has been stored at '${sessionLogPath}'`, { color: 'green' });
115116
}
116117
// Clear progress module setting now that export is complete
117118
clearProgressModuleSetting();
118119
} catch (error) {
119120
// Clear progress module setting even on error
120121
clearProgressModuleSetting();
121122
handleAndLogError(error);
123+
const sessionLogPath = getSessionLogPath();
122124
if (!configHandler.get('log')?.showConsoleLogs) {
123125
cliux.print(`Error: ${error}`, { color: 'red' });
124-
cliux.print(`The log has been stored at '${getLogPath()}'`, { color: 'green' });
126+
cliux.print(`The log has been stored at '${sessionLogPath}'`, { color: 'green' });
125127
}
126128
}
127129
}

packages/contentstack-export/src/export/modules/base-class.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import chunk from 'lodash/chunk';
55
import isEmpty from 'lodash/isEmpty';
66
import entries from 'lodash/entries';
77
import isEqual from 'lodash/isEqual';
8-
import { log, CLIProgressManager, configHandler } from '@contentstack/cli-utilities';
8+
import { log, CLIProgressManager, configHandler, getSessionLogPath } from '@contentstack/cli-utilities';
99

1010
import { ExportConfig, ModuleClassParams } from '../../types';
1111

@@ -109,7 +109,7 @@ export default abstract class BaseClass {
109109
* - moduleName: The module name to generate the message (e.g., 'Assets', 'Entries')
110110
* If not provided, uses this.currentModuleName
111111
* - customSuccessMessage: Optional custom success message. If not provided, generates: "{moduleName} have been exported successfully!"
112-
* - customWarningMessage: Optional custom warning message. If not provided, generates: "{moduleName} have been exported with some errors. Please check the logs for details."
112+
* - customWarningMessage: Optional custom warning message. If not provided, generates: "{moduleName} have been exported with some errors. Please check the logs at: {sessionLogPath}"
113113
* - context: Optional context for logging
114114
*/
115115
protected completeProgressWithMessage(options?: CompleteProgressOptions): void {
@@ -120,7 +120,8 @@ export default abstract class BaseClass {
120120

121121
// Generate default messages if not provided
122122
const successMessage = options?.customSuccessMessage || `${name} have been exported successfully!`;
123-
const warningMessage = options?.customWarningMessage || `${name} have been exported with some errors. Please check the logs for details.`;
123+
const sessionLogPath = getSessionLogPath();
124+
const warningMessage = options?.customWarningMessage || `${name} have been exported with some errors. Please check the logs at: ${sessionLogPath}`;
124125

125126
this.completeProgress(true);
126127

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

Lines changed: 159 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ export default class ExportTaxonomies extends BaseClass {
4040
this.qs = { include_count: true, limit: this.taxonomiesConfig.limit || 100, skip: 0 };
4141

4242
this.applyQueryFilters(this.qs, 'taxonomies');
43-
this.exportConfig.context.module = 'taxonomies';
43+
this.exportConfig.context.module = MODULE_CONTEXTS.TAXONOMIES;
44+
this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.TAXONOMIES];
4445
this.localesFilePath = pResolve(
4546
sanitizePath(exportConfig.exportDir),
4647
sanitizePath(exportConfig.branchName || ''),
@@ -50,56 +51,149 @@ export default class ExportTaxonomies extends BaseClass {
5051
}
5152

5253
async start(): Promise<void> {
53-
log.debug('Starting export process for taxonomies...', this.exportConfig.context);
54-
55-
//create taxonomies folder
56-
this.taxonomiesFolderPath = pResolve(
57-
this.exportConfig.exportDir,
58-
this.exportConfig.branchName || '',
59-
this.taxonomiesConfig.dirName,
60-
);
61-
log.debug(`Taxonomies folder path: '${this.taxonomiesFolderPath}'`, this.exportConfig.context);
62-
63-
await fsUtil.makeDirectory(this.taxonomiesFolderPath);
64-
log.debug('Created taxonomies directory.', this.exportConfig.context);
54+
try {
55+
log.debug('Starting export process for taxonomies...', this.exportConfig.context);
6556

66-
const localesToExport = this.getLocalesToExport();
67-
log.debug(
68-
`Will attempt to export taxonomies for ${localesToExport.length} locale(s): ${localesToExport.join(', ')}`,
69-
this.exportConfig.context,
70-
);
57+
const totalCount = await this.initializeExport();
58+
if (totalCount === 0) {
59+
log.info(messageHandler.parse('TAXONOMY_NOT_FOUND'), this.exportConfig.context);
60+
return;
61+
}
7162

72-
if (localesToExport.length === 0) {
73-
log.warn('No locales found to export', this.exportConfig.context);
74-
return;
63+
const progress = this.setupProgress(totalCount);
64+
const localesToExport = this.getLocalesToExport();
65+
66+
if (localesToExport.length === 0) {
67+
log.warn('No locales found to export', this.exportConfig.context);
68+
this.completeProgress(true);
69+
return;
70+
}
71+
72+
// Start fetch process
73+
progress
74+
.startProcess(PROCESS_NAMES.FETCH_TAXONOMIES)
75+
.updateStatus(PROCESS_STATUS[PROCESS_NAMES.FETCH_TAXONOMIES].FETCHING, PROCESS_NAMES.FETCH_TAXONOMIES);
76+
77+
// Determine export strategy and fetch taxonomies
78+
await this.determineExportStrategy(this.exportConfig.master_locale?.code);
79+
await this.fetchAllTaxonomies(localesToExport);
80+
progress.completeProcess(PROCESS_NAMES.FETCH_TAXONOMIES, true);
81+
82+
// Export taxonomies with detailed information
83+
const actualCount = await this.exportAllTaxonomies(progress, localesToExport, totalCount);
84+
85+
// Write metadata and complete
86+
await this.writeTaxonomiesMetadata();
87+
log.success(messageHandler.parse('TAXONOMY_EXPORT_COMPLETE', actualCount), this.exportConfig.context);
88+
this.completeProgress(true);
89+
} catch (error) {
90+
handleAndLogError(error, { ...this.exportConfig.context });
91+
this.completeProgress(false, error?.message || 'Taxonomies export failed');
7592
}
93+
}
7694

77-
// Test locale-based export support with master locale
78-
const masterLocale = this.exportConfig.master_locale?.code;
79-
await this.fetchTaxonomies(masterLocale, true);
95+
/**
96+
* Initialize export setup (create directories, get initial count)
97+
*/
98+
private async initializeExport(): Promise<number> {
99+
return this.withLoadingSpinner('TAXONOMIES: Analyzing taxonomy structure...', async () => {
100+
this.taxonomiesFolderPath = pResolve(
101+
this.exportConfig.exportDir,
102+
this.exportConfig.branchName || '',
103+
this.taxonomiesConfig.dirName,
104+
);
105+
log.debug(`Taxonomies folder path: '${this.taxonomiesFolderPath}'`, this.exportConfig.context);
106+
107+
await fsUtil.makeDirectory(this.taxonomiesFolderPath);
108+
log.debug('Created taxonomies directory.', this.exportConfig.context);
109+
110+
// Get count first for progress tracking
111+
const countResponse = await this.stack
112+
.taxonomy()
113+
.query({ ...this.qs, include_count: true, limit: 1 })
114+
.find();
115+
return countResponse.count || 0;
116+
});
117+
}
118+
119+
/**
120+
* Setup progress manager with processes
121+
*/
122+
private setupProgress(totalCount: number): any {
123+
const progress = this.createNestedProgress(this.currentModuleName);
124+
// For fetch: count API calls, not individual taxonomies
125+
const fetchApiCallsCount = Math.ceil(totalCount / (this.qs.limit || 100));
126+
progress.addProcess(PROCESS_NAMES.FETCH_TAXONOMIES, fetchApiCallsCount);
127+
progress.addProcess(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, totalCount);
128+
return progress;
129+
}
80130

131+
/**
132+
* Determine if locale-based export is supported
133+
*/
134+
private async determineExportStrategy(masterLocale?: string): Promise<void> {
135+
await this.fetchTaxonomies(masterLocale, true);
81136
if (!this.isLocaleBasedExportSupported) {
137+
log.debug('Falling back to legacy export (non-localized)', this.exportConfig.context);
82138
this.taxonomies = {};
83139
this.taxonomiesByLocale = {};
84-
85-
// Fetch taxonomies without locale parameter
86-
await this.fetchTaxonomies();
87-
await this.exportTaxonomies();
88-
await this.writeTaxonomiesMetadata();
89140
} else {
90-
// Process all locales with locale-based export
91141
log.debug('Localization enabled, proceeding with locale-based export', this.exportConfig.context);
142+
}
143+
}
92144

145+
/**
146+
* Fetch all taxonomies based on export strategy
147+
*/
148+
private async fetchAllTaxonomies(localesToExport: string[]): Promise<void> {
149+
if (!this.isLocaleBasedExportSupported) {
150+
await this.fetchTaxonomies();
151+
} else {
93152
for (const localeCode of localesToExport) {
94153
await this.fetchTaxonomies(localeCode);
95-
await this.processLocaleExport(localeCode);
96154
}
155+
}
156+
}
97157

98-
await this.writeTaxonomiesMetadata();
158+
/**
159+
* Export all taxonomies with detailed information
160+
*/
161+
private async exportAllTaxonomies(progress: any, localesToExport: string[], totalCount: number): Promise<number> {
162+
const actualCount = Object.keys(this.taxonomies || {})?.length;
163+
log.debug(
164+
`Found ${actualCount} taxonomies to export (API reported ${totalCount})`,
165+
this.exportConfig.context,
166+
);
167+
168+
if (actualCount === 0) {
169+
log.info('No taxonomies found to export detailed information', this.exportConfig.context);
170+
return 0;
171+
}
172+
173+
// Update progress total if needed
174+
if (actualCount !== totalCount) {
175+
progress.updateProcessTotal(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, actualCount);
99176
}
100177

101-
this.completeProgressWithMessage();
178+
// Start export process
179+
progress
180+
.startProcess(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS)
181+
.updateStatus(
182+
PROCESS_STATUS[PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS].EXPORTING,
183+
PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS,
184+
);
102185

186+
// Export based on strategy
187+
if (!this.isLocaleBasedExportSupported) {
188+
await this.exportTaxonomies();
189+
} else {
190+
for (const localeCode of localesToExport) {
191+
await this.processLocaleExport(localeCode);
192+
}
193+
}
194+
195+
progress.completeProcess(PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS, true);
196+
return actualCount;
103197
}
104198

105199
/**
@@ -176,6 +270,17 @@ export default class ExportTaxonomies extends BaseClass {
176270
}
177271

178272
this.sanitizeTaxonomiesAttribs(items, localeCode);
273+
274+
// Track progress per API call (only for actual fetch, not locale support check)
275+
if (!checkLocaleSupport) {
276+
this.progressManager?.tick(
277+
true,
278+
`fetched ${items.length} taxonomies${localeInfo}`,
279+
null,
280+
PROCESS_NAMES.FETCH_TAXONOMIES,
281+
);
282+
}
283+
179284
skip += this.qs.limit || 100;
180285

181286
if (skip >= taxonomiesCount) {
@@ -261,14 +366,34 @@ export default class ExportTaxonomies extends BaseClass {
261366
}
262367

263368
const onSuccess = ({ response, uid }: any) => {
369+
const taxonomyName = this.taxonomies[uid]?.name;
264370
const filePath = pResolve(exportFolderPath, `${uid}.json`);
265371
log.debug(`Writing detailed taxonomy data to: ${filePath}`, this.exportConfig.context);
266372
fsUtil.writeFile(filePath, response);
267-
log.success(messageHandler.parse('TAXONOMY_EXPORT_SUCCESS', uid), this.exportConfig.context);
373+
374+
// Track progress for each exported taxonomy
375+
this.progressManager?.tick(
376+
true,
377+
`taxonomy: ${taxonomyName || uid}`,
378+
null,
379+
PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS,
380+
);
381+
382+
log.success(messageHandler.parse('TAXONOMY_EXPORT_SUCCESS', taxonomyName || uid), this.exportConfig.context);
268383
};
269384

270385
const onReject = ({ error, uid }: any) => {
386+
const taxonomyName = this.taxonomies[uid]?.name;
271387
log.debug(`Failed to export detailed data for taxonomy: ${uid}${localeInfo}`, this.exportConfig.context);
388+
389+
// Track failure
390+
this.progressManager?.tick(
391+
false,
392+
`taxonomy: ${taxonomyName || uid}`,
393+
error?.message || PROCESS_STATUS[PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS].FAILED,
394+
PROCESS_NAMES.EXPORT_TAXONOMIES_TERMS,
395+
);
396+
272397
handleAndLogError(error, { ...this.exportConfig.context, uid, ...(localeCode && { locale: localeCode }) });
273398
};
274399

0 commit comments

Comments
 (0)