Skip to content
Draft
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
47 changes: 40 additions & 7 deletions packages/contentstack-import/src/import/modules/taxonomies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,35 @@ export default class ImportTaxonomies extends BaseClass {
log.debug('Using legacy folder structure for taxonomies', this.importConfig.context);
}

//Step 5 create taxonomy & related terms success & failure file
// Step 5: Flag taxonomies that were never processed (no matching export data
// found in any locale/legacy path), so they don't silently disappear.
for (const taxonomyUID of Object.keys(this.taxonomies || {})) {
if (!(taxonomyUID in this.createdTaxonomies) && !(taxonomyUID in this.failedTaxonomies)) {
log.error(
`Taxonomy '${taxonomyUID}' could not be imported: no matching export data found`,
this.importConfig.context,
);
this.failedTaxonomies[taxonomyUID] = this.taxonomies[taxonomyUID];
}
}

//Step 6 create taxonomy & related terms success & failure file
log.debug('Creating success and failure files...', this.importConfig.context);
this.createSuccessAndFailedFile();

log.success('Taxonomies imported successfully!', this.importConfig.context);
const createdCount = Object.keys(this.createdTaxonomies).length;
const failedCount = Object.keys(this.failedTaxonomies).length;

if (failedCount > 0) {
log.error(
`Taxonomies import completed with errors: ${createdCount} succeeded, ${failedCount} failed`,
this.importConfig.context,
);
} else if (createdCount > 0) {
log.success('Taxonomies imported successfully!', this.importConfig.context);
} else {
log.info('No taxonomies to import.', this.importConfig.context);
}
}

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

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

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

return true;
log.debug('No locale-based folder structure detected', this.importConfig.context);
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ module.exports = async ({ migration, config }) => {
}

