Skip to content

Commit 6d8b11c

Browse files
authored
Merge pull request #231 from contentstack/fix/DX-8939
change master locale fix, change master locale script support for loc…
2 parents 078fbe0 + 0ff8360 commit 6d8b11c

2 files changed

Lines changed: 145 additions & 25 deletions

File tree

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,35 @@ export default class ImportTaxonomies extends BaseClass {
8484
log.debug('Using legacy folder structure for taxonomies', this.importConfig.context);
8585
}
8686

87-
//Step 5 create taxonomy & related terms success & failure file
87+
// Step 5: Flag taxonomies that were never processed (no matching export data
88+
// found in any locale/legacy path), so they don't silently disappear.
89+
for (const taxonomyUID of Object.keys(this.taxonomies || {})) {
90+
if (!(taxonomyUID in this.createdTaxonomies) && !(taxonomyUID in this.failedTaxonomies)) {
91+
log.error(
92+
`Taxonomy '${taxonomyUID}' could not be imported: no matching export data found`,
93+
this.importConfig.context,
94+
);
95+
this.failedTaxonomies[taxonomyUID] = this.taxonomies[taxonomyUID];
96+
}
97+
}
98+
99+
//Step 6 create taxonomy & related terms success & failure file
88100
log.debug('Creating success and failure files...', this.importConfig.context);
89101
this.createSuccessAndFailedFile();
90102

91-
log.success('Taxonomies imported successfully!', this.importConfig.context);
103+
const createdCount = Object.keys(this.createdTaxonomies).length;
104+
const failedCount = Object.keys(this.failedTaxonomies).length;
105+
106+
if (failedCount > 0) {
107+
log.error(
108+
`Taxonomies import completed with errors: ${createdCount} succeeded, ${failedCount} failed`,
109+
this.importConfig.context,
110+
);
111+
} else if (createdCount > 0) {
112+
log.success('Taxonomies imported successfully!', this.importConfig.context);
113+
} else {
114+
log.info('No taxonomies to import.', this.importConfig.context);
115+
}
92116
}
93117

