Skip to content
Open
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
16 changes: 8 additions & 8 deletions upload-api/migration-wordpress/libs/extractEntries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const contentTypeFolderPath = path.resolve(config?.data, contentTypesConfig?.dir

const EXCLUDED_POST_TYPES = new Set(['attachment', 'wp_global_styles', 'wp_navigation']);

const ALLOWED_POST_STATUSES = new Set(['publish', 'inherit']);

const normalizeArray = <T>(value: T | T[] | undefined): T[] => {
if (!value) return [];
return Array.isArray(value) ? value : [value];
Expand Down Expand Up @@ -47,10 +49,7 @@ const getEntryName = (item: any): string => {
return 'Untitled Entry';
};

/**
* All WordPress source entry keys use `posts_${...}` (any content type) so they align with
* wordpress.service export JSON and CLI uid-mapping.
*/

const getSourceEntryUid = (item: any): string => {
const postId = item?.['wp:post_id'];
if (postId != null && String(postId).trim() !== '') {
Expand All @@ -59,12 +58,12 @@ const getSourceEntryUid = (item: any): string => {

const authorId = item?.['wp:author_id'];
if (authorId != null && String(authorId).trim() !== '') {
return idCorrector(`posts_${authorId}`);
return idCorrector(`authors_${authorId}`);
}
Comment thread
aishwarya-cstk marked this conversation as resolved.

const termId = item?.['wp:term_id'];
if (termId != null && String(termId).trim() !== '') {
return idCorrector(`posts_${termId}`);
return idCorrector(`terms_${termId}`);
}
Comment thread
aishwarya-cstk marked this conversation as resolved.
Comment thread
aishwarya-cstk marked this conversation as resolved.

const candidate =
Expand Down Expand Up @@ -102,6 +101,8 @@ const extractEntries = async (filePath: string, contentTypeData: any[] = []) =>
const groupedByType = items?.reduce((acc: Record<string, any[]>, item: any) => {
const postType = item?.['wp:post_type'] || 'unknown';
if (EXCLUDED_POST_TYPES.has(postType)) return acc;
const postStatus = String(item?.['wp:status'] || '').toLowerCase();
if (!ALLOWED_POST_STATUSES.has(postStatus)) return acc;
if (!acc[postType]) acc[postType] = [];
acc[postType].push(item);
return acc;
Expand All @@ -112,7 +113,7 @@ const extractEntries = async (filePath: string, contentTypeData: any[] = []) =>
if (authorData) {
const authorEntries = normalizeArray(authorData).map((author: any) => ({
'wp:post_type': 'author',
'wp:post_id': author?.['wp:author_id'],
'wp:author_id': author?.['wp:author_id'],
title: author?.['wp:author_display_name'] || author?.['wp:author_login'],
'wp:author_login': author?.['wp:author_login'],
'wp:author_email': author?.['wp:author_email'],
Expand All @@ -130,7 +131,6 @@ const extractEntries = async (filePath: string, contentTypeData: any[] = []) =>
if (termData) {
const termEntries = normalizeArray(termData).map((term: any) => ({
'wp:post_type': 'terms',
'wp:post_id': term?.['wp:term_id'],
title: term?.['wp:term_name'] || term?.['wp:term_slug'],
'wp:term_id': term?.['wp:term_id'],
'wp:term_taxonomy': term?.['wp:term_taxonomy'],
Expand Down
11 changes: 7 additions & 4 deletions upload-api/migration-wordpress/libs/extractItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as cheerio from 'cheerio';


import { setupWordPressBlocks } from "../utils/parseUtil";
import { clientIdForUid, getFieldName, getFieldUid, schemaMapper } from "./schemaMapper";
import { stableSuffix, getFieldName, getFieldUid, schemaMapper } from "./schemaMapper";
import helper from "../utils/helper";
import config from '../config/index.json';
import extractTaxonomy from './extractTaxonomy';
Expand Down Expand Up @@ -365,7 +365,10 @@ const extractItems = async (item: any, config: DataConfig, type: string, affix:
// Track processed similar blocks to avoid duplicates

for (const field of blocksJson) {
const fieldUid = getFieldUid(`${field?.name}_${clientIdForUid(field?.clientId)}`|| '', affix || '');
// Guard a missing block name (freeform/whitespace separators parse with name: null)
// so the UID never gets a literal "undefined" prefix.
const blockName = field?.name || 'block';
const fieldUid = getFieldUid(`${blockName}${stableSuffix(field)}`, affix || '');
const contentstackFieldName = getFieldName(resolveBlockName(field));
Comment thread
aishwarya-cstk marked this conversation as resolved.

const similarBlocks = findSimilarBlocks(result, field?.clientId);
Expand Down Expand Up @@ -428,7 +431,7 @@ const extractItems = async (item: any, config: DataConfig, type: string, affix:
// No duplicate found - add the modular block child
if(Schema?.length > 0){
CT?.push?.({
"uid": `modular_blocks.${getFieldUid(`${field?.name}_${clientIdForUid(field?.clientId)}`, affix)}`,
"uid": `modular_blocks.${getFieldUid(`${blockName}${stableSuffix(field)}`, affix)}`,
"backupFieldUid": `modular_blocks.${fieldUid}`,
"contentstackFieldUid": `modular_blocks.${fieldUid}`,
"otherCmsField": contentstackFieldName,
Expand Down Expand Up @@ -484,7 +487,7 @@ const extractItems = async (item: any, config: DataConfig, type: string, affix:
// No duplicate found - add the modular block child
if(Schema?.length > 0){
CT?.push?.({
"uid": `modular_blocks.${getFieldUid(`${field?.name}_${clientIdForUid(field?.clientId)}`, affix)}`,
"uid": `modular_blocks.${getFieldUid(`${blockName}${stableSuffix(field)}`, affix)}`,
"backupFieldUid": `modular_blocks.${fieldUid}`,
"contentstackFieldUid": `modular_blocks.${fieldUid}`,
"otherCmsField": contentstackFieldName,
Expand Down
80 changes: 60 additions & 20 deletions upload-api/migration-wordpress/libs/schemaMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,46 @@ export function clientIdForUid(clientId: string | undefined): string {
const compact = clientId?.replace?.(/-/g, '')?.toLowerCase();
return compact?.slice?.(0, 4) || '0';
}

/** Slugify an author-supplied identifier into a UID-safe token (lowercase, `_`-joined). */
const slugifyIdentifier = (value: unknown): string => {
if (value == null) return '';
return String(value)
.toLowerCase()
.replace(/[^a-z0-9]+/g, '_')
.replace(/^_+|_+$/g, '');
};

/** Cap for the identity suffix so UIDs stay short. */
const MAX_SUFFIX_LEN = 40;

/**
* Deterministic, iteration-stable suffix for a block's field UID.
*
* The UID used to embed `clientIdForUid(block.clientId)`, but Gutenberg's parse()
* mints a fresh random clientId on every parse β€” so the UID changed on each
* re-migration and Contentstack treated the field as brand new, never updating
* the existing content on a 2nd iteration.
*
* This keys off `attributes.metadata.name` β€” the explicit author "Rename block"
* label β€” which survives re-parse, content edits, and reordering. It is the ONLY
* attribute the block de-duplication (`resolveBlockName`) uses to decide whether
* two same-named blocks are separate fields, so it is the correct β€” and only
* meaningful β€” disambiguator.
*
* We deliberately do NOT use `anchor`/`id`: those don't affect field separation
* and are frequently auto-derived from the block's text, which would make UIDs
* long and couple them to content (an edit would change the UID). Anonymous
* blocks therefore get NO suffix β€” the block name alone is a stable, unique UID
* within its scope.
*
* Returns a leading-underscore token (e.g. "_hero") or "" when anonymous, so
* callers can append it directly after the block name.
*/
export function stableSuffix(key: any): string {
const identity = slugifyIdentifier(key?.attributes?.metadata?.name).slice(0, MAX_SUFFIX_LEN);
return identity ? `_${identity}` : '';
}


async function processInnerBlocks(key: WordPressBlock, parentUid: string | null = null, parentFieldName: string | null = null, affix: string | null = null): Promise<any[]> {
Expand Down Expand Up @@ -207,8 +247,8 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
case 'core/verse':
case 'core/code': {
const rteUid = parentUid ?
`${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`
: getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
`${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}`
: getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);
return {
uid: rteUid,
otherCmsField: getFieldName(key?.name),
Expand All @@ -223,8 +263,8 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
}
case 'core/missing':
const rteUid = parentUid ?
`${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`
: getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
`${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}`
: getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);
if(key?.attributes?.originalName === 'jetpack/markdown'){
return {
uid: rteUid,
Expand Down Expand Up @@ -296,7 +336,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
case 'core/audio':
case 'core/video':
case 'core/file': {
const fileUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const fileUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);

return {
uid: fileUid,
Expand All @@ -314,7 +354,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
case 'core/heading':
case 'core/accordion-heading':
case 'core/list-item': {
const textUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const textUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);
return {
uid: textUid,
otherCmsField: getFieldName(key?.name),
Expand All @@ -331,7 +371,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
case 'core/social-link':
case 'core/navigation-link': {

const LinkUid = parentUid ? `${parentUid}.${getFieldUid(key?.name, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const LinkUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);
return {
Comment thread
aishwarya-cstk marked this conversation as resolved.
uid: LinkUid,
otherCmsField: getFieldName(key?.name),
Expand Down Expand Up @@ -377,7 +417,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
}

const groupSchema: Field[] = [];
const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);

const innerBlocks = await processInnerBlocks(
key,
Expand Down Expand Up @@ -423,7 +463,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
case 'core/accordion-panel':
case 'core/navigation': {
const groupSchema: Field[] = [];
const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);

const innerBlocks = await processInnerBlocks(
key,
Expand Down Expand Up @@ -465,14 +505,14 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
const coverSchema: Field[] = []
if(key?.attributes?.url){
coverSchema.push({
uid: `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`,
uid: `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}`,
otherCmsField: 'media',
Comment thread
aishwarya-cstk marked this conversation as resolved.
otherCmsType: getFieldName(key?.attributes?.metadata?.name ?? key?.name),
contentstackField: 'media',
contentstackFieldUid: `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`,
contentstackFieldUid: `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}`,
contentstackFieldType: 'file',
backupFieldType: 'file',
backupFieldUid: `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`,
backupFieldUid: `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}`,
advanced: {}
});
}
Expand All @@ -498,7 +538,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:


case 'core/search': {
const searchEleUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const searchEleUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);
const searchEle = await processAttributes(key, searchEleUid,fieldName, affix);
const groupSchema: Field[] = [];
searchEle?.length > 0 && groupSchema?.push({
Expand Down Expand Up @@ -526,7 +566,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:

case 'core/button': {
const fieldName = parentFieldName ? `${parentFieldName} > ${getFieldName(key?.attributes?.metadata?.name ?? key?.name)}` : `${getFieldName(key?.attributes?.metadata?.name ?? key?.name)}` ;
const buttonUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const buttonUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);

return {
uid: buttonUid,
Expand All @@ -544,7 +584,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:

case 'core/buttons': {
const groupSchema: Field[] = [];
const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);

const innerBlocks = await processInnerBlocks(
key,
Expand All @@ -555,13 +595,13 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:
if (innerBlocks?.length === 1) {
const items = Array.isArray(innerBlocks[0]) ? innerBlocks[0] : [innerBlocks[0]];
items?.forEach((item: Field) => {
item.uid = `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`;

item.uid = groupUid;
item.otherCmsField = getFieldName(resolveBlockName(key));
item.otherCmsType = getFieldName(resolveBlockName(key));
item.contentstackField = `${parentFieldName} > ${getFieldName(resolveBlockName(key))}`;
item.contentstackFieldUid = `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`;
item.backupFieldUid = `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`;
item.contentstackFieldUid = groupUid;
item.backupFieldUid = groupUid;
Comment thread
aishwarya-cstk marked this conversation as resolved.
});
return items;
}
Expand Down Expand Up @@ -596,7 +636,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid:

case 'core/media-text': {
const mediaTextSchema: Field[] = [];
const mediaTextUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix);
const mediaTextUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}${stableSuffix(key)}`, affix)}` : getFieldUid(`${key?.name}${stableSuffix(key)}`, affix);
const innerBlocks =
key?.innerBlocks && key?.innerBlocks?.length > 0
? await processInnerBlocks(key, parentUid, parentFieldName, affix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,9 @@ describe('schemaMapper', () => {
const result = await schemaMapper(block, 'page', 'Page', affix);

result.forEach((field: any) => {
expect(field.contentstackFieldUid).toBe(`page.${getFieldUid('core/buttons_btns', affix)}`);
expect(field.uid).toBe(`page.${getFieldUid('core/buttons_btns', affix)}`);
expect(field.backupFieldUid).toBe(`page.${getFieldUid('core/buttons_btns', affix)}`);
expect(field.contentstackFieldUid).toBe(`page.${getFieldUid('core/buttons', affix)}`);
expect(field.uid).toBe(`page.${getFieldUid('core/buttons', affix)}`);
expect(field.backupFieldUid).toBe(`page.${getFieldUid('core/buttons', affix)}`);
expect(field.contentstackField).toContain('Page > ');
});
});
Expand Down Expand Up @@ -259,7 +259,7 @@ describe('schemaMapper', () => {

expect(Array.isArray(result)).toBe(true);
const p = result[0] as any;
expect(p.uid).toMatch(/^panel_uid\.paragraph_/);
expect(p.uid).toBe('panel_uid.paragraph');
expect(p.contentstackField).toBe('Panel > paragraph');
});

Expand Down
Loading