Skip to content
Closed
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
6 changes: 4 additions & 2 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
fileignoreconfig:
- filename: package-lock.json
checksum: d3b93fad9630655f037e36b78fea3354f1a038988562254afdad0f6e54ece12d
checksum: f91639a9537cdf1d30a0050806005f142d57a352cf5e5c313594ddab136d3ff3
- filename: pnpm-lock.yaml
checksum: aa6177859aaa87caf2892e8034657fd485c3abe7c13a833fd28449a1d33fa950
checksum: 518861be793f410723ce3d4eb095f179ea0cea0908cdd79043a3879cabe47cae
- filename: packages/contentstack-import-setup/test/unit/backup-handler.test.ts
checksum: 0582d62b88834554cf12951c8690a73ef3ddbb78b82d2804d994cf4148e1ef93
- filename: packages/contentstack-import-setup/test/config.json
Expand Down Expand Up @@ -201,4 +201,6 @@ fileignoreconfig:
checksum: eca2702d1f7ed075b9b857964b9e56f69b16e4a31942423d6b1265e4bf398db5
- filename: packages/contentstack-import/test/unit/utils/logger.test.ts
checksum: 794e06e657a7337c8f094d6042fb04c779683f97b860efae14e075098d2af024
- filename: packages/contentstack-import-setup/src/import/modules/taxonomies.ts
checksum: 2863f93434671e8c8ec3534c10277036980e4ca167c2dec0e6587799cf766904
version: "1.0"
3,317 changes: 1,601 additions & 1,716 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/contentstack-import-setup/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@contentstack/cli-cm-import-setup",
"description": "Contentstack CLI plugin to setup the mappers and configurations for the import command",
"version": "1.6.0",
"version": "1.7.0",
"author": "Contentstack",
"bugs": "https://github.com/contentstack/cli/issues",
"dependencies": {
Expand Down
4 changes: 4 additions & 0 deletions packages/contentstack-import-setup/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const config: DefaultConfig = {
dirName: 'environments',
fileName: 'environments.json',
},
locales: {
dirName: 'locales',
fileName: 'locales.json',
},
extensions: {
dirName: 'extensions',
fileName: 'extensions.json',
Expand Down
164 changes: 140 additions & 24 deletions packages/contentstack-import-setup/src/import/modules/taxonomies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,41 @@ import { join } from 'path';
import omit from 'lodash/omit';
import isEmpty from 'lodash/isEmpty';

import { log, fsUtil } from '../../utils';
import { log, fsUtil, fileHelper } from '../../utils';
import { ImportConfig, ModuleClassParams, TaxonomyQueryParams } from '../../types';
import { sanitizePath } from '@contentstack/cli-utilities';

export default class TaxonomiesImportSetup {
private config: ImportConfig;
private taxonomiesFilePath: string;
private taxonomiesFolderPath: string;
private stackAPIClient: ModuleClassParams['stackAPIClient'];
private dependencies: ModuleClassParams['dependencies'];
private taxonomiesConfig: ImportConfig['modules']['taxonomies'];
private termsSuccessPath: string;
private taxSuccessPath: string;
private taxonomiesMapperDirPath: string;
private termsMapperDirPath: string;
private localesFilePath: string;
private isLocaleBasedStructure: boolean = false;
public taxonomiesMapper: Record<string, unknown> = {};
public termsMapper: Record<string, unknown> = {};

constructor({ config, stackAPIClient }: ModuleClassParams) {
this.config = config;
this.stackAPIClient = stackAPIClient;
this.taxonomiesFilePath = join(sanitizePath(this.config.contentDir), 'taxonomies', 'taxonomies.json');
this.taxonomiesFolderPath = join(sanitizePath(this.config.contentDir), 'taxonomies');
this.taxonomiesFilePath = join(this.taxonomiesFolderPath, 'taxonomies.json');
this.taxonomiesConfig = config.modules.taxonomies;
this.taxonomiesMapperDirPath = join(sanitizePath(this.config.backupDir), 'mapper', 'taxonomies');
this.taxSuccessPath = join(sanitizePath(this.taxonomiesMapperDirPath), 'success.json');
this.termsMapperDirPath = join(sanitizePath(this.taxonomiesMapperDirPath), 'terms');
this.termsSuccessPath = join(sanitizePath(this.termsMapperDirPath), 'success.json');
this.localesFilePath = join(
sanitizePath(this.config.contentDir),
config.modules.locales?.dirName || 'locales',
config.modules.locales?.fileName || 'locales.json',
);
this.taxonomiesMapper = {};
this.termsMapper = {};
}
Expand All @@ -41,21 +50,19 @@ export default class TaxonomiesImportSetup {
try {
const taxonomies: any = fsUtil.readFile(this.taxonomiesFilePath);
if (!isEmpty(taxonomies)) {
// 1. Detect locale-based structure
this.isLocaleBasedStructure = this.detectLocaleBasedStructure();

// 2. Create mapper directory
fsUtil.makeDirectory(this.taxonomiesMapperDirPath);
fsUtil.makeDirectory(this.termsMapperDirPath);

for (const taxonomy of Object.values(taxonomies) as any) {
let targetTaxonomy: any = await this.getTaxonomies(taxonomy);
if (!targetTaxonomy) {
log(this.config, `Taxonomies with uid '${taxonomy.uid}' not found in the stack!`, 'info');
continue;
}
targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);
this.taxonomiesMapper[taxonomy.uid] = targetTaxonomy;
const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy);
const sanitizedTerms = this.sanitizeTermsAttribs(terms);
this.termsMapper[taxonomy.uid] = sanitizedTerms;
if (this.isLocaleBasedStructure) {
log(this.config, 'Detected locale-based folder structure for taxonomies', 'info');
await this.setupTaxonomiesByLocale(taxonomies);
} else {
log(this.config, 'Using legacy folder structure for taxonomies', 'info');
await this.setupTaxonomiesLegacy(taxonomies);
}

if (this.taxonomiesMapper !== undefined && !isEmpty(this.taxonomiesMapper)) {
Expand All @@ -74,18 +81,121 @@ export default class TaxonomiesImportSetup {
}
}

/**
* Setup taxonomies using legacy format (root-level taxonomy files)
*/
async setupTaxonomiesLegacy(taxonomies: any): Promise<void> {
for (const taxonomy of Object.values(taxonomies) as any) {
let targetTaxonomy: any = await this.getTaxonomies(taxonomy);
if (!targetTaxonomy) {
log(this.config, `Taxonomies with uid '${taxonomy.uid}' not found in the stack!`, 'info');
continue;
}
targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);
this.taxonomiesMapper[taxonomy.uid] = targetTaxonomy;
const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy);
const sanitizedTerms = this.sanitizeTermsAttribs(terms);
this.termsMapper[taxonomy.uid] = sanitizedTerms;
}
}

/**
* Setup taxonomies using locale-based format (taxonomies organized by locale)
* For locale-based structure, we query the target stack for each taxonomy+locale combination
*/
async setupTaxonomiesByLocale(taxonomies: any): Promise<void> {
const locales = this.loadAvailableLocales();

for (const localeCode of Object.keys(locales)) {
log(this.config, `Processing taxonomies for locale: ${localeCode}`, 'info');

for (const taxonomy of Object.values(taxonomies) as any) {
// Query target stack for this taxonomy in this locale
let targetTaxonomy: any = await this.getTaxonomies(taxonomy, localeCode);
if (!targetTaxonomy) {
log(this.config, `Taxonomy '${taxonomy.uid}' not found in target stack for locale: ${localeCode}`, 'info');
continue;
}

targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);

// Store with composite key: taxonomyUID_locale
const mapperKey = `${taxonomy.uid}_${localeCode}`;
this.taxonomiesMapper[mapperKey] = targetTaxonomy;

// Get terms for this taxonomy+locale from target stack
const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy, localeCode);
const sanitizedTerms = this.sanitizeTermsAttribs(terms);
this.termsMapper[mapperKey] = sanitizedTerms;
}
}
}

/**
* Detect if locale-based folder structure exists
* @returns {boolean} true if locale-based structure detected, false otherwise
*/
detectLocaleBasedStructure(): boolean {
const masterLocaleCode = this.config.master_locale?.code || 'en-us';
const masterLocaleFolder = join(this.taxonomiesFolderPath, masterLocaleCode);

// Check if master locale folder exists (indicates new locale-based structure)
if (!fileHelper.fileExistsSync(masterLocaleFolder)) {
log(this.config, 'No locale-based folder structure detected', 'info');
return false;
}

log(this.config, 'Locale-based folder structure detected', 'info');
return true;
}

/**
* Load available locales from locales file
* @returns {Record<string, string>} Map of locale codes
*/
loadAvailableLocales(): Record<string, string> {
if (!fileHelper.fileExistsSync(this.localesFilePath)) {
log(this.config, 'No locales file found, using default locale', 'info');
return { [this.config.master_locale?.code || 'en-us']: this.config.master_locale?.code || 'en-us' };
}

try {
const localesData = fsUtil.readFile(this.localesFilePath, true) as Record<string, Record<string, any>>;
const locales: Record<string, string> = {};
locales[this.config.master_locale?.code] = this.config.master_locale?.code;

for (const [code, locale] of Object.entries(localesData)) {
if (locale?.code) {
locales[locale.code] = code;
}
}

log(this.config, `Loaded ${Object.keys(locales).length} locales from file`, 'info');
return locales;
} catch (error) {
log(this.config, 'Error loading locales file', 'error');
return { [this.config.master_locale?.code || 'en-us']: this.config.master_locale?.code || 'en-us' };
}
}

/**
* Retrieves the taxonomies based on the provided taxonomy UID.
*
* @param taxonomy - The UID of the taxonomy to retrieve.
* @param locale - Optional locale code to query taxonomy in specific locale
* @returns A promise that resolves to the retrieved taxonomies.
*/
async getTaxonomies(taxonomy: any): Promise<any> {
async getTaxonomies(taxonomy: any, locale?: string): Promise<any> {
const query: any = {};
if (locale) {
query.locale = locale;
}

return await this.stackAPIClient
.taxonomy(taxonomy.uid)
.fetch()
.fetch(query)
.then((data: any) => data)
.catch((err: any) => this.handleTaxonomyErrorMsg(err));
.catch((err: any) => this.handleTaxonomyErrorMsg(err, taxonomy.uid, locale));
}

/**
Expand All @@ -102,19 +212,22 @@ export default class TaxonomiesImportSetup {
* Retrieves all terms of a taxonomy.
*
* @param taxonomy - The taxonomy object.
* @param locale - Optional locale code to query terms in specific locale
* @param skip - The number of terms to skip (default: 0).
* @param terms - An array to store the retrieved terms (default: []).
* @returns A promise that resolves to an array of terms.
*/
async getAllTermsOfTaxonomy(taxonomy: any, skip = 0, terms: any[] = []): Promise<any> {
async getAllTermsOfTaxonomy(taxonomy: any, locale?: string, skip = 0, terms: any[] = []): Promise<any> {
const queryParams: TaxonomyQueryParams = {
include_count: true,
limit: 100,
skip,
depth: 0,
};

if (skip >= 0) queryParams['skip'] = skip || 0;
queryParams['depth'] = 0;
if (locale) {
queryParams.locale = locale;
}

await this.stackAPIClient
.taxonomy(taxonomy.uid)
Expand All @@ -124,10 +237,10 @@ export default class TaxonomiesImportSetup {
.then((data: any) => {
terms = terms.concat(data.items);
if (data.count >= skip + queryParams.limit) {
return this.getAllTermsOfTaxonomy(taxonomy, skip + 100, terms);
return this.getAllTermsOfTaxonomy(taxonomy, locale, skip + 100, terms);
}
})
.catch((err: any) => this.handleTaxonomyErrorMsg(err));
.catch((err: any) => this.handleTaxonomyErrorMsg(err, taxonomy.uid, locale));
return terms;
}

Expand All @@ -144,12 +257,15 @@ export default class TaxonomiesImportSetup {
return terms;
}

handleTaxonomyErrorMsg(err: any) {
handleTaxonomyErrorMsg(err: any, taxonomyUid?: string, locale?: string) {
const context = locale ? ` for locale: ${locale}` : '';
const taxInfo = taxonomyUid ? ` (${taxonomyUid}${context})` : '';

if (err?.errorMessage || err?.message) {
const errorMsg = err?.errorMessage || err?.errors?.taxonomy || err?.errors?.term || err?.message;
log(this.config, errorMsg, 'error');
log(this.config, `${errorMsg}${taxInfo}`, 'error');
} else {
log(this.config, 'Error fetching taxonomy data!', 'error');
log(this.config, `Error fetching taxonomy data${taxInfo}!`, 'error');
log(this.config, err, 'error');
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export default interface DefaultConfig {
fileName: string;
dependencies?: Modules[];
};
locales: {
dirName: string;
fileName: string;
dependencies?: Modules[];
};
extensions: {
dirName: string;
fileName: string;
Expand Down
1 change: 1 addition & 0 deletions packages/contentstack-import-setup/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,5 @@ export type TaxonomyQueryParams = {
limit: number;
skip: number;
depth?: number;
locale?: string;
};
Loading
Loading