94118
/**
@@ -367,13 +391,22 @@ export default class ImportTaxonomies extends BaseClass {
367391
const masterLocaleFolder = join(this.taxonomiesFolderPath, masterLocaleCode);
368392

369393
// Check if master locale folder exists (indicates new locale-based structure)
370-
if (!fileHelper.fileExistsSync(masterLocaleFolder)) {
371-
log.debug('No locale-based folder structure detected', this.importConfig.context);
372-
return false;
394+
if (fileHelper.fileExistsSync(masterLocaleFolder)) {
395+
log.debug('Locale-based folder structure detected', this.importConfig.context);
396+
return true;
373397
}
374398

375-
log.debug('Locale-based folder structure detected', this.importConfig.context);
399+
// The master locale may not have any localized taxonomies (so its folder was
400+
// never exported), but other locales can still use the locale-based structure.
401+
const locales = this.loadAvailableLocales();
402+
for (const localeCode of Object.keys(locales)) {
403+
if (fileHelper.fileExistsSync(join(this.taxonomiesFolderPath, localeCode))) {
404+
log.debug('Locale-based folder structure detected', this.importConfig.context);
405+
return true;
406+
}
407+
}
376408

377-
return true;
409+
log.debug('No locale-based folder structure detected', this.importConfig.context);
410+
return false;
378411
}
379412
}

packages/contentstack-migration/examples/change-master-locale/02-change-master-locale-new-file-structure.js

Lines changed: 105 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ module.exports = async ({ migration, config }) => {
2525
}
2626

2727
async function tailorData() {
28-
let locales = await fs.readFile(pathValidator(path.resolve(sanitizePath(config.data_dir), 'locales/locales.json')), 'utf-8');
28+
let locales = await fs.readFile(
29+
pathValidator(path.resolve(sanitizePath(config.data_dir), 'locales/locales.json')),
30+
'utf-8',
31+
);
2932
let masterLocale = await fs.readFile(
3033
pathValidator(path.resolve(sanitizePath(config.data_dir), 'locales/master-locale.json')),
3134
'utf-8',
@@ -34,12 +37,14 @@ module.exports = async ({ migration, config }) => {
3437
if (masterLocale) {
3538
masterLocale = JSON.parse(masterLocale);
3639
masterLocale = Object.values(masterLocale);
37-
masterLocale = masterLocale[0]
38-
40+
masterLocale = masterLocale[0];
41+
3942
// Validate that we have a valid master locale code
40-
if (!masterLocale) {
43+
if (!masterLocale || !masterLocale.code) {
4144
throw new Error('Unable to determine master locale code from master-locale.json');
4245
}
46+
47+
masterLocale = masterLocale.code;
4348
}
4449
locales = JSON.parse(locales);
4550
let id = crypto.randomBytes(8).toString('hex');
@@ -60,6 +65,7 @@ module.exports = async ({ migration, config }) => {
6065
locales[id].fallback_locale = config.target_locale;
6166

6267
await handleEntries(masterLocale);
68+
await handleTaxonomies(masterLocale);
6369
await fs.writeFile(
6470
pathValidator(path.resolve(sanitizePath(config.data_dir), 'locales/locales.json')),
6571
JSON.stringify(locales),
@@ -84,21 +90,23 @@ module.exports = async ({ migration, config }) => {
8490
let sourceMasterLocaleEntries, targetMasterLocaleEntries;
8591

8692
// Check if index.json exists (if no entries, index.json won't be created)
87-
const indexFilePath = pathValidator(path.resolve(sanitizePath(config.data_dir), sanitizePath(`entries/${contentType}/${masterLocale}/index.json`)));
93+
const indexFilePath = pathValidator(
94+
path.resolve(
95+
sanitizePath(config.data_dir),
96+
sanitizePath(`entries/${contentType}/${masterLocale}/index.json`),
97+
),
98+
);
8899
if (!existsSync(indexFilePath)) {
89100
console.log(`Skipping ${contentType} - no index.json found (likely no entries)`);
90101
continue;
91102
}
92103

93-
sourceMasterLocaleEntries = await fs.readFile(
94-
indexFilePath,
95-
{ encoding: 'utf8' },
96-
);
104+
sourceMasterLocaleEntries = await fs.readFile(indexFilePath, { encoding: 'utf8' });
97105

98106
// Parse the index.json to get the entries file name
99107
const indexData = JSON.parse(sourceMasterLocaleEntries);
100108
const entriesFileName = Object.values(indexData)[0];
101-
109+
102110
// Check if we have a valid entries file name
103111
if (!entriesFileName) {
104112
console.log(`Skipping ${contentType} - no entries file found in index.json`);
@@ -112,10 +120,7 @@ module.exports = async ({ migration, config }) => {
112120
),
113121
);
114122

115-
sourceMasterLocaleEntries = await fs.readFile(
116-
entriesFilePath,
117-
{ encoding: 'utf8' },
118-
);
123+
sourceMasterLocaleEntries = await fs.readFile(entriesFilePath, { encoding: 'utf8' });
119124
sourceMasterLocaleEntries = JSON.parse(sourceMasterLocaleEntries);
120125
if (
121126
existsSync(pathValidator(path.resolve(config.data_dir, `entries/${contentType}/${config.target_locale}`)))
@@ -127,7 +132,7 @@ module.exports = async ({ migration, config }) => {
127132
if (targetMasterLocaleEntries) {
128133
const targetIndexData = JSON.parse(targetMasterLocaleEntries);
129134
const targetEntriesFileName = Object.values(targetIndexData)[0];
130-
135+
131136
if (targetEntriesFileName) {
132137
targetMasterLocaleEntries = await fs.readFile(
133138
pathValidator(
@@ -152,7 +157,7 @@ module.exports = async ({ migration, config }) => {
152157
Object.keys(sourceMasterLocaleEntries).forEach((uid) => {
153158
if (!targetMasterLocaleEntries[uid]) {
154159
targetMasterLocaleEntries[uid] = JSON.parse(JSON.stringify(sourceMasterLocaleEntries[uid]));
155-
delete targetMasterLocaleEntries[uid]['publish_details'];
160+
targetMasterLocaleEntries[uid]['publish_details'] = [];
156161
targetMasterLocaleEntries[uid].locale = config.target_locale;
157162
}
158163
});
@@ -164,10 +169,10 @@ module.exports = async ({ migration, config }) => {
164169
pathValidator(path.resolve(config.data_dir, `entries/${contentType}/${config.target_locale}/index.json`)),
165170
{ encoding: 'utf8', flag: 'a+' },
166171
);
167-
172+
168173
const existingIndexData = JSON.parse(exsitingTargetMasterLocalEntries);
169174
const existingEntriesFileName = Object.values(existingIndexData)[0];
170-
175+
171176
if (existingEntriesFileName) {
172177
await fs.writeFile(
173178
pathValidator(
@@ -194,6 +199,88 @@ module.exports = async ({ migration, config }) => {
194199
}
195200
}
196201

202+
async function handleTaxonomies(masterLocale) {
203+
const taxonomiesDirPath = pathValidator(path.resolve(sanitizePath(config.data_dir), 'taxonomies'));
204+
const taxonomiesIndexPath = pathValidator(path.resolve(taxonomiesDirPath, 'taxonomies.json'));
205+
206+
if (!existsSync(taxonomiesIndexPath)) {
207+
console.log('Skipping taxonomies - no taxonomies.json found');
208+
return;
209+
}
210+
211+
let taxonomiesIndex = await fs.readFile(taxonomiesIndexPath, { encoding: 'utf8' });
212+
taxonomiesIndex = JSON.parse(taxonomiesIndex);
213+
214+
const targetLocaleDirPath = pathValidator(path.resolve(taxonomiesDirPath, sanitizePath(config.target_locale)));
215+
216+
for (const taxonomyUid of Object.keys(taxonomiesIndex)) {
217+
const fileName = `${sanitizePath(taxonomyUid)}.json`;
218+
const targetFilePath = pathValidator(path.resolve(targetLocaleDirPath, fileName));
219+
220+
// Prefer the old master locale's taxonomy data, then the locale recorded at export time,
221+
// then fall back to any other locale that has it
222+
const exportedLocale = taxonomiesIndex[taxonomyUid]?.locale;
223+
let sourceFilePath;
224+
for (const localeCode of [masterLocale, exportedLocale]) {
225+
if (!localeCode) {
226+
continue;
227+
}
228+
const candidatePath = pathValidator(path.resolve(taxonomiesDirPath, sanitizePath(localeCode), fileName));
229+
if (existsSync(candidatePath)) {
230+
sourceFilePath = candidatePath;
231+
break;
232+
}
233+
}
234+
235+
if (!sourceFilePath) {
236+
const localeEntries = await fs.readdir(taxonomiesDirPath, { withFileTypes: true });
237+
for (const localeEntry of localeEntries) {
238+
if (!localeEntry.isDirectory() || localeEntry.name === config.target_locale) {
239+
continue;
240+
}
241+
const candidatePath = pathValidator(
242+
path.resolve(taxonomiesDirPath, sanitizePath(localeEntry.name), fileName),
243+
);
244+
if (existsSync(candidatePath)) {
245+
sourceFilePath = candidatePath;
246+
break;
247+
}
248+
}
249+
}
250+
251+
if (!sourceFilePath) {
252+
console.log(`Skipping taxonomy '${taxonomyUid}' - no source locale data found`);
253+
continue;
254+
}
255+
256+
let sourceTaxonomy = await fs.readFile(sourceFilePath, { encoding: 'utf8' });
257+
sourceTaxonomy = JSON.parse(sourceTaxonomy);
258+
259+
if (existsSync(targetFilePath)) {
260+
let targetTaxonomy = await fs.readFile(targetFilePath, { encoding: 'utf8' });
261+
targetTaxonomy = JSON.parse(targetTaxonomy);
262+
targetTaxonomy.terms = targetTaxonomy.terms || [];
263+
264+
const existingTermUids = new Set(targetTaxonomy.terms.map((term) => term.uid));
265+
for (const term of sourceTaxonomy.terms || []) {
266+
if (!existingTermUids.has(term.uid)) {
267+
targetTaxonomy.terms.push(JSON.parse(JSON.stringify(term)));
268+
}
269+
}
270+
271+
await fs.writeFile(targetFilePath, JSON.stringify(targetTaxonomy));
272+
} else {
273+
await fs.mkdir(targetLocaleDirPath, { recursive: true });
274+
275+
const targetTaxonomy = JSON.parse(JSON.stringify(sourceTaxonomy));
276+
targetTaxonomy.taxonomy = targetTaxonomy.taxonomy || {};
277+
targetTaxonomy.taxonomy.locale = config.target_locale;
278+
279+
await fs.writeFile(targetFilePath, JSON.stringify(targetTaxonomy));
280+
}
281+
}
282+
}
283+
197284
await tailorData();
198285
},
199286
};

0 commit comments

Comments
 (0)