async function tailorData() {
let locales = await fs.readFile(pathValidator(path.resolve(sanitizePath(config.data_dir), 'locales/locales.json')), 'utf-8');
let locales = await fs.readFile(
pathValidator(path.resolve(sanitizePath(config.data_dir), 'locales/locales.json')),
'utf-8',
);
let masterLocale = await fs.readFile(
pathValidator(path.resolve(sanitizePath(config.data_dir), 'locales/master-locale.json')),
'utf-8',
Expand All @@ -34,12 +37,14 @@ module.exports = async ({ migration, config }) => {
if (masterLocale) {
masterLocale = JSON.parse(masterLocale);
masterLocale = Object.values(masterLocale);
masterLocale = masterLocale[0]
masterLocale = masterLocale[0];

// Validate that we have a valid master locale code
if (!masterLocale) {
if (!masterLocale || !masterLocale.code) {
throw new Error('Unable to determine master locale code from master-locale.json');
}

masterLocale = masterLocale.code;
}
locales = JSON.parse(locales);
let id = crypto.randomBytes(8).toString('hex');
Expand All @@ -60,6 +65,7 @@ module.exports = async ({ migration, config }) => {
locales[id].fallback_locale = config.target_locale;

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

// Check if index.json exists (if no entries, index.json won't be created)
const indexFilePath = pathValidator(path.resolve(sanitizePath(config.data_dir), sanitizePath(`entries/${contentType}/${masterLocale}/index.json`)));
const indexFilePath = pathValidator(
path.resolve(
sanitizePath(config.data_dir),
sanitizePath(`entries/${contentType}/${masterLocale}/index.json`),
),
);
if (!existsSync(indexFilePath)) {
console.log(`Skipping ${contentType} - no index.json found (likely no entries)`);
continue;
}

sourceMasterLocaleEntries = await fs.readFile(
indexFilePath,
{ encoding: 'utf8' },
);
sourceMasterLocaleEntries = await fs.readFile(indexFilePath, { encoding: 'utf8' });

// Parse the index.json to get the entries file name
const indexData = JSON.parse(sourceMasterLocaleEntries);
const entriesFileName = Object.values(indexData)[0];

// Check if we have a valid entries file name
if (!entriesFileName) {
console.log(`Skipping ${contentType} - no entries file found in index.json`);
Expand All @@ -112,10 +120,7 @@ module.exports = async ({ migration, config }) => {
),
);

sourceMasterLocaleEntries = await fs.readFile(
entriesFilePath,
{ encoding: 'utf8' },
);
sourceMasterLocaleEntries = await fs.readFile(entriesFilePath, { encoding: 'utf8' });
sourceMasterLocaleEntries = JSON.parse(sourceMasterLocaleEntries);
if (
existsSync(pathValidator(path.resolve(config.data_dir, `entries/${contentType}/${config.target_locale}`)))
Expand All @@ -127,7 +132,7 @@ module.exports = async ({ migration, config }) => {
if (targetMasterLocaleEntries) {
const targetIndexData = JSON.parse(targetMasterLocaleEntries);
const targetEntriesFileName = Object.values(targetIndexData)[0];

if (targetEntriesFileName) {
targetMasterLocaleEntries = await fs.readFile(
pathValidator(
Expand All @@ -152,7 +157,7 @@ module.exports = async ({ migration, config }) => {
Object.keys(sourceMasterLocaleEntries).forEach((uid) => {
if (!targetMasterLocaleEntries[uid]) {
targetMasterLocaleEntries[uid] = JSON.parse(JSON.stringify(sourceMasterLocaleEntries[uid]));
delete targetMasterLocaleEntries[uid]['publish_details'];
targetMasterLocaleEntries[uid]['publish_details'] = [];
targetMasterLocaleEntries[uid].locale = config.target_locale;
}
});
Expand All @@ -164,10 +169,10 @@ module.exports = async ({ migration, config }) => {
pathValidator(path.resolve(config.data_dir, `entries/${contentType}/${config.target_locale}/index.json`)),
{ encoding: 'utf8', flag: 'a+' },
);

const existingIndexData = JSON.parse(exsitingTargetMasterLocalEntries);
const existingEntriesFileName = Object.values(existingIndexData)[0];

if (existingEntriesFileName) {
await fs.writeFile(
pathValidator(
Expand All @@ -194,6 +199,88 @@ module.exports = async ({ migration, config }) => {
}
}

async function handleTaxonomies(masterLocale) {
const taxonomiesDirPath = pathValidator(path.resolve(sanitizePath(config.data_dir), 'taxonomies'));
const taxonomiesIndexPath = pathValidator(path.resolve(taxonomiesDirPath, 'taxonomies.json'));

if (!existsSync(taxonomiesIndexPath)) {
console.log('Skipping taxonomies - no taxonomies.json found');
return;
}

let taxonomiesIndex = await fs.readFile(taxonomiesIndexPath, { encoding: 'utf8' });
taxonomiesIndex = JSON.parse(taxonomiesIndex);

const targetLocaleDirPath = pathValidator(path.resolve(taxonomiesDirPath, sanitizePath(config.target_locale)));

for (const taxonomyUid of Object.keys(taxonomiesIndex)) {
const fileName = `${sanitizePath(taxonomyUid)}.json`;
const targetFilePath = pathValidator(path.resolve(targetLocaleDirPath, fileName));

// Prefer the old master locale's taxonomy data, then the locale recorded at export time,
// then fall back to any other locale that has it
const exportedLocale = taxonomiesIndex[taxonomyUid]?.locale;
let sourceFilePath;
for (const localeCode of [masterLocale, exportedLocale]) {
if (!localeCode) {
continue;
}
const candidatePath = pathValidator(path.resolve(taxonomiesDirPath, sanitizePath(localeCode), fileName));
if (existsSync(candidatePath)) {
sourceFilePath = candidatePath;
break;
}
}

if (!sourceFilePath) {
const localeEntries = await fs.readdir(taxonomiesDirPath, { withFileTypes: true });
for (const localeEntry of localeEntries) {
if (!localeEntry.isDirectory() || localeEntry.name === config.target_locale) {
continue;
}
const candidatePath = pathValidator(
path.resolve(taxonomiesDirPath, sanitizePath(localeEntry.name), fileName),
);
if (existsSync(candidatePath)) {
sourceFilePath = candidatePath;
break;
}
}
}

if (!sourceFilePath) {
console.log(`Skipping taxonomy '${taxonomyUid}' - no source locale data found`);
continue;
}

let sourceTaxonomy = await fs.readFile(sourceFilePath, { encoding: 'utf8' });
sourceTaxonomy = JSON.parse(sourceTaxonomy);

if (existsSync(targetFilePath)) {
let targetTaxonomy = await fs.readFile(targetFilePath, { encoding: 'utf8' });
targetTaxonomy = JSON.parse(targetTaxonomy);
targetTaxonomy.terms = targetTaxonomy.terms || [];

const existingTermUids = new Set(targetTaxonomy.terms.map((term) => term.uid));
for (const term of sourceTaxonomy.terms || []) {
if (!existingTermUids.has(term.uid)) {
targetTaxonomy.terms.push(JSON.parse(JSON.stringify(term)));
}
}

await fs.writeFile(targetFilePath, JSON.stringify(targetTaxonomy));
} else {
await fs.mkdir(targetLocaleDirPath, { recursive: true });

const targetTaxonomy = JSON.parse(JSON.stringify(sourceTaxonomy));
targetTaxonomy.taxonomy = targetTaxonomy.taxonomy || {};
targetTaxonomy.taxonomy.locale = config.target_locale;

await fs.writeFile(targetFilePath, JSON.stringify(targetTaxonomy));
}
}
}

await tailorData();
},
};
Expand Down
Loading