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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/contentstack-export/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@contentstack/cli-cm-export",
"description": "Contentstack CLI plugin to export content from stack",
"version": "1.21.0",
"version": "1.21.1",
"author": "Contentstack",
"bugs": "https://github.com/contentstack/cli/issues",
"dependencies": {
Expand Down
40 changes: 33 additions & 7 deletions packages/contentstack-export/src/export/modules/taxonomies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ export default class ExportTaxonomies extends BaseClass {
await this.fetchTaxonomies(masterLocale, true);

if (!this.isLocaleBasedExportSupported) {
log.debug('Localization disabled, falling back to legacy export method', this.exportConfig.context);
this.taxonomies = {};
this.taxonomiesByLocale = {};

// Fetch taxonomies without locale parameter
await this.fetchTaxonomies();
await this.exportTaxonomies();
await this.writeTaxonomiesMetadata();
} else {
Expand Down Expand Up @@ -174,15 +178,26 @@ export default class ExportTaxonomies extends BaseClass {
log.debug(`Completed fetching all taxonomies ${localeInfo}`, this.exportConfig.context);
break;
}
} catch (error) {
} catch (error: any) {
log.debug(`Error fetching taxonomies ${localeInfo}`, this.exportConfig.context);
handleAndLogError(error, {
...this.exportConfig.context,
...(localeCode && { locale: localeCode }),
});
if (checkLocaleSupport) {

if (checkLocaleSupport && this.isLocalePlanLimitationError(error)) {
log.debug(
'Taxonomy localization is not included in your plan. Falling back to non-localized export.',
this.exportConfig.context,
);
this.isLocaleBasedExportSupported = false;
} else if (checkLocaleSupport) {
log.debug('Locale-based taxonomy export not supported, will use legacy method', this.exportConfig.context);
this.isLocaleBasedExportSupported = false;
} else {
// Log actual errors during normal fetch (not locale check)
handleAndLogError(error, {
...this.exportConfig.context,
...(localeCode && { locale: localeCode }),
});
}

// Break to avoid infinite retry loop on errors
break;
}
Expand Down Expand Up @@ -312,4 +327,15 @@ export default class ExportTaxonomies extends BaseClass {

return localesToExport;
}

private isLocalePlanLimitationError(error: any): boolean {
return (
error?.status === 403 &&
error?.errors?.taxonomies?.some(
(msg: string) =>
msg.toLowerCase().includes('taxonomy localization') &&
msg.toLowerCase().includes('not included in your plan'),
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,14 @@ describe('ExportTaxonomies', () => {
});

it('should disable locale-based export on API error when checkLocaleSupport is true', async () => {
// Create a structured API error (not a plan limitation error)
const apiError: any = new Error('API Error');
apiError.status = 500;
apiError.errors = { general: ['Internal server error'] };

mockStackClient.taxonomy.returns({
query: sinon.stub().returns({
find: sinon.stub().rejects(new Error('API Error'))
find: sinon.stub().rejects(apiError)
})
});

Expand All @@ -546,6 +551,27 @@ describe('ExportTaxonomies', () => {
// Should disable locale-based export on error
expect(exportTaxonomies.isLocaleBasedExportSupported).to.be.false;
});

it('should handle taxonomy localization plan limitation error gracefully', async () => {
// Create the exact 403 error from the plan limitation
const planLimitationError: any = new Error('Forbidden');
planLimitationError.status = 403;
planLimitationError.statusText = 'Forbidden';
planLimitationError.errors = {
taxonomies: ['Taxonomy localization is not included in your plan. Please contact the support@contentstack.com team for assistance.']
};

mockStackClient.taxonomy.returns({
query: sinon.stub().returns({
find: sinon.stub().rejects(planLimitationError)
})
});

await exportTaxonomies.fetchTaxonomies('en-us', true);

// Should disable locale-based export and not throw error
expect(exportTaxonomies.isLocaleBasedExportSupported).to.be.false;
});
});

describe('exportTaxonomies() method - locale-based export', () => {
Expand Down Expand Up @@ -586,6 +612,37 @@ describe('ExportTaxonomies', () => {
mockGetLocales.restore();
});

it('should clear taxonomies and re-fetch when falling back to legacy export', async () => {
let fetchCallCount = 0;
const mockFetchTaxonomies = sinon.stub(exportTaxonomies, 'fetchTaxonomies').callsFake(async (locale, checkSupport) => {
fetchCallCount++;
if (checkSupport) {
// First call fails locale check
exportTaxonomies.isLocaleBasedExportSupported = false;
exportTaxonomies.taxonomies = { 'partial-data': { uid: 'partial-data' } }; // Simulate partial data
} else {
// Second call should have cleared data
expect(exportTaxonomies.taxonomies).to.deep.equal({});
}
});
const mockExportTaxonomies = sinon.stub(exportTaxonomies, 'exportTaxonomies').resolves();
const mockWriteMetadata = sinon.stub(exportTaxonomies, 'writeTaxonomiesMetadata').resolves();
const mockGetLocales = sinon.stub(exportTaxonomies, 'getLocalesToExport').returns(['en-us']);

await exportTaxonomies.start();

// Should call fetchTaxonomies twice: once for check, once for legacy
expect(fetchCallCount).to.equal(2);
// First call with locale, second without
expect(mockFetchTaxonomies.firstCall.args).to.deep.equal(['en-us', true]);
expect(mockFetchTaxonomies.secondCall.args).to.deep.equal([]);

mockFetchTaxonomies.restore();
mockExportTaxonomies.restore();
mockWriteMetadata.restore();
mockGetLocales.restore();
});

it('should use locale-based export when supported', async () => {
const mockFetchTaxonomies = sinon.stub(exportTaxonomies, 'fetchTaxonomies').callsFake(async (locale, checkSupport) => {
if (checkSupport) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ describe('Logger', () => {
});

it('should handle very long messages', async () => {
const longMessage = 'A'.repeat(10000);
const longMessage = 'A'.repeat(10);

// Should complete without throwing
await loggerModule.log(mockExportConfig, longMessage, 'info');
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@contentstack/cli-cm-branches": "~1.6.0",
"@contentstack/cli-cm-bulk-publish": "~1.10.2",
"@contentstack/cli-cm-clone": "~1.17.0",
"@contentstack/cli-cm-export": "~1.21.0",
"@contentstack/cli-cm-export": "~1.21.1",
"@contentstack/cli-cm-export-to-csv": "~1.10.0",
"@contentstack/cli-cm-import": "~1.29.0",
"@contentstack/cli-cm-import-setup": "~1.7.0",
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading