Skip to content

Commit 5613014

Browse files
Merge pull request #2232 from contentstack/fix/update-version-12-11
merge staging changes
2 parents 84d9f61 + b6834f0 commit 5613014

File tree

9 files changed

+425
-256
lines changed

9 files changed

+425
-256
lines changed

.talismanrc

Lines changed: 205 additions & 225 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/contentstack-import-setup/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ $ npm install -g @contentstack/cli-cm-import-setup
4747
$ csdx COMMAND
4848
running command...
4949
$ csdx (--version)
50-
@contentstack/cli-cm-import-setup/1.6.1 darwin-arm64 node-v22.14.0
50+
@contentstack/cli-cm-import-setup/1.7.0 darwin-arm64 node-v22.13.1
5151
$ csdx --help [COMMAND]
5252
USAGE
5353
$ csdx COMMAND

packages/contentstack-import-setup/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@contentstack/cli-cm-import-setup",
33
"description": "Contentstack CLI plugin to setup the mappers and configurations for the import command",
4-
"version": "1.6.1",
4+
"version": "1.7.0",
55
"author": "Contentstack",
66
"bugs": "https://github.com/contentstack/cli/issues",
77
"dependencies": {

packages/contentstack-import-setup/src/config/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const config: DefaultConfig = {
2222
fileName: 'custom-roles.json',
2323
dependencies: ['environments', 'entries'],
2424
},
25+
locales: {
26+
dirName: 'locales',
27+
fileName: 'locales.json',
28+
dependencies: [],
29+
},
2530
environments: {
2631
dirName: 'environments',
2732
fileName: 'environments.json',
@@ -43,7 +48,7 @@ const config: DefaultConfig = {
4348
entries: {
4449
dirName: 'entries',
4550
fileName: 'entries.json',
46-
dependencies: ['assets', 'marketplace-apps', 'taxonomies'],
51+
dependencies: ['assets', 'extensions', 'marketplace-apps', 'taxonomies'],
4752
},
4853
'global-fields': {
4954
dirName: 'global_fields',

packages/contentstack-import-setup/src/import/modules/taxonomies.ts

Lines changed: 202 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,48 @@ import { join } from 'path';
22
import omit from 'lodash/omit';
33
import isEmpty from 'lodash/isEmpty';
44

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

99
export default class TaxonomiesImportSetup {
1010
private config: ImportConfig;
1111
private taxonomiesFilePath: string;
12+
private taxonomiesFolderPath: string;
1213
private stackAPIClient: ModuleClassParams['stackAPIClient'];
1314
private dependencies: ModuleClassParams['dependencies'];
1415
private taxonomiesConfig: ImportConfig['modules']['taxonomies'];
1516
private termsSuccessPath: string;
1617
private taxSuccessPath: string;
1718
private taxonomiesMapperDirPath: string;
1819
private termsMapperDirPath: string;
20+
private localesFilePath: string;
21+
private isLocaleBasedStructure: boolean = false;
1922
public taxonomiesMapper: Record<string, unknown> = {};
2023
public termsMapper: Record<string, unknown> = {};
24+
public masterLocaleFilePath: string;
2125

2226
constructor({ config, stackAPIClient }: ModuleClassParams) {
2327
this.config = config;
2428
this.stackAPIClient = stackAPIClient;
25-
this.taxonomiesFilePath = join(sanitizePath(this.config.contentDir), 'taxonomies', 'taxonomies.json');
29+
this.taxonomiesFolderPath = join(sanitizePath(this.config.contentDir), 'taxonomies');
30+
this.taxonomiesFilePath = join(this.taxonomiesFolderPath, 'taxonomies.json');
2631
this.taxonomiesConfig = config.modules.taxonomies;
2732
this.taxonomiesMapperDirPath = join(sanitizePath(this.config.backupDir), 'mapper', 'taxonomies');
2833
this.taxSuccessPath = join(sanitizePath(this.taxonomiesMapperDirPath), 'success.json');
2934
this.termsMapperDirPath = join(sanitizePath(this.taxonomiesMapperDirPath), 'terms');
3035
this.termsSuccessPath = join(sanitizePath(this.termsMapperDirPath), 'success.json');
36+
this.localesFilePath = join(
37+
sanitizePath(this.config.contentDir),
38+
config.modules.locales?.dirName || 'locales',
39+
config.modules.locales?.fileName || 'locales.json',
40+
);
41+
this.masterLocaleFilePath = join(
42+
sanitizePath(this.config.contentDir),
43+
config.modules.locales?.dirName || 'locales',
44+
'master-locale.json',
45+
);
46+
3147
this.taxonomiesMapper = {};
3248
this.termsMapper = {};
3349
}
@@ -41,21 +57,19 @@ export default class TaxonomiesImportSetup {
4157
try {
4258
const taxonomies: any = fsUtil.readFile(this.taxonomiesFilePath);
4359
if (!isEmpty(taxonomies)) {
60+
// 1. Detect locale-based structure
61+
this.isLocaleBasedStructure = this.detectLocaleBasedStructure();
62+
4463
// 2. Create mapper directory
4564
fsUtil.makeDirectory(this.taxonomiesMapperDirPath);
4665
fsUtil.makeDirectory(this.termsMapperDirPath);
4766

48-
for (const taxonomy of Object.values(taxonomies) as any) {
49-
let targetTaxonomy: any = await this.getTaxonomies(taxonomy);
50-
if (!targetTaxonomy) {
51-
log(this.config, `Taxonomies with uid '${taxonomy.uid}' not found in the stack!`, 'info');
52-
continue;
53-
}
54-
targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);
55-
this.taxonomiesMapper[taxonomy.uid] = targetTaxonomy;
56-
const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy);
57-
const sanitizedTerms = this.sanitizeTermsAttribs(terms);
58-
this.termsMapper[taxonomy.uid] = sanitizedTerms;
67+
if (this.isLocaleBasedStructure) {
68+
log(this.config, 'Detected locale-based folder structure for taxonomies', 'info');
69+
await this.setupTaxonomiesByLocale(taxonomies);
70+
} else {
71+
log(this.config, 'Using legacy folder structure for taxonomies', 'info');
72+
await this.setupTaxonomiesLegacy(taxonomies);
5973
}
6074

6175
if (this.taxonomiesMapper !== undefined && !isEmpty(this.taxonomiesMapper)) {
@@ -74,18 +88,176 @@ export default class TaxonomiesImportSetup {
7488
}
7589
}
7690

91+
/**
92+
* Setup taxonomies using legacy format (root-level taxonomy files)
93+
*/
94+
async setupTaxonomiesLegacy(taxonomies: any): Promise<void> {
95+
for (const taxonomy of Object.values(taxonomies) as any) {
96+
let targetTaxonomy: any = await this.getTaxonomies(taxonomy);
97+
if (!targetTaxonomy) {
98+
log(this.config, `Taxonomies with uid '${taxonomy.uid}' not found in the stack!`, 'info');
99+
continue;
100+
}
101+
targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);
102+
this.taxonomiesMapper[taxonomy.uid] = targetTaxonomy;
103+
const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy);
104+
if (Array.isArray(terms) && terms.length > 0) {
105+
log(this.config, `Terms found for taxonomy '${taxonomy.uid}', processing...`, 'info');
106+
const sanitizedTerms = this.sanitizeTermsAttribs(terms);
107+
this.termsMapper[taxonomy.uid] = sanitizedTerms;
108+
} else {
109+
log(this.config, `No terms found for taxonomy '${taxonomy.uid}', skipping...`, 'info');
110+
}
111+
}
112+
}
113+
114+
/**
115+
* Setup taxonomies using locale-based format (taxonomies organized by locale)
116+
* For locale-based structure, we query the target stack for each taxonomy+locale combination
117+
*/
118+
async setupTaxonomiesByLocale(taxonomies: any): Promise<void> {
119+
const locales = this.loadAvailableLocales();
120+
121+
for (const localeCode of Object.keys(locales)) {
122+
log(this.config, `Processing taxonomies for locale: ${localeCode}`, 'info');
123+
124+
for (const taxonomy of Object.values(taxonomies) as any) {
125+
// Query target stack for this taxonomy in this locale
126+
let targetTaxonomy: any = await this.getTaxonomies(taxonomy, localeCode);
127+
if (!targetTaxonomy) {
128+
log(this.config, `Taxonomy '${taxonomy.uid}' not found in target stack for locale: ${localeCode}`, 'info');
129+
continue;
130+
}
131+
132+
targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);
133+
134+
// Store with composite key: taxonomyUID_locale
135+
// const mapperKey = `${taxonomy.uid}_${localeCode}`; // TODO: Unsure about this required or not
136+
this.taxonomiesMapper[taxonomy.uid] = targetTaxonomy;
137+
const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy, localeCode);
138+
if (Array.isArray(terms) && terms.length > 0) {
139+
log(
140+
this.config,
141+
`Terms found for taxonomy '${taxonomy.uid} for locale: ${localeCode}', processing...`,
142+
'info',
143+
);
144+
const sanitizedTerms = this.sanitizeTermsAttribs(terms);
145+
this.termsMapper[taxonomy.uid] = sanitizedTerms;
146+
} else {
147+
log(
148+
this.config,
149+
`No terms found for taxonomy '${taxonomy.uid} for locale: ${localeCode}', skipping...`,
150+
'info',
151+
);
152+
}
153+
}
154+
}
155+
}
156+
157+
/**
158+
* Detect if locale-based folder structure exists
159+
* @returns {boolean} true if locale-based structure detected, false otherwise
160+
*/
161+
detectLocaleBasedStructure(): boolean {
162+
const masterLocaleCode = this.getMasterLocaleCode();
163+
const masterLocaleFolder = join(this.taxonomiesFolderPath, masterLocaleCode);
164+
165+
// Check if master locale folder exists (indicates new locale-based structure)
166+
if (!fileHelper.fileExistsSync(masterLocaleFolder)) {
167+
log(this.config, 'No locale-based folder structure detected', 'info');
168+
return false;
169+
}
170+
171+
log(this.config, 'Locale-based folder structure detected', 'info');
172+
return true;
173+
}
174+
175+
/**
176+
* Get the master locale code
177+
* First tries to read from master-locale.json, then falls back to config, then 'en-us'
178+
* @returns {string} The master locale code
179+
*/
180+
getMasterLocaleCode(): string {
181+
// Try to read from master-locale.json file
182+
if (fileHelper.fileExistsSync(this.masterLocaleFilePath)) {
183+
try {
184+
const masterLocaleData = fsUtil.readFile(this.masterLocaleFilePath, true) as Record<
185+
string,
186+
Record<string, any>
187+
>;
188+
// The file contains an object with UID as key, extract the code
189+
const firstLocale = Object.values(masterLocaleData)[0];
190+
if (firstLocale?.code) {
191+
log(this.config, `Master locale loaded from file: ${firstLocale.code}`, 'info');
192+
return firstLocale.code;
193+
}
194+
} catch (error) {
195+
log(this.config, 'Error reading master-locale.json, using fallback', 'warn');
196+
}
197+
}
198+
199+
// Fallback to config or default
200+
const fallbackCode = this.config.master_locale?.code || 'en-us';
201+
log(this.config, `Using fallback master locale: ${fallbackCode}`, 'info');
202+
return fallbackCode;
203+
}
204+
205+
/**
206+
* Load available locales from locales file
207+
* @returns {Record<string, string>} Map of locale codes
208+
*/
209+
loadAvailableLocales(): Record<string, string> {
210+
const locales: Record<string, string> = {};
211+
212+
// First, get the master locale
213+
const masterLocaleCode = this.getMasterLocaleCode();
214+
locales[masterLocaleCode] = masterLocaleCode;
215+
216+
// Then load additional locales from locales.json if it exists
217+
if (!fileHelper.fileExistsSync(this.localesFilePath)) {
218+
log(this.config, 'No locales file found, using only master locale', 'info');
219+
return locales;
220+
}
221+
222+
try {
223+
const localesData = fsUtil.readFile(this.localesFilePath, true) as Record<string, Record<string, any>>;
224+
225+
for (const [uid, locale] of Object.entries(localesData)) {
226+
if (locale?.code) {
227+
locales[locale.code] = locale.code;
228+
}
229+
}
230+
231+
log(
232+
this.config,
233+
`Loaded ${Object.keys(locales).length} locales (1 master + ${Object.keys(locales).length - 1} additional)`,
234+
'info',
235+
);
236+
return locales;
237+
} catch (error) {
238+
log(this.config, 'Error loading locales file, using only master locale', 'error');
239+
return locales;
240+
}
241+
}
242+
77243
/**
78244
* Retrieves the taxonomies based on the provided taxonomy UID.
79245
*
80246
* @param taxonomy - The UID of the taxonomy to retrieve.
247+
* @param locale - Optional locale code to query taxonomy in specific locale
81248
* @returns A promise that resolves to the retrieved taxonomies.
82249
*/
83-
async getTaxonomies(taxonomy: any): Promise<any> {
250+
async getTaxonomies(taxonomy: any, locale?: string): Promise<any> {
251+
const query: any = {};
252+
if (locale) {
253+
query.locale = locale;
254+
}
255+
84256
return await this.stackAPIClient
85257
.taxonomy(taxonomy.uid)
86-
.fetch()
258+
.fetch(query)
87259
.then((data: any) => data)
88-
.catch((err: any) => this.handleTaxonomyErrorMsg(err));
260+
.catch((err: any) => this.handleTaxonomyErrorMsg(err, taxonomy.uid, locale));
89261
}
90262

91263
/**
@@ -102,19 +274,22 @@ export default class TaxonomiesImportSetup {
102274
* Retrieves all terms of a taxonomy.
103275
*
104276
* @param taxonomy - The taxonomy object.
277+
* @param locale - Optional locale code to query terms in specific locale
105278
* @param skip - The number of terms to skip (default: 0).
106279
* @param terms - An array to store the retrieved terms (default: []).
107280
* @returns A promise that resolves to an array of terms.
108281
*/
109-
async getAllTermsOfTaxonomy(taxonomy: any, skip = 0, terms: any[] = []): Promise<any> {
282+
async getAllTermsOfTaxonomy(taxonomy: any, locale?: string, skip = 0, terms: any[] = []): Promise<any> {
110283
const queryParams: TaxonomyQueryParams = {
111284
include_count: true,
112285
limit: 100,
113286
skip,
287+
depth: 0,
114288
};
115289

116-
if (skip >= 0) queryParams['skip'] = skip || 0;
117-
queryParams['depth'] = 0;
290+
if (locale) {
291+
queryParams.locale = locale;
292+
}
118293

119294
await this.stackAPIClient
120295
.taxonomy(taxonomy.uid)
@@ -124,10 +299,10 @@ export default class TaxonomiesImportSetup {
124299
.then((data: any) => {
125300
terms = terms.concat(data.items);
126301
if (data.count >= skip + queryParams.limit) {
127-
return this.getAllTermsOfTaxonomy(taxonomy, skip + 100, terms);
302+
return this.getAllTermsOfTaxonomy(taxonomy, locale, skip + 100, terms);
128303
}
129304
})
130-
.catch((err: any) => this.handleTaxonomyErrorMsg(err));
305+
.catch((err: any) => this.handleTaxonomyErrorMsg(err, taxonomy.uid, locale));
131306
return terms;
132307
}
133308

@@ -144,12 +319,15 @@ export default class TaxonomiesImportSetup {
144319
return terms;
145320
}
146321

147-
handleTaxonomyErrorMsg(err: any) {
322+
handleTaxonomyErrorMsg(err: any, taxonomyUid?: string, locale?: string) {
323+
const context = locale ? ` for locale: ${locale}` : '';
324+
const taxInfo = taxonomyUid ? ` (${taxonomyUid}${context})` : '';
325+
148326
if (err?.errorMessage || err?.message) {
149327
const errorMsg = err?.errorMessage || err?.errors?.taxonomy || err?.errors?.term || err?.message;
150-
log(this.config, errorMsg, 'error');
328+
log(this.config, `${errorMsg}${taxInfo}`, 'error');
151329
} else {
152-
log(this.config, 'Error fetching taxonomy data!', 'error');
330+
log(this.config, `Error fetching taxonomy data${taxInfo}!`, 'error');
153331
log(this.config, err, 'error');
154332
}
155333
}

packages/contentstack-import-setup/src/types/default-config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export default interface DefaultConfig {
1414
fileName: string;
1515
dependencies?: Modules[];
1616
};
17+
locales: {
18+
dirName: string;
19+
fileName: string;
20+
dependencies?: Modules[];
21+
};
1722
extensions: {
1823
dirName: string;
1924
fileName: string;

packages/contentstack-import-setup/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,5 @@ export type TaxonomyQueryParams = {
150150
limit: number;
151151
skip: number;
152152
depth?: number;
153+
locale?: string;
153154
};

0 commit comments

Comments
 (